Næsten hver succesfuld forretningsapplikation når en fase, hvor horisontal skalerering er nødvendig. I mange tilfælde kan man blot starte en ny instans og reducere den gennemsnitlige belastning. Men der er også mindre trivielle tilfælde, hvor vi skal sørge for, at forskellige noder ved, hvem de er, og omhyggeligt fordeler arbejdsbelastningen.

Det lykkedes heldigvis, at erlang, som vi valgte på grund af den behagelige syntaks og hypen omkring det, har fremragende . I teorien lyder det helt trivielt:
Meddelelsesoverførsel mellem processer på forskellige noder samt mellem referencer og monitorer er gennemsigtig […]
I praksis er alt lidt mere kompliceret. Den distribuerede erlang blev udviklet, da "container" betød en så stor metalboks til transport, og "docker" bare var et synonym for havnearbejder. I IP4 var der mange ledige adresser, i netværksbrud var det som regel de gnavere, der havde gnasket i kablerne, og den gennemsnitlige driftsikkerhed for produktionssystemer blev målt i årtier.
Nu er vi allesammen ufatteligt selvforsynende, pakket ind, og kører den fordelte erlang i et miljø, hvor dynamiske IP-adresser tildeles efter princippet om stor tilfældighed, og noder kan komme og gå efter den venstrehånds planlæggers humør. For at undgå en bunke skabelonkode i hvert projekt, der kører den fordelte erlang, er der brug for hjælp til at håndtere det ugunstige miljø.
Bemærk: Jeg er klar over, at der er . Det er virkelig fedt, det har mere end tusind stjerner, forfatteren er kendt i samfundet, og alt det der. Hvis du er tilfreds med de metoder, som dette bibliotek tilbyder til oprettelse og vedligeholdelse af et klynge - er jeg glad for dig. Jeg har desværre brug for meget mere. Jeg vil gerne styre konfigurationen i detaljer og ikke blot være en tilskuer i omformningen af klyngen.
Krav
Hvad jeg personligt havde brug for, var et bibliotek, der kunne tage styringen over klyngen og have følgende egenskaber:
- gennemsigtig drift både med en hårdkodet liste af noder og med dynamisk opdagelse gennem tjenester erlang;
- fuld funktionsdygtig callback ved hver ændring af topologi (node der, node hit, netværksinstabilitet, splits);
- en gennemsigtig grænseflade til opstart af et cluster med lange og korte navne, ligesom med
:nonode@nohost; - understøttelse af Docker fra starten, uden behov for at skrive infrastrukturolige kode.
Det betyder, at efter jeg har testet applikationen lokalt i :nonode@nohost, eller i en kunstigt distribueret miljø ved hjælp af , ønsker jeg bare at starte docker-compose up --scale my_app=3 og se, hvordan det kører tre instanser i Docker uden ændringer i koden. Jeg vil også have, at afhængige applikationer, såsom mnesia — når topologien ændres, vil clusteret blive ombygget bag kulisserne uden yderligere input fra applikationen.
Cloister er ikke tænkt som et bibliotek, der kan gøre alt: fra clusterunderstøttelse til at lave kaffe. Det er ikke en sølvkugle, der stræber efter at dække alle mulige scenarier, eller være en akademisk fuldstændig løsning i den forstand, som teoretikere fra CS læser ind i dette begreb. Dette bibliotek er designet til at tjene et meget klart formål, men at udføre sit ikke alt for store arbejdsvolumen perfekt. Dette mål vil være at sikre fuld gennemsigtighed mellem det lokale udviklingsmiljø og det distribuerede elastiske miljø, fyldt med modstridende containere.
Den valgte tilgang
Cloister forudsætter, at det køres som en applikation, selvom erfarne brugere kan arbejde med bygning og vedligeholdelse af clusteret manuelt ved direkte at starte Cloister.Manager i træet af supervisorer for den målrettede applikation.
Når det køres som en applikation, afhænger biblioteket af config, hvorfra følgende grundlæggende værdier hentes:
config :cloister,
otp_app: :my_app,
sentry: :"cloister.local", # eller ~w|n1@foo n2@bar|a
consensus: 3, # antal noder at overveje
# clusteret er oppe
listener: MyApp.Listener # lytter til at blive kaldt når
# ringen er ændretDe ovenstående indstillinger betyder bogstaveligt talt følgende: Cloister der bruges til et OTP-app :my_app, bruger erlang service discovery til at forbinde noder, mindst tre, og MyApp.Listener modul (implementerende ) er konfigureret til at modtage meddelelser om ændringer i topologien. En detaljeret beskrivelse af den fulde konfiguration kan findes i .
Med denne konfiguration, vil applikationen Cloister det vil , og udsætter opstartsprocessen for hovedapplikationen, indtil konsensus opnås (tre noder er tilsluttet og forbundet, som i det ovenstående eksempel.) Dette giver hovedapplikationen mulighed for at forudsætte, at når den er startet, er klyngen allerede tilgængelig. Ved hver ændring af topologien (som vil være mange, da noderne ikke startes helt synkront) vil en handler blive kaldt . I de fleste tilfælde udfører vi en handling, når vi modtager en besked med tilstanden %Cloister.Monitor{status: :up}, hvilket betyder: "hej, klyngen er samlet."
I de fleste tilfælde er installationen consensus: 3 optimal, fordi selvom vi forventer, at flere noder tilslutter sig, vil callback'en gå igennem status: :rehashing → status: :up på enhver ny tilføjet eller fjernet node.
Når vi kører i udviklingstilstand, er det tilstrækkeligt blot at indstille consensus: 1 og Cloister og den vil lykkeligt springe over ventetiden for at samle klyngen, når den ser :nonode@nohost, eller :node@host, eller :node@host.domain — afhængig af hvordan noden blev konfigureret (:none | :shortnames | :longnames).
Håndtering af distribuerede applikationer
Distribuerede applikationer eksisterer ikke i et vakuum, de inkluderer typisk også distribuerede afhængigheder, som mnesia. Vi har nemt ved at håndtere deres rekonfiguration fra den samme callback on_state_change/2. Her er et eksempel på, hvordan man rekonfigurerer mnesia on the fly in .
Den største fordel ved at bruge Cloister er, at den udfører alle de nødvendige operationer for at rekonstruere klyngen efter ændringen af topologi under panseret. Applikationen starter blot i et allerede forberedt distribueret miljø, med alle tilsluttede noder, uanset om vi kender IP-adresserne og dermed node-navnene på forhånd, eller de er blevet tildelt/ændret dynamisk. Dette kræver ingen specielle konfigurationstræk ved Docker og set fra applikationsudviklerens synspunkt er der ingen forskel mellem at køre i et distribueret miljø eller lokalt på :nonode@nohost. Du kan læse mere om det i .
Selvom kompleks håndtering af ændringer i topologi er mulig gennem egen implementering MyApp.Listener, kan der altid være grænsetilfælde, hvor disse biblioteksbegrænsninger og forudindtagne tilgang til konfiguration vil være en hindring for implementering. Det er helt okay; tag bare det nævnte ovenfor libcluster, som er mere alsidig, eller endda håndtere et lavt niveau cluster selv. Formålet med dette kodebibliotek er ikke at dække alle mulige scenarier, men at bruge det mest almindelige scenarie uden unødig smerte og omfattende copy-paste.
Bemærk: på dette sted i originalen var der en sætning «Happy clustering!», og Yandex, som jeg oversætter for (skal jeg ikke selv søge i ordbøger), foreslog mig varianten «Glædelig gruppering!». Bedre oversættelse, især i lyset af den nuværende geopolitiske situation, kan man næppe forestille sig.
Kilde: habr.com
