Tarantool Cartridge: skära en Lua-backend i tre rader

Tarantool Cartridge: skära en Lua-backend i tre rader

På Mail.ru Group har vi Tarantool - detta är en applikationsserver i Lua, som också fungerar som en databas (eller vice versa?). Det är snabbt och coolt, men kapaciteten hos en server är fortfarande inte obegränsad. Vertikal skalning är inte heller ett universalmedel, så Tarantool har verktyg för horisontell skalning - vshard-modulen [1]. Det låter dig dela data över flera servrar, men du måste mixtra med det för att ställa in det och bifoga affärslogiken.

Goda nyheter: vi har samlat några stora skott (t.ex [2], [3]) och skapade ett annat ramverk som avsevärt kommer att förenkla lösningen på detta problem.

Tarantool patron är ett nytt ramverk för att utveckla komplexa distribuerade system. Det låter dig fokusera på att skriva affärslogik istället för att lösa infrastrukturproblem. Nedanför snittet kommer jag att berätta hur detta ramverk fungerar och hur man skriver distribuerade tjänster med det.

Och exakt vad är problemet?

Vi har en tarantula, vi har vshard - vad mer kan man önska sig?

För det första är det en fråga om bekvämlighet. Vshard-konfigurationen konfigureras genom Lua-tabeller. För att ett distribuerat system med flera Tarantool-processer ska fungera korrekt måste konfigurationen vara densamma överallt. Ingen vill göra detta manuellt. Därför används alla typer av skript, Ansible och distributionssystem.

Cartridge själv hanterar vshard-konfigurationen, den gör detta baserat på dess egen distribuerad konfiguration. Det är i huvudsak en enkel YAML-fil, vars kopia lagras i varje Tarantool-instans. Förenklingen är att själva ramverket övervakar sin konfiguration och ser till att det är likadant överallt.

För det andra är det återigen en fråga om bekvämlighet. Vshard-konfigurationen har ingenting att göra med utvecklingen av affärslogik och distraherar bara programmeraren från hans arbete. När vi diskuterar arkitekturen i ett projekt talar vi oftast om enskilda komponenter och deras interaktion. Det är för tidigt att tänka på att rulla ut ett kluster till tre datacenter.

Vi löste dessa problem om och om igen, och vid något tillfälle lyckades vi utveckla ett tillvägagångssätt som förenklade arbetet med applikationen under hela dess livscykel: skapande, utveckling, testning, CI/CD, underhåll.

Cartridge introducerar konceptet med en roll för varje Tarantool-process. Roller är ett koncept som gör att en utvecklare kan fokusera på att skriva kod. Alla roller som är tillgängliga i projektet kan köras på en Tarantool-instans, och detta kommer att räcka för tester.

Nyckelfunktioner hos Tarantool Cartridge:

  • automatiserad klusterorkestrering;
  • utöka applikationens funktionalitet med hjälp av nya roller;
  • applikationsmall för utveckling och driftsättning;
  • inbyggd automatisk skärning;
  • integration med Luatest testramverk;
  • klusterhantering med WebUI och API;
  • paketerings- och distributionsverktyg.

Hej världen!

Jag kan inte vänta med att visa själva ramverket, så vi lämnar historien om arkitekturen till senare och börjar med något enkelt. Om vi ​​antar att själva Tarantool redan är installerat, återstår bara att göra

$ tarantoolctl rocks install cartridge-cli
$ export PATH=$PWD/.rocks/bin/:$PATH

Dessa två kommandon installerar kommandoradsverktygen och låter dig skapa din första applikation från mallen:

$ cartridge create --name myapp

Och det här är vad vi får:

myapp/
├── .git/
├── .gitignore
├── app/roles/custom.lua
├── deps.sh
├── init.lua
├── myapp-scm-1.rockspec
├── test
│   ├── helper
│   │   ├── integration.lua
│   │   └── unit.lua
│   ├── helper.lua
│   ├── integration/api_test.lua
│   └── unit/sample_test.lua
└── tmp/

Detta är ett git-förråd med ett färdigt "Hello, World!" Ansökan. Låt oss försöka köra det direkt efter att ha installerat beroenden (inklusive själva ramverket):

$ tarantoolctl rocks make
$ ./init.lua --http-port 8080

Så vi har en nod som körs för den framtida shardade applikationen. En nyfiken lekman kan omedelbart öppna webbgränssnittet, konfigurera ett kluster av en nod med musen och njuta av resultatet, men det är för tidigt att glädjas. Hittills kan applikationen inte göra något användbart, så jag kommer att berätta om distributionen senare, men nu är det dags att skriva kod.

Applikationsutveckling

Föreställ dig, vi designar ett projekt som måste ta emot data, spara det och bygga en rapport en gång om dagen.

Tarantool Cartridge: skära en Lua-backend i tre rader

Vi börjar rita ett diagram och placerar tre komponenter på det: gateway, lagring och schemaläggare. Vi jobbar vidare med arkitekturen. Eftersom vi använder vshard som lagring lägger vi till vshard-router och vshard-storage till schemat. Varken gatewayen eller schemaläggaren kommer direkt åt lagringen; det är vad routern är till för, det är vad den skapades för.

Tarantool Cartridge: skära en Lua-backend i tre rader

Det här diagrammet representerar fortfarande inte exakt vad vi kommer att bygga i projektet eftersom komponenterna ser abstrakta ut. Vi behöver fortfarande se hur detta kommer att projiceras på det riktiga Tarantool - låt oss gruppera våra komponenter efter process.

Tarantool Cartridge: skära en Lua-backend i tre rader

Det finns ingen mening med att behålla vshard-router och gateway på separata instanser. Varför behöver vi surfa på nätverket igen om detta redan är routerns ansvar? De måste köras inom samma process. Det vill säga, både gateway och vshard.router.cfg initieras i en process och låter dem interagera lokalt.

På designstadiet var det bekvämt att arbeta med tre komponenter, men jag, som utvecklare, när jag skriver koden, vill inte tänka på att lansera tre instanser av Tarnatool. Jag måste köra tester och kontrollera att jag skrev gateway korrekt. Eller så kanske jag vill visa en funktion för mina kollegor. Varför ska jag gå igenom besväret med att distribuera tre kopior? Så föddes begreppet roller. En roll är en vanlig luash-modul vars livscykel hanteras av Cartridge. I det här exemplet finns det fyra av dem - gateway, router, lagring, schemaläggare. Det kan finnas mer i ett annat projekt. Alla roller kan köras i en process, och det räcker.

Tarantool Cartridge: skära en Lua-backend i tre rader

Och när det gäller distribution till iscensättning eller produktion, kommer vi att tilldela varje Tarantool-process sin egen uppsättning roller beroende på hårdvarukapaciteten:

Tarantool Cartridge: skära en Lua-backend i tre rader

Topologihantering

Information om var vilka roller körs måste lagras någonstans. Och det här "någonstans" är den distribuerade konfigurationen, som jag redan nämnde ovan. Det viktigaste med det är klustertopologin. Här är 3 replikeringsgrupper med 5 Tarantool-processer:

Tarantool Cartridge: skära en Lua-backend i tre rader

Vi vill inte förlora data, så vi behandlar information om att köra processer med omsorg. Cartridge håller reda på konfigurationen med hjälp av en tvåfasig commit. När vi väl vill uppdatera konfigurationen kontrollerar den först att alla instanser är tillgängliga och redo att acceptera den nya konfigurationen. Efter detta tillämpar den andra fasen konfigurationen. Således, även om en kopia visar sig vara tillfälligt otillgänglig, kommer inget dåligt att hända. Konfigurationen kommer helt enkelt inte att tillämpas och du kommer att se ett felmeddelande i förväg.

Även i topologisektionen anges en så viktig parameter som ledaren för varje replikeringsgrupp. Vanligtvis är detta kopian som spelas in. Resten är oftast skrivskyddad, även om det kan finnas undantag. Ibland är modiga utvecklare inte rädda för konflikter och kan skriva data till flera repliker parallellt, men det finns vissa operationer som, oavsett vad, inte bör utföras två gånger. För detta finns ett tecken på en ledare.

Tarantool Cartridge: skära en Lua-backend i tre rader

Rollernas liv

För att en abstrakt roll ska existera i en sådan arkitektur måste ramverket hantera dem på något sätt. Naturligtvis sker kontroll utan att starta om Tarantool-processen. Det finns 4 callbacks för att hantera roller. Cartridge själv kommer att anropa dem beroende på vad som är skrivet i dess distribuerade konfiguration, och applicerar därmed konfigurationen på specifika roller.

function init()
function validate_config()
function apply_config()
function stop()

Varje roll har en funktion init. Den anropas en gång antingen när rollen är aktiverad eller när Tarantool startas om. Det är bekvämt där, till exempel, att initiera box.space.create, eller så kan schemaläggaren starta någon bakgrundsfiber som kommer att utföra arbete med vissa tidsintervall.

En funktion init kanske inte räcker. Cartridge tillåter roller att dra fördel av den distribuerade konfiguration som den använder för att lagra topologin. Vi kan deklarera en ny sektion i samma konfiguration och lagra ett fragment av affärskonfigurationen i den. I mitt exempel kan detta vara ett dataschema eller schemainställningar för schemaläggarrollen.

Klustersamtal validate_config и apply_config varje gång den distribuerade konfigurationen ändras. När en konfiguration tillämpas av en två-fas commit, kontrollerar klustret att varje roll är redo att acceptera denna nya konfiguration och, om nödvändigt, rapporterar ett fel till användaren. När alla är överens om att konfigurationen är normal, då apply_config.

Även roller har en metod stop, som behövs för att rensa upp rollens output. Om vi ​​säger att schemaläggaren inte längre behövs på den här servern, kan den stoppa de fibrerna som den började använda init.

Roller kan interagera med varandra. Vi är vana vid att skriva funktionsanrop i Lua, men det kan hända att en given process inte har den roll vi behöver. För att underlätta samtal över nätverket använder vi hjälpmodulen rpc (remote procedure call), som är byggd på basen av standardnätboxen inbyggd i Tarantool. Detta kan vara användbart om till exempel din gateway direkt vill be schemaläggaren att göra jobbet just nu, istället för att vänta en dag.

En annan viktig punkt är att säkerställa feltolerans. Cartridge använder SWIM-protokollet för att övervaka hälsan [4]. Kort sagt, processer utbyter "rykten" med varandra över UDP - varje process berättar för sina grannar de senaste nyheterna och de svarar. Om svaret plötsligt inte kommer, börjar Tarantool misstänka att något är fel, och efter ett tag reciterar den döden och börjar berätta för alla runt den här nyheten.

Tarantool Cartridge: skära en Lua-backend i tre rader

Baserat på detta protokoll organiserar Cartridge automatisk felbehandling. Varje process övervakar sin miljö, och om ledaren plötsligt slutar svara kan repliken ta över dess roll, och Cartridge konfigurerar de löpande rollerna därefter.

Tarantool Cartridge: skära en Lua-backend i tre rader

Du måste vara försiktig här, eftersom frekvent växling fram och tillbaka kan leda till datakonflikter under replikering. Naturligtvis bör du inte aktivera automatisk failover slumpmässigt. Vi måste tydligt förstå vad som händer och vara säkra på att replikeringen inte kommer att gå sönder efter att ledaren har återställts och kronan har återlämnats till honom.

Av allt detta kan du få en känsla av att roller liknar mikrotjänster. På sätt och vis är de just det, bara som moduler i Tarantool-processer. Men det finns också ett antal grundläggande skillnader. För det första måste alla projektroller leva i samma kodbas. Och alla Tarantool-processer bör startas från samma kodbas, så att det inte finns några överraskningar som de när vi försöker initiera schemaläggaren, men den existerar helt enkelt inte. Dessutom bör du inte tillåta skillnader i kodversioner, eftersom systemets beteende i en sådan situation är mycket svårt att förutsäga och felsöka.

Till skillnad från Docker kan vi inte bara ta en roll "image", ta den till en annan maskin och köra den där. Våra roller är inte lika isolerade som Docker-containrar. Dessutom kan vi inte köra två identiska roller på en instans. En roll finns antingen eller inte; på sätt och vis är det en singel. Och för det tredje måste rollerna vara desamma inom hela replikeringsgruppen, för annars vore det absurt - data är desamma, men konfigurationen är annorlunda.

Implementeringsverktyg

Jag lovade att visa hur Cartridge hjälper till att distribuera applikationer. För att göra livet lättare för andra, paketerar rampaketet RPM-paket:

$ cartridge pack rpm myapp -- упакует для нас ./myapp-0.1.0-1.rpm
$ sudo yum install ./myapp-0.1.0-1.rpm

Det installerade paketet innehåller nästan allt du behöver: både applikationen och de installerade beroenden. Tarantool kommer också att anlända till servern som ett beroende av RPM-paketet, och vår tjänst är redo att lanseras. Detta görs genom systemd, men först måste du skriva lite konfiguration. Ange åtminstone URI för varje process. Tre räcker till exempel.

$ sudo tee /etc/tarantool/conf.d/demo.yml <<CONFIG
myapp.router: {"advertise_uri": "localhost:3301", "http_port": 8080}
myapp.storage_A: {"advertise_uri": "localhost:3302", "http_enabled": False}
myapp.storage_B: {"advertise_uri": "localhost:3303", "http_enabled": False}
CONFIG

Det finns en intressant nyans här. Istället för att bara ange den binära protokollporten anger vi hela den offentliga adressen för processen inklusive värdnamnet. Detta är nödvändigt så att klusternoderna vet hur de ska ansluta till varandra. Det är en dålig idé att använda 0.0.0.0 som advertis_uri-adress, det bör vara en extern IP-adress, inte en socket-bindning. Utan det kommer ingenting att fungera, så Cartridge låter dig helt enkelt inte starta en nod med fel advertise_uri.

Nu när konfigurationen är klar kan du starta processerna. Eftersom en vanlig systemd enhet inte tillåter mer än en process att starta, installeras applikationer på Cartridge av den sk. instansierade enheter som fungerar så här:

$ sudo systemctl start myapp@router
$ sudo systemctl start myapp@storage_A
$ sudo systemctl start myapp@storage_B

I konfigurationen angav vi HTTP-porten som Cartridge tjänar webbgränssnittet på - 8080. Låt oss gå till det och ta en titt:

Tarantool Cartridge: skära en Lua-backend i tre rader

Vi ser att även om processerna körs är de inte konfigurerade ännu. Patronen vet ännu inte vem som ska replikera med vem och kan inte fatta ett beslut på egen hand, så den väntar på våra handlingar. Men vi har inte mycket val: livet för ett nytt kluster börjar med konfigurationen av den första noden. Sedan lägger vi till de andra i klustret, tilldelar dem roller, och vid det här laget kan distributionen anses vara framgångsrik.

Låt oss hälla upp ett glas av din favoritdrink och koppla av efter en lång arbetsvecka. Applikationen kan användas.

Tarantool Cartridge: skära en Lua-backend i tre rader

Resultat av

Vilka är resultaten? Prova det, använd det, lämna feedback, skapa biljetter på Github.

referenser

[1] Tarantool » 2.2 » Referens » Rocks-referens » Modul vshard

[2] Hur vi implementerade kärnan i Alfa-Banks investeringsverksamhet baserad på Tarantool

[3] Ny generations faktureringsarkitektur: transformation med övergången till Tarantool

[4] SWIM - klusterkonstruktionsprotokoll

[5] GitHub - tarantool/cartridge-cli

[6] GitHub - tarantverktyg/patron

Källa: will.com

Lägg en kommentar