Kloster → enkel OTP-klusterhantering

Nästan varje framgångsrik affärsapplikation går förr eller senare in i en fas där horisontell skalning krävs. I många fall kan du helt enkelt starta en ny instans och minska belastningsgenomsnittet. Men det finns också mindre triviala fall där vi måste se till att olika noder känner till varandra och noggrant fördela arbetsbördan.

Kloster → enkel OTP-klusterhantering

Det blev så tur att erlang, som vi valde för sin trevliga syntax och hype runt den, har en förstklassig stöd för distribuerade system. I teorin låter detta helt trivialt:

Meddelande som skickas mellan processer på olika noder, såväl som mellan länkar och monitorer, är transparent […]

I praktiken är allt lite mer komplicerat. Distribuerad erlang utvecklades när "container" betydde en stor järnlåda för frakt, och "docker" helt enkelt var en synonym för longshoreman. I IP4 det fanns många lediga adresser, nätverksavbrott orsakades vanligtvis av att råttor tuggade igenom kabeln, och den genomsnittliga drifttiden för produktionssystemet mättes i decennier.

Nu är vi alla otroligt självförsörjande, paketerade och kör distribuerade erlang i en miljö där dynamiska IP-adresser delas ut enligt principen om stor slumpmässighet, och noder kan dyka upp och försvinna efter infall av schemaläggarens vänstra häl. För att undvika högar med kod i varje projekt som kör en distribuerad erlang, för att bekämpa den fientliga miljön krävs hjälp.

Notera: Jag är medveten om att det finns libcluster. Den är riktigt cool, den har över tusen stjärnor, författaren är känd i samhället och allt det där. Om metoderna som erbjuds av detta paket för att skapa och underhålla ett kluster räcker för dig, är jag glad för din skull. Tyvärr behöver jag mycket mer. Jag vill styra upplägget i detalj och inte vara en utomstående åskådare på teatern för klusteromorganisation.

Krav

Vad jag personligen behövde var ett bibliotek som skulle ta över hanteringen av klustret och skulle ha följande egenskaper:

  • transparent arbete med både en hårdkodad lista av noder och dynamisk upptäckt genom tjänster erlang;
  • fullt fungerande återuppringning för varje topologiändring (nod där, nod här, nätverksinstabilitet, uppdelningar);
  • transparent gränssnitt för att starta ett kluster med långa och korta namn, som med :nonode@nohost;
  • Docker-stöd direkt, utan att behöva skriva infrastrukturkod.

Det senare betyder att efter att jag testat applikationen lokalt i :nonode@nohost, eller i en artificiellt distribuerad miljö med hjälp av test_cluster_task, jag vill bara springa docker-compose up --scale my_app=3 och se hur den kör tre instanser i docker utan några kodändringar. Jag vill också ha beroende applikationer som mnesia - när topologin ändras, bakom kulisserna bygger de om klustret live utan någon extra kick från applikationen.

Kloster var inte tänkt att vara ett bibliotek som kan allt från att stödja ett kluster till att göra kaffe. Det är inte en silverkula som syftar till att täcka alla möjliga fall, eller vara en akademiskt komplett lösning i den meningen att teoretiker från CS lägga in i denna term. Det här biblioteket är utformat för att tjäna ett mycket tydligt syfte, men gör sitt inte alltför stora jobb perfekt. Detta mål kommer att vara att ge fullständig transparens mellan den lokala utvecklingsmiljön och en distribuerad elastisk miljö full av fientliga containrar.

Valt tillvägagångssätt

Kloster är tänkt att köras som en applikation, även om avancerade användare kan arbeta med montering och underhåll av klustret manuellt genom att direkt köra Cloister.Manager i målapplikationens supervisor-träd.

När det körs som en applikation förlitar sig biblioteket på config, från vilken den läser följande grundläggande värden:

config :cloister,
  otp_app: :my_app,
  sentry: :"cloister.local", # or ~w|n1@foo n2@bar|a
  consensus: 3,              # number of nodes to consider
                             #    the cluster is up
  listener: MyApp.Listener   # listener to be called when
                             #    the ring has changed

Parametrarna ovan betyder bokstavligen följande: Kloster används för OTP-applikation :my_app, använder erlang tjänst upptäckt för att ansluta noder, minst tre, och MyApp.Listener modul (implementering @behaviour Cloister.Listener) är konfigurerad för att ta emot meddelanden om topologiändringar. En detaljerad beskrivning av den fullständiga konfigurationen finns i dokumentation.

Med denna konfiguration, programmet Kloster kommer lansering i etapper, försenar processen att starta huvudapplikationen tills konsensus uppnås (tre noder är anslutna och anslutna, som i exemplet ovan.) Detta ger huvudapplikationen möjlighet att anta att när den startar är klustret redan tillgängligt. Närhelst topologin ändras (det kommer att finnas många av dem, eftersom noderna inte startar helt synkront), kommer hanteraren att anropas MyApp.Listener.on_state_change/2. För det mesta utför vi en åtgärd när vi får ett statusmeddelande %Cloister.Monitor{status: :up}, vilket betyder: "Hej, klustret är monterat."

I de flesta fall, installation consensus: 3 är optimal eftersom även om vi förväntar oss att fler noder ska ansluta, kommer återuppringningen att gå igenom status: :rehashingstatus: :up på valfri nytillagd eller borttagen nod.

När du startar i utvecklingsläge behöver du bara ställa in consensus: 1 и Kloster kommer gärna att hoppa över väntan på klustermontering när han ser :nonode@nohost, eller :node@host, eller :[email protected] - beroende på hur noden konfigurerades (:none | :shortnames | :longnames).

Distribuerad applikationshantering

Distribuerade applikationer som inte är i ett vakuum inkluderar vanligtvis distribuerade beroenden, som t.ex mnesia. Det är lätt för oss att hantera deras omkonfiguration från samma återuppringning on_state_change/2. Här finns till exempel en detaljerad beskrivning av hur du konfigurerar om mnesia i farten dokumentation Kloster.

Den största fördelen med att använda Kloster är att den utför alla nödvändiga operationer för att bygga om klustret efter en topologiändring under huven. Applikationen körs helt enkelt i en redan förberedd distribuerad miljö, med alla noder anslutna, oavsett om vi känner till IP-adresserna och därmed nodnamnen i förväg, eller om de har blivit dynamiskt tilldelade/ändrade. Detta kräver absolut inga speciella dockarkonfigurationsinställningar och från en applikationsutvecklares synvinkel är det ingen skillnad mellan att köra i en distribuerad miljö eller att köra i en lokal. :nonode@nohost. Du kan läsa mer om detta i dokumentation.

Även om komplex hantering av topologiändringar är möjlig genom en anpassad implementering MyApp.Listener, kan det alltid finnas edge-fall där dessa biblioteksbegränsningar och konfigurationsfördomar visar sig vara hörnstenarna i implementeringen. Det är okej, ta bara ovanstående libcluster, som är mer allmänt ändamålsenligt, eller till och med hantera lågnivåklustret själv. Målet med detta kodbibliotek är inte att täcka alla möjliga scenarier, utan att använda det vanligaste scenariot utan onödig smärta och besvärlig copy-paste.

Notera: vid denna tidpunkt i originalet fanns frasen "Happy clustering!", och Yandex, som jag översätter med (jag behöver inte gå igenom ordböcker själv), erbjöd mig alternativet "Happy clustering!" Det är kanske omöjligt att tänka sig en bättre översättning, särskilt i ljuset av den nuvarande geopolitiska situationen.

Källa: will.com

Lägg en kommentar