
A Mail.ru Group-nál van a Tarantool – ez egy Lua alapú alkalmazásszerver, amely egyben adatbázisként is funkcionál (vagy fordítva?). Gyors és remek, de egy szerver lehetőségei mégsem végtelenek. A vertikális skálázás sem csodaszer, ezért a Tarantoolban vannak eszközök a horizontális skálázásra – vshard modul. . Ez lehetővé teszi az adatok shardelését több szerver között, de meg kell dolgozni a beállításával és az üzleti logika integrálásával.
Jó hírek: már összeszedtük a tapasztalatokat (például , ) és elkészítettük a következő keretrendszert, amely jelentősen megkönnyíti ennek a problémának a megoldását.
egy új keretrendszer bonyolult elosztott rendszerek fejlesztéséhez. Lehetővé teszi, hogy a üzleti logikára összpontosítsunk az infrastruktúrával kapcsolatos problémák helyett. A további részletekben elmondom, hogyan működik ez a keretrendszer, és hogyan lehet vele elosztott szolgáltatásokat írni.
De mi is a probléma valójában?
Van egy Tarantoolunk, van vshard – mit kívánhatnánk még?
Először is, a kényelemről van szó. A vshard konfigurációja Lua-táblákon keresztül állítható be. Ahhoz, hogy az elosztott Tarantool rendszerek helyesen működjenek, a konfigurációnak mindenütt azonosnak kell lennie. Senki sem akar ezzel manuálisan foglalkozni. Ezért különféle szkriptek, Ansible és telepítési rendszerek jönnek segítségül.
A Cartridge automatikusan kezeli a vshard konfigurációját, ez saját elosztott konfigurációján alapul. Gyakorlatilag ez egy egyszerű YAML-fájl, amelynek egy példánya minden Tarantool példányban tárolva van. Az egyszerűsítés abban rejlik, hogy a keretrendszer saját konfigurációit figyeli, és biztosítja, hogy azok mindenütt azonosak legyenek.
Másodszor, újra a kényelem. A vshard konfigurációjának semmi köze az üzleti logika fejlesztéséhez, csak elvonja a programozó figyelmét a munkáról. Amikor egy projekt architektúráját vitatjuk meg, gyakran a különféle összetevőkről és azok kölcsönhatásáról van szó. Korai még a három adatközpontban történő klaszterkiadásra gondolni.
Ezeket a problémákat újra és újra megoldottuk, és egy idő után sikerült kidolgoznunk egy megközelítést, amely megkönnyíti az alkalmazással való munkát annak teljes életciklusa során: létrehozás, fejlesztés, tesztelés, CI/CD, karbantartás.
A Cartridge bevezeti a szerepek fogalmát a Tarantool minden egyes folyamatához. A szerepek azok a koncepciók, amelyek lehetővé teszik a fejlesztők számára, hogy a kód írására összpontosítsanak. Minden projektben létező szerep egy Tarantool példányon futtatható, és a teszteléshez ez elegendő lesz.
A Tarantool Cartridge főbb funkciói:
- automatikus klaszter-orchestration;
- az alkalmazás funkcionalitásának bővítése új szerepekkel;
- alkalmazási sablon fejlesztéshez és telepítéshez;
- beépített automatikus shardolás;
- integráció a Luatest teszt keretrendszerrel;
- a klaszter kezelése WebUI és API segítségével;
- csomagolási és telepítési eszközök.
Hello, World!
Alig várom, hogy bemutassam magát a keretrendszert, ezért az architektúráról szóló mesét későbbre hagyjuk, és kezdjük az egyszerűbbel. Tegyük fel, hogy a Tarantool már telepítve van, így csak annyit kell tennünk, hogy
$ tarantoolctl rocks install cartridge-cli
$ export PATH=$PWD/.rocks/bin/:$PATHEzek a két parancs telepíti a parancssori eszközöket, és lehetővé teszik, hogy a sablonból létrehozzuk első alkalmazásunkat:
$ cartridge create --name myappÉs ezt kapjuk:
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/
Ez egy git-repozitórium, kész "Hello, World!" alkalmazással. Próbáljuk meg azonnal futtatni, miután telepítettük a függőségeket (többek között magát a keretrendszert):
$ tarantoolctl rocks make
$ ./init.lua --http-port 8080Így egy node-ot futtatunk a jövőbeli shardolt alkalmazásból. A kíváncsi felhasználó azonnal megnyithatja a webes felületet, az egérrel konfigurálhatja az egycsomópontos klasztert, és élvezheti az eredményt, de még korai az öröm. Jelenleg az alkalmazás nem képes semmilyen hasznos dolgot tenni, ezért a telepítésről később beszélek, most viszont itt az idő a kód írására.
Alkalmazásfejlesztés
Képzeljük el, hogy egy projektet tervezünk, amely adatokat fogad, tárol, és naponta egyszer jelentéseket készít.

Elkezdjük megrajzolni a sémát, és három komponenst helyezünk el rajta: gateway, storage és scheduler. Tovább dolgozunk az architektúrán. Mivel a vshardot használjuk tárolásra, hozzáadjuk a sémához a vshard-routert és a vshard-storage-t. Sem a gateway, sem a scheduler nem fog közvetlenül a tárolóra hivatkozni, erre van a router, amely ezért készült.

Ez a séma még nem tükrözi pontosan azt, amit a projekt során létre fogunk hozni, mivel az elemek még elvontak. Meg kell néznünk, hogy ez hogyan jelenik meg a valós Tarantool-ban — csoportosítjuk az elemeinket a folyamatok szerint.

Nincs sok értelme a vshard-router-t és a gateway-t külön példányokban tartani. Miért tennénk meg az extra hálózati lépéseket, ha ez a router feladata? Ezeknek egy folyamaton belül kellene futniuk. Tehát egy folyamaton belül inicializálódik a gateway és a vshard.router.cfg, és hagyjuk, hogy helyben lépjenek interakcióba.
A tervezési szakaszban kényelmes volt három elemmel dolgozni, de én, mint fejlesztő, nem szeretnék három Tarantool példány indításának mibenlétével foglalkozni, miközben a kódot írom. Teszteket kell futtatnom és ellenőriznem kell, hogy jól írtam-e meg a gateway-t. Vagy talán szeretném megmutatni a kollégáimnak egy funkciót. Miért szenvednék három példány telepítésével? Ekkor született meg a szerepkörök koncepciója. A szerepkör egy egyszerű lua modul, amelynek életciklusát a Cartridge menedzseli. Ebben a példában négy van — gateway, router, storage, scheduler. Egy másik projektben lehet, hogy több lesz. Az összes szerepkört egyetlen folyamatban lehet futtatni, és ez elegendő lesz.

Amikor pedig a staging vagy a mûködési környezet telepítéséről van szó, akkor minden Tarantool folyamatnak megfelelő szerepköröket osztunk ki a hardveres lehetőségek függvényében:

Topológia kezelése
Az információt arról, hogy hol milyen szerepkörök futnak, valahol tárolni kell. És ez a „valahol” a megosztott konfiguráció, amelyről már korábban említést tettem. A legfontosabb benne a klaszter topológiája. Itt három replikációs csoportot látunk öt Tarantool folyamattal:

Nem akarjuk elveszíteni az adatokat, ezért gondosan bánunk az indított folyamatok információjával. A Cartridge figyeli a konfigurációt a kétszakaszos elkötelezés segítségével. Amint frissíteni akarjuk a konfigurációt, először ellenőrzi az összes példány elérhetőségét és készségét az új konfiguráció fogadására. Ezt követően a második szakaszban alkalmazza a konfigurációt. Így ha egy példány átmenetileg nem elérhető, akkor semmi baj nem fog történni. A konfiguráció egyszerűen nem lesz alkalmazva, és előre látni fogja a hibaüzenetet.
A topológiai szekcióban szerepel egy fontos paraméter is, amely a replikációs csoport szereplője. Általában ez az az példány, amelyre az írás történik. A többi jellemzően csak olvasható, bár itt lehetnek kivételek. Néha merész fejlesztők nem félnek a konfliktusoktól, és párhuzamosan több replikára is írhatnak adatokat, de vannak bizonyos műveletek, amelyeket semmilyen körülmények között sem szabad kétszer végrehajtani. Erre szolgál a vezető jelző.

Szerepek élete
Ahhoz, hogy egy absztrakt szerep létezzen egy ilyen architektúrában, a keretrendszernek valamilyen módon kezelnie kell őket. Természetesen a kezelés a Tarantool folyamat újraindítása nélkül történik. A szerepek kezelésére négy callback létezik. A Cartridge automatikusan meghívja őket attól függően, hogy mi szerepel a megosztott konfigurációban, így alkalmazva a konfigurációt a konkrét szerepekre.
function init()
function validate_config()
function apply_config()
function stop()
Minden szerepnek van egy funkciója init, amelyet egyszer hívnak meg, amikor a szerep aktiválódik, vagy amikor a Tarantool újraindul. Ott kényelmes például inicializálni a box.space.create-t, vagy a scheduler elindíthat egy háttérbeli fibert, amely bizonyos időközönként végez munkát.
Egy funkció init nem biztos, hogy elegendő. A Cartridge lehetővé teszi a szerepek számára, hogy a megosztott konfigurációt használják, amelyet a topológia tárolására használnak. Ugyanebben a konfigurációban új szekciót deklarálhatunk, és abban tárolhatunk egy üzleti konfigurációs részt. Az én példámban ez lehet adatmodell vagy a scheduler szerepének ütemezési beállításai.
A klaszter meghívja validate_config és apply_config a megosztott konfiguráció minden módosításakor. Amikor a konfigurációt kettős fázisú biztonsági mentéssel alkalmazzák, a klaszter ellenőrzi, hogy minden szerep kész-e elfogadni az új konfigurációt, és szükség esetén értesíti a felhasználót a hibáról. Amikor mindenki egyetért abban, hogy a konfiguráció rendben van, akkor végrehajtódik a apply_config.
A szerepeknek van egy stop, amely a szerep életciklusának eredményeinek tisztítására szükséges. Ha azt mondjuk, hogy a scheduler már nem szükséges ezen a szerveren, leállíthatja azokat a fibereket, amelyeket elindított. init.
A szerepek kölcsönhatásba léphetnek egymással. Megszoktuk, hogy Lua nyelven írunk függvényhívásokat, de előfordulhat, hogy a folyamatban nincs megfelelő szerepünk. A hálózati hívások megkönnyítése érdekében egy segédmodult, az rpc-t (távoli eljáráshívás) használjuk, amely a Tarantoolba integrált standard netbox alapjaira épül. Hasznos lehet például, ha a gateway közvetlenül azt kéri a schedulertől, hogy azonnal végezze el a munkát, ne várjon egy napot.
Egy másik fontos szempont az üzemelési megbízhatóság biztosítása. A Cartridge egészségi állapotának ellenőrzésére a SWIM protokollt használják. Röviden szólva, a folyamatok ‘pletykákat’ cserélnek egymással UDP-n keresztül – minden folyamat elmondja a szomszédainak a legfrissebb híreket, és azok válaszolnak. Ha váratlanul nem érkezik válasz, a Tarantool gyanakodni kezd, és egy idő után bejelenti a halált, majd elmondja minden körülötte lévőnek ezt a hírt.

Ezen protokoll alapján a Cartridge automatikus hibakezelést szervez. Minden folyamat figyeli a környezetét, és ha a vezető hirtelen nem válaszol, a replikát átveheti az ő szerepét, a Cartridge pedig ennek megfelelően konfigurálja a futó szerepeket.

Itt óvatosnak kell lenni, mert a gyakori kapcsolgatás oda-vissza adatkonfliktusokat okozhat a replikálás során. Az automatikus hibákat nem ártalanul aktiválni, mindenképpen meg kell érteni, hogy mi történik, és biztosnak kell lenni abban, hogy a replikáció nem hibásodik meg, miután a vezető helyreállt és visszakapta a koronáját.
A fentiek alapján az lehet a benyomásunk, hogy a szerepek hasonlítanak a mikroszolgáltatásokhoz. Valamilyen értelemben valóban azok, csak a Tarantool folyamatain belüli modulokként. Ugyanakkor vannak alapvető eltérések is. Először is, a projekt összes szerepének egy kódbázison belül kell élnie. Minden Tarantool folyamatnak ugyanabból a kódbázisból kell indulnia, hogy elkerülhessük a meglepetéseket, mint például amikor megpróbáljuk inicializálni a schedulert, de az egyszerűen nem létezik. Emellett nem szabad megengedni a kódverziók eltéréseit, mert az ilyen helyzetben a rendszer viselkedése nagyon nehezen előrejelezhető és hibakereshető.
A Dockerral ellentétben nem tudjuk egyszerűen csak elvinni egy „képet” a szerepből, áthozni egy másik gépre és ott elindítani. A szerepeink nem annyira izoláltak, mint a Docker-konténerek. Ezenkívül nem tudunk két azonos szerepet futtatni egy példányon. A szerep vagy jelen van, vagy nincs, bizonyos értelemben ez egy singleton. Továbbá a replikációs csoporton belül a szerepeknek azonosnak kell lenniük, mert különben értelmetlen lenne – az adatok azonosak, de a konfiguráció eltérő.
Telepítési eszközök
Megígértem, hogy megmutatom, hogyan segít a Cartridge az alkalmazások telepítésében. Az élet megkönnyítése érdekében a keretrendszer RPM-csomagokat csomagol:
$ cartridge pack rpm myapp -- csomagolja nekünk a .\/myapp-0.1.0-1.rpm-t
$ sudo yum install .\/myapp-0.1.0-1.rpmA telepített csomag szinte mindent magában foglal, amire szükség van: az alkalmazást és a telepített Lua-függőségeket. A Tarantool is a szerverre fog érkezni, mint az RPM-csomag függősége, és a szolgáltatásunk készen áll a futtatásra. Ezt a systemd-n keresztül végezzük, de először egy kis konfigurációt kell írni. Legalább az egyes folyamatok URI-ját meg kell adni. Három például elegendő.
$ 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}
CONFIGItt van egy érdekes szempont. Ahelyett, hogy csak a bináris protokoll portját adnánk meg, az egész folyamat nyilvános címét megadjuk, beleértve a hostname-t is. Erre azért van szükség, hogy a klaszter csomópontjai tudják, hogyan csatlakozzanak egymáshoz. Rossz ötlet 0.0.0.0 címet használni advertise_uri-ként, ennek egy külső IP-címnek kell lennie, nem pedig egy bind socket-nek. Enélkül semmi sem fog működni, ezért a Cartridge egyszerűen nem engedi, hogy helytelen advertise_uri-val indítsunk csomópontot.
Most, hogy a konfiguráció készen áll, indíthatjuk a folyamatokat. Mivel a szokásos systemd egység nem engedi több folyamat indítását, a Cartridge alkalmazás olyan úgynevezett instantiated egységeket telepít, amelyek így működnek:
$ sudo systemctl start myapp@router
$ sudo systemctl start myapp@storage_A
$ sudo systemctl start myapp@storage_BA konfigurációban megadtuk az HTTP-portot, amelyen a Cartridge a webes felületet szolgálja ki – 8080. Nézzük meg, és ellenőrizzük:

Azt látjuk, hogy a folyamatok elindultak, de még nincsenek konfigurálva. A patron jelenleg nem tudja, hogy kivel kell replikálnia, és nem tud önálló döntést hozni, ezért várja a mi lépéseinket. A választékunk nem nagy: az új klaszter élete az első node konfigurálásával kezdődik. Utána hozzáadjuk a többit a klaszterhez, beállítjuk a szerepeiket, és így a telepítést sikeresen befejezettnek lehet tekinteni.
Öntsünk egy pohárral a kedvenc italunkból, és lazítsunk a hosszú munkahét után. Az alkalmazás használatra kész.

Összegzés
Mik a következtetések? Próbáljátok ki, használjátok, hagyjátok el a visszajelzéseiteket, nyissatok tikeket a GitHub-on.
Hivatkozások
[1]
[2]
[3]
[4]
[5]
[6]
Forrás: habr.com
