Nästan varje framgångsrik affärsapplikation når så småningom en fas där horisontell skalning krävs. I många fall kan man enkelt starta en ny instans och minska den genomsnittliga belastningen. Men det finns också mindre triviala fall där vi måste se till att olika noder känner till varandra och effektivt fördelar arbetsbelastningen.

Det passade så bra att erlang, som vi valde för sin trevliga syntax och hypen runt omkring, har förstklassigt . I teorin låter det helt trivialt:
Meddelandeöverföring mellan processer på olika noder, liksom mellan länkar och övervakare, är transparent […]
I praktiken är allt lite mer komplicerat. Distribuerad erlang utvecklades när 'behållare' betydde en stor järnlåda för transport, och 'docker' bara var en synonym för hamnarbetare. I IP4 fanns det många oanvända adresser, och nätverksavbrott berodde oftast på gnagare som hade bitit av kablar, medan den genomsnittliga driftsäkerhetstiden för ett produktionssystem mättes i årtionden.
Nu är vi alla ofantligt självförsörjande, paketerade, och kör en distribuerad erlang i en miljö där dynamiska IP-adresser tilldelas enligt principen om stora slumpen, och noder kan dyka upp och försvinna i takt med schemaläggarens vilja. För att undvika en hög av mallkod i varje projekt som kör en distribuerad erlang, behöver vi hjälp med att hantera den ogynnsamma miljön.
Anmärkning: Jag är medveten om att det finns . Det är verkligen coolt, har mer än tusen stjärnor, författaren är känd i gemenskapen, och så vidare. Om det räcker med de metoder som erbjuds av detta paket för att skapa och underhålla en kluster – jag är glad för din skull. Tyvärr behöver jag mycket mer. Jag vill hantera konfigurationen i detalj och inte vara en åskådare i teatern av klusteromformning.
Krav
Vad jag personligen behövde var ett bibliotek som tar hand om klusterhantering och har följande egenskaper:
- transparent funktionalitet både med en hårdkodad lista av noder och med dynamisk upptäckts genom tjänster erlang;
- fullfjädrad callback vid varje förändring i topologin (nod dit, nod hit, nätverksinstabilitet, splittringar);
- ett transparent gränssnitt för att starta ett kluster med både långa och korta namn, precis som med
:nonode@nohost; - inbyggt stöd för Docker utan att behöva skriva infrastrukturkod.
Detta innebär att efter att jag har testat applikationen lokalt i :nonode@nohost, eller i en artificiellt distribuerad miljö med hjälp av , vill jag bara köra docker-compose up --scale my_app=3 och se hur det kör tre instanser i Docker utan några kodändringar. Jag vill även att beroendeapplikationer, som mnesia — när topologin förändras, ska klustret omkonfigureras i bakgrunden utan ytterligare påtryckningar från applikationen.
Cloister är inte avsedd som ett bibliotek som kan göra allt: från klusterhantering till att brygga kaffe. Det är inte en silverkula som strävar efter att täcka alla möjliga fall, eller vara en akademiskt komplett lösning i den bemärkelsen som teoretiker inom CS lägger i denna term. Detta bibliotek är avsett att tjäna ett mycket tydligt syfte, men att utföra sin begränsade uppgift perfekt. Detta syfte är att säkerställa fullständig transparens mellan den lokala utvecklingsmiljön och den distribuerade elastiska miljön med alla dess fientliga containrar.
Den valda metoden
Cloister är avsedd att köras som en applikation, även om erfarna användare kan arbeta med installation och underhåll av klustret manuellt genom att direkt köra Cloister.Manager i övervakningsträdet av målapplikationen.
Vid körning som en applikation förlitar sig biblioteket på config, varifrån det läser följande huvudvärden:
config :cloister,
otp_app: :my_app,
sentry: :"cloister.local", # eller ~w|n1@foo n2@bar|a
consensus: 3, # antal noder att beakta
# klustret är aktivt
listener: MyApp.Listener # lyssnare som ska anropas när
# ringen har förändratsOvanstående parametrar betyder bokstavligen följande: Cloister används för OTP-applikationen :my_app, använder erlang service discovery för att ansluta minst tre noder, och MyApp.Listener modul (implementerar ) är konfigurerad för att ta emot meddelanden om toppologiförändringar. En detaljerad beskrivning av hela konfigurationen kan hittas i .
Med denna konfiguration kommer applikationen Cloister För användare med migrän och epilepsi har vissa animations effekter vid öppning av flikar tagits bort. Till exempel, istället för den hoppande punkten visas nu en ikon med sandur under tiden fliken laddar. , och skjuter upp processen för att starta huvudapplikationen tills konsensus nås (tre noder är anslutna och sammankopplade, som i det ovanstående exemplet.) Detta ger huvudapplikationen möjlighet att anta att klustret redan är tillgängligt när det har startat. Vid varje förändring i topologin (det kommer att bli många, eftersom noder startas asynkront) kommer en hanterare att anropas . I de flesta fall utför vi en åtgärd när vi får ett meddelande om tillstånd %Cloister.Monitor{status: :up}, vilket betyder: "Hej, klustret är sammanställt."
I de flesta fall är inställningen consensus: 3 optimal, eftersom även om vi förväntar oss att fler noder ansluter, kommer tillbaka att ske genom status: :rehashing → status: :up för varje ny tillagd eller borttagen nod.
När du kör i utvecklingsläge är det tillräckligt att bara ställa in consensus: 1 och Cloister så att det glatt hoppar över väntan på att klustret ska byggas, när det ser :nonode@nohost, eller :node@host, eller :node@host.domain — beroende på hur noden har konfigurerats (:none | :shortnames | :longnames).
Hantera distribuerade applikationer
Distribuerade applikationer finns vanligtvis inte i vakuum och omfattar vanligen även distribuerade beroenden, såsom mnesia. Det är enkelt för oss att hantera deras omkonfiguration från samma tillbaka-anrop on_state_change/2. Här är till exempel en detaljerad beskrivning av hur man omkonfigurerar mnesia i farten i .
Den största fördelen med att använda Cloister är att den utför alla nödvändiga operationer för att rekonstruera klustret efter att topologin har ändrats under huven. Applikationen startar helt enkelt i en redan förberedd distribuerad miljö, med alla anslutna noder, oavsett om vi känner till IP-adresserna och därmed nodnamnen i förväg, eller om de har tilldelats/ändrats dynamiskt. Det kräver inga speciella inställningar av dokers konfiguration och från en applikationsutvecklares perspektiv finns det ingen skillnad mellan att köra i en distribuerad miljö eller lokalt på :nonode@nohost. Mer information om detta kan läsas i .
Trots att komplex behandling av förändringar i topologin är möjlig genom en egen implementation MyApp.Listener, kan det alltid finnas gränsfall där dessa bibliotekens begränsningar och partiska konfiguration kan utgöra en hinder för implementeringen. Det är okej, bara ta det ovan nämnda libcluster, som är mer universell, eller till och med hantera en låg-nivåkluster själv. Målet med detta kodbibliotek är inte att täcka alla möjliga scenarier, utan att använda det mest förekommande scenariot utan onödig smärta och klumpiga kopieringar och klistermärken.
Anmärkning: På denna plats i originalet fanns frasen «Happy clustering!», och Yandex, som jag översätter för (ska jag gå igenom ordböcker för mig själv?), föreslog mig variationen «Lycklig klustring!». En bättre översättning går knappast att föreställa sig, särskilt med tanke på den nuvarande geopolitiska situationen.
Källa: habr.com
