
La Mail.ru Group avem Tarantool — un server de aplicații pe Lua, care este de asemenea și o bază de date (sau invers?). Este rapid și excelent, dar posibilitățile unui singur server nu sunt nelimitate. Scalarea verticală nu este o soluție universală, de aceea Tarantool oferă instrumente pentru scalarea orizontală — modul vshard. . Acesta permite shardarea datelor pe mai multe servere, dar va fi nevoie de eforturi pentru a-l configura și integra logica de afaceri.
Veste bună: am adunat probleme (de exemplu , ) și am creat un nou framework care simplifică considerabil rezolvarea acestei probleme.
— este un nou framework pentru dezvoltarea sistemelor distribuite complexe. Acesta permite concentrare pe scrierea logicii de afaceri în loc de a rezolva probleme infrastructurale. În continuare, voi povesti cum este organizat acest framework și cum se pot crea servicii distribuite cu ajutorul său.
Și care este, de fapt, problema?
Avem Tarantool, avem vshard — ce altceva ne-am putea dori?
În primul rând, este vorba despre confort. Configurarea vshard se realizează prin tabele Lua. Pentru ca un sistem distribuit din mai multe procese Tarantool să funcționeze corect, configurarea trebuie să fie identică în toate locurile. Nimeni nu vrea să se ocupe de asta manual. De aceea, se folosesc tot felul de scripturi, Ansible, sisteme de desfășurare.
Cartridge gestionează singur configurarea vshard, făcând acest lucru pe baza propriei configurații distribuite. Practic, este un simplu fișier YAML, o copie a căruia este stocată în fiecare instanță Tarantool. Simplificarea constă în faptul că framework-ul se ocupă de configurația sa și se asigură că aceasta este identică în toate locurile.
În al doilea rând, este din nou vorba despre confort. Configurația vshard nu are legătură cu dezvoltarea logicii de afaceri și doar distrage atenția programatorului de la muncă. Atunci când discutăm arhitectura unui anumit proiect, de cele mai multe ori ne referim la componente separate și la interacțiunea dintre ele. Gândirea la desfășurarea unui cluster pe 3 centre de date este prematură.
Am abordat aceste probleme repetat, iar la un moment dat am reușit să dezvoltăm o abordare care facilitează lucrul cu aplicația pe întreaga sa durată de viață: crearea, dezvoltarea, testarea, CI/CD, întreținerea.
Cartridge introduce conceptul de rol pentru fiecare proces Tarantool. Rolurile sunt acea idee care permite dezvoltatorului să se concentreze pe scrierea codului. Toate rolurile disponibile în proiect pot fi rulate pe o singură instanță de Tarantool, iar pentru teste, acest lucru va fi suficient.
Funcționalitățile principale ale Tarantool Cartridge:
- orchestrare automată a cluster-ului;
- extinderea funcționalității aplicației prin noi roluri;
- șablon de aplicație pentru dezvoltare și desfășurare;
- shard-ing automat integrat;
- integrare cu cadrul de testare Luatest;
- gestionarea cluster-ului prin WebUI și API;
- unelte de pachetare și desfășurare.
Hello, World!
Abia aștept să vă arăt cadrul, așa că voi lăsa povestea despre arhitectură pe mai târziu și voi începe cu ceva simplu. Dacă presupunem că Tarantool este deja instalat, rămâne doar să facem
$ tarantoolctl rocks install cartridge-cli
$ export PATH=$PWD/.rocks/bin/:$PATHAceste două comenzi vor instala utilitarele din linia de comandă și ne vor permite să creăm prima noastră aplicație din șablon:
$ cartridge create --name myappIată ce vom obține:
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/
Acesta este un repo git cu aplicația „Hello, World!” gata. Să încercăm imediat să o lansăm, instalând mai întâi dependențele (inclusiv cadrul în sine):
$ tarantoolctl rocks make
$ ./init.lua --http-port 8080Deci, avem o nodă a viitoarei aplicații sharded. Un observator curios poate deschide imediat interfața web, să configureze clusterul dintr-un singur nod cu mouse-ul și să se bucure de rezultat, dar bucuria este prematură. Deocamdată, aplicația nu poate face nimic util, așa că voi vorbi despre desfășurare mai târziu, iar acum este timpul să scriem cod.
Dezvoltarea aplicațiilor
Imaginați-vă că proiectăm un proiect care trebuie să primească date, să le salveze și să genereze un raport o dată pe zi.

Începem să desenăm schema și plasăm pe ea trei componente: gateway, storage și scheduler. Continuăm să dezvoltăm arhitectura. Deoarece folosim vshard ca stocare, adăugăm în schemă vshard-router și vshard-storage. Nici gateway-ul, nici scheduler-ul nu vor accesa stocarea direct, pentru asta există routerul, care a fost creat tocmai pentru a îndeplini această funcție.

Această schemă încă nu reflectă complet ceea ce vom crea în proiect, deoarece componentele arată abstract. Trebuie să vedem cum se va proiecta aceasta pe Tarantool-ul real — să grupăm componentele noastre în funcție de procese.

Este puțin probabil să aibă sens să menținem vshard-router și gateway pe instanțe separate. De ce să facem un tur inutil prin rețea, dacă acesta se încadrează deja în responsabilitățile routerului? Ele ar trebui să fie lansate într-un singur proces. Adică, în același proces se inițializează și gateway, și vshard.router.cfg, și să permită interacțiunea lor local.
În etapa de proiectare, a fost convenabil să lucrăm cu cele trei componente, dar eu, ca dezvoltator, în timp ce scriu cod, nu vreau să mă gândesc la lansarea a trei instanțe Tarnatool. Trebuie să lansez teste și să verific că am scris corect gateway-ul. Sau, poate, vreau să demonstrez o caracteristică colegilor. De ce să mă complic cu desfășurarea a trei instanțe? Așa a apărut conceptul de roluri. O rol este un modul obișnuit Lua, al cărui ciclu de viață este gestionat de Cartridge. În acest exemplu, există patru — gateway, router, storage, scheduler. În alt proiect, numărul acestora poate fi mai mare. Toate rolurile pot fi lansate într-un singur proces, iar aceasta va fi suficient.

Și când va veni vorba despre desfășurarea în staging sau în producție, atunci vom aloca fiecărui proces Tarantool propriul set de roluri în funcție de capacitățile hardware:

Gestionarea topologiei
Informațiile despre unde sunt lansate diverse roluri trebuie să fie stocate undeva. Și acest „undeva” este configurația distribuită, despre care am menționat mai sus. Cel mai important în aceasta este topologia clusterei. Aici sunt ilustrate 3 grupuri de replicare din 5 procese Tarantool:

Nu vrem să pierdem date, așa că avem grijă de informațiile despre procesele lansate. Cartridge monitorizează configurația printr-un commit în două faze. Atunci când dorim să actualizăm configurația, acesta verifică întâi disponibilitatea tuturor instanțelor și pregătirea lor de a accepta noua configurație. Apoi, în a doua fază, este aplicată configurația. Astfel, chiar dacă o instanță devine temporar inaccesibilă, nu se va întâmpla nimic grav. Configurația pur și simplu nu va fi aplicată și veți vedea eroarea din timp.
În secțiunea topologie este specificat un parametru important, și anume liderul fiecărei grupuri de replicare. De obicei, acesta este exemplarul pe care se fac înregistrările. Celelalte sunt, cel mai adesea, read-only, deși pot exista excepții. Uneori, dezvoltatorii curajoși nu se tem de conflicte și pot scrie date pe mai multe replici simultan, dar există anumite operațiuni care, indiferent de circumstanțe, nu ar trebui să fie executate de două ori. Pentru aceasta, există un semn al liderului.

Viața rolurilor
Pentru ca un rol abstract să poată exista într-o astfel de arhitectură, cadrul trebuie să le gestioneze într-un fel. Evident, gestionarea se face fără a reporni procesul Tarantool. Există 4 callback-uri pentru gestionarea rolurilor. Cartridge le va apela în funcție de ceea ce are scris în configurația distribuită, aplicând astfel configurația rolurilor specifice.
function init()
function validate_config()
function apply_config()
function stop()
Fiecare rol are o funcție init. Aceasta este apelată o singură dată, fie la activarea rolului, fie la repornirea Tarantool-ului. Aici este convenabil, de exemplu, să inițializezi box.space.create, sau schedulerul poate porni un anumit fiber de fundal care va efectua lucrări la intervale de timp specificate.
O singură funcție init poate să nu fie suficientă. Cartridge permite rolurilor să folosească aceea configurație distribuită pe care o folosește pentru a stoca topologia. În aceeași configurație, putem declara o nouă secțiune și stoca un fragment de configurație de afaceri în aceasta. În exemplul meu, aceasta ar putea fi schema de date sau setările programului pentru rolul scheduler.
Clusterele apelează validate_config și apply_config de fiecare dată când configurația distribuită este modificată. Când configurația este aplicată printr-o tranzacție în două faze, clusterul verifică dacă fiecare rol este pregătit să accepte noua configurație și, dacă este necesar, informează utilizatorul despre o eroare. Când toți au fost de acord că configurația este normală, se execută apply_config.
De asemenea, rolurile au o metodă stop, care este necesară pentru a curăța rezultatele activității rolului. Dacă spunem că schedulerul de pe acest server nu mai este necesar, acesta poate opri fiberii pe care i-a pornit cu ajutorul init.
Rolurile pot interacționa între ele. Ne-am obișnuit să scriem apeluri de funcții în Lua, dar poate fi cazul în care în acest proces nu există rolul de care avem nevoie. Pentru a facilita apelurile peste rețea, folosim modulul auxiliar rpc (apel de procedură la distanță), care este construit pe baza standardului netbox, integrat în Tarantool. Aceasta poate fi util, de exemplu, dacă gateway-ul dumneavoastră vrea să ceară direct scheduler-ului să execute o sarcină chiar acum, în loc să aștepte o zi întreagă.
Un alt aspect important este asigurarea disponibilității. Pentru monitorizarea sănătății, Cartridge utilizează protocolul SWIM. Pe scurt, procesele schimbă între ele „zvonuri” prin UDP — fiecare proces își informează vecinii despre ultimele noutăți, iar aceștia răspund. Dacă, dintr-o dată, nu vine un răspuns, Tarantool începe să suspecteze că ceva nu este în regulă, iar după un timp declară moartea și începe să le povestească tuturor această veste.

Pe baza acestui protocol, Cartridge organizează gestionarea automată a defectelor. Fiecare proces își monitorizează mediul, iar dacă liderul încetează să răspundă, replica poate prelua rolul acestuia, iar Cartridge configurează corespunzător rolurile active.

Aici trebuie să fim precauți, deoarece comutarea frecventă între roluri poate duce la conflicte de date în timpul replicării. Activarea automată a failover-ului fără discernământ, desigur, nu este o idee bună. Trebuie să înțelegem clar ce se întâmplă și să fim siguri că replicarea nu se va deteriora după ce liderul se va recupera și i se va restitui coroana.
Din tot ce s-a spus, ar putea rezulta impresia că rolurile sunt asemănătoare microserviciilor. Într-un anumit sens, ele chiar sunt, dar ca module în cadrul proceselor Tarantool. Însă există și o serie de diferențe esențiale. În primul rând, toate rolurile proiectului trebuie să existe în aceeași bază de cod. Toate procesele Tarantool trebuie să fie lansate din aceeași bază de cod, pentru a evita surprizele, cum ar fi acelea în care încercăm să inițializăm scheduler-ul, dar acesta pur și simplu nu există. De asemenea, nu trebuie să existe diferențe de versiuni ale codului, deoarece comportamentul sistemului în această situație este foarte greu de prezis și de depanat.
Spre deosebire de Docker, nu putem lua pur și simplu un „imagine” al unui rol, să-l transferăm pe o altă mașină și să-l pornim acolo. Rolurile noastre nu sunt la fel de izolate ca și containerelor Docker. De asemenea, nu putem rula două roluri identice pe un singur exemplar. Un rol fie există, fie nu, într-un anumit sens este un singleton. Și, în al treilea rând, în cadrul întregii grupuri de replicare, rolurile trebuie să fie identice, pentru că în caz contrar ar fi ridicol - datele sunt identice, iar configurația diferită.
Instrumentele de deploy
Am promis să arăt cum Cartridge ajută la deploy-ul aplicațiilor. Pentru a le facilita viața celor din jur, framework-ul împachetează pachete RPM:
$ cartridge pack rpm myapp -- va împacheta pentru noi .\/myapp-0.1.0-1.rpm
$ sudo yum install .\/myapp-0.1.0-1.rpmPachetul instalat conține aproape tot ce este necesar: atât aplicația, cât și dependențele luașe instalate. Tarantool pe server va veni de asemenea ca dependență a pachetului RPM, iar serviciul nostru este gata de pornire. Se face acest lucru prin systemd, dar înainte trebuie să scriem puțină configurație. Cel puțin, să specificăm URI pentru fiecare proces. Trei pentru exemplu sunt suficienți.
$ 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}
CONFIGAici există un detaliu interesant. În loc să specificăm doar portul protocolului binar, specificăm întreaga adresă publică a procesului incluzând hostname-ul. Acest lucru este necesar pentru ca nodurile cluster-ului să știe cum să se conecteze între ele. Este o idee proastă să folosim ca advertise_uri adresa 0.0.0.0, aceasta ar trebui să fie o adresă IP externă, nu un socket bind. Fără aceasta, nimic nu va funcționa, de aceea Cartridge pur și simplu nu va permite pornirea unui nod cu un advertise_uri incorect.
Acum, când configurația este gata, putem porni procesele. Deoarece un unit systemd obișnuit nu permite pornirea mai multor procese, aplicațiile pe Cartridge instalează așa-numitele unități instantiate, care funcționează astfel:
$ sudo systemctl start myapp@router
$ sudo systemctl start myapp@storage_A
$ sudo systemctl start myapp@storage_BÎn configurație, am specificat portul HTTP, pe care Cartridge servește interfața web - 8080. Să accesăm acesta și să vedem:

Vedem că procesele sunt pornite, dar încă nu sunt configurate. Cartușul nu știe încă cu cine ar trebui să se replice și nu poate lua o decizie singur, așa că așteaptă acțiunile noastre. Iar opțiunile noastre nu sunt multe: viața unui nou cluster începe cu configurarea primului nod. Apoi vom adăuga restul la cluster, le vom atribui roluri, iar astfel desfășurarea poate fi considerată finalizată cu succes.
Să ne umplem o căniță cu băutura preferată și să ne relaxăm după o săptămână lungă de muncă. Aplicația poate fi utilizată.

Concluzii
Dar care sunt rezultatele? Încercați, folosiți, lăsați feedback, deschideți bilete pe GitHub.
Linkuri
[1]
[2]
[3]
[4]
[5]
[6]
Sursa: habr.com
