Cartușul Tarantool: fragmentarea unui backend Lua în trei rânduri

Cartușul Tarantool: fragmentarea unui backend Lua în trei rânduri

La Mail.ru Group avem Tarantool - acesta este un server de aplicații în Lua, care funcționează și ca bază de date (sau invers?). Este rapid și cool, dar capacitățile unui server nu sunt încă nelimitate. De asemenea, scalarea verticală nu este un panaceu, așa că Tarantool are instrumente pentru scalarea orizontală - modulul vshard [1]. Vă permite să împărțiți datele pe mai multe servere, dar trebuie să le modificați pentru a le configura și a atașa logica de afaceri.

Vești bune: am adunat câteva fotografii mari (de ex [2], [3]) și a creat un alt cadru care va simplifica semnificativ soluția acestei probleme.

Cartușul Tarantool este un nou cadru pentru dezvoltarea sistemelor complexe distribuite. Vă permite să vă concentrați pe scrierea logicii de afaceri în loc să rezolvați problemele de infrastructură. Mai jos vă voi spune cum funcționează acest cadru și cum să scrieți servicii distribuite folosindu-l.

Și care este, mai exact, problema?

Avem o tarantula, avem vshard - ce ai mai putea dori?

În primul rând, este o chestiune de comoditate. Configurația vshard este configurată prin tabelele Lua. Pentru ca un sistem distribuit de mai multe procese Tarantool să funcționeze corect, configurația trebuie să fie aceeași peste tot. Nimeni nu vrea să facă asta manual. Prin urmare, sunt folosite tot felul de scripturi, Ansible și sisteme de implementare.

Cartușul însuși gestionează configurația vshard, face acest lucru pe baza acestuia configurație distribuită proprie. Este în esență un simplu fișier YAML, a cărui copie este stocată în fiecare instanță Tarantool. Simplificarea este că cadrul însuși își monitorizează configurația și se asigură că este același peste tot.

În al doilea rând, este din nou o chestiune de comoditate. Configurația vshard nu are nimic de-a face cu dezvoltarea logicii de afaceri și doar distrage atenția programatorului de la munca sa. Când discutăm despre arhitectura unui proiect, cel mai adesea vorbim despre componente individuale și interacțiunea acestora. Este prea devreme să ne gândim la lansarea unui cluster în trei centre de date.

Am rezolvat aceste probleme din nou și din nou și, la un moment dat, am reușit să dezvoltăm o abordare care a simplificat lucrul cu aplicația de-a lungul întregului ciclu de viață: creare, dezvoltare, testare, CI/CD, întreținere.

Cartridge introduce conceptul de rol pentru fiecare proces Tarantool. Rolurile sunt un concept care permite unui dezvoltator să se concentreze pe scrierea codului. Toate rolurile disponibile în proiect pot fi rulate pe o singură instanță Tarantool, iar acest lucru va fi suficient pentru teste.

Caracteristici cheie ale cartuşului Tarantool:

  • orchestrare automată a clusterului;
  • extinderea funcționalității aplicației folosind noi roluri;
  • șablon de aplicație pentru dezvoltare și implementare;
  • fragmentare automată încorporată;
  • integrarea cu cadrul de testare Luatest;
  • managementul clusterului folosind WebUI și API;
  • instrumente de ambalare și implementare.

Salut Lume!

Abia aștept să arăt cadrul în sine, așa că vom lăsa povestea despre arhitectură pentru mai târziu și vom începe cu ceva simplu. Dacă presupunem că Tarantool în sine este deja instalat, atunci tot ce rămâne de făcut

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

Aceste două comenzi vor instala utilitarele liniei de comandă și vă vor permite să vă creați prima aplicație din șablon:

$ cartridge create --name myapp

Și asta obținem:

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 depozit git cu un „Hello, World!” gata făcut. aplicarea. Să încercăm să-l rulăm imediat, după ce au instalat anterior dependențele (inclusiv cadrul în sine):

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

Deci, avem un nod care rulează pentru viitoarea aplicație fragmentată. Un profan curios poate deschide imediat interfața web, poate configura un grup de un nod cu mouse-ul și se poate bucura de rezultat, dar este prea devreme să ne bucurăm. Până acum, aplicația nu poate face nimic util, așa că vă voi spune despre implementare mai târziu, dar acum este timpul să scrieți cod.

Dezvoltarea aplicației

Imaginați-vă, proiectăm un proiect care trebuie să primească date, să le salveze și să construiască un raport o dată pe zi.

Cartușul Tarantool: fragmentarea unui backend Lua în trei rânduri

Începem să desenăm o diagramă și plasăm trei componente pe ea: gateway, storage și scheduler. Lucrăm în continuare la arhitectură. Deoarece folosim vshard ca stocare, adăugăm vshard-router și vshard-storage la schemă. Nici gateway-ul, nici planificatorul nu vor accesa direct spațiul de stocare pentru asta este routerul, pentru asta a fost creat.

Cartușul Tarantool: fragmentarea unui backend Lua în trei rânduri

Această diagramă încă nu reprezintă exact ceea ce vom construi în proiect, deoarece componentele arată abstract. Mai trebuie să vedem cum va fi proiectat acest lucru pe Tarantool real - să ne grupăm componentele după proces.

Cartușul Tarantool: fragmentarea unui backend Lua în trei rânduri

Nu are rost să păstrați vshard-routerul și gateway-ul pe instanțe separate. De ce trebuie să navigăm din nou în rețea dacă aceasta este deja responsabilitatea routerului? Ele trebuie să fie rulate în cadrul aceluiași proces. Adică, atât gateway-ul, cât și vshard.router.cfg sunt inițializate într-un singur proces și le lasă să interacționeze local.

În faza de proiectare, a fost convenabil să lucrez cu trei componente, dar eu, ca dezvoltator, în timp ce scriu codul, nu vreau să mă gândesc la lansarea a trei instanțe de Tarnatool. Trebuie să rulez teste și să verific dacă am scris corect gateway-ul. Sau poate vreau să demonstrez o caracteristică colegilor mei. De ce ar trebui să trec prin bătaia de cap de a implementa trei copii? Așa s-a născut conceptul de roluri. Un rol este un modul Lush obișnuit al cărui ciclu de viață este gestionat de Cartridge. În acest exemplu există patru dintre ele - gateway, router, stocare, planificator. S-ar putea să fie mai multe într-un alt proiect. Toate rolurile pot fi rulate într-un singur proces, iar acest lucru va fi suficient.

Cartușul Tarantool: fragmentarea unui backend Lua în trei rânduri

Și când vine vorba de implementare în scenă sau producție, atunci vom atribui fiecărui proces Tarantool propriul set de roluri, în funcție de capacitățile hardware:

Cartușul Tarantool: fragmentarea unui backend Lua în trei rânduri

Managementul topologiei

Informațiile despre locul în care rulează rolurile trebuie stocate undeva. Și acest „undeva” este configurația distribuită, pe care am menționat-o deja mai sus. Cel mai important lucru este topologia clusterului. Iată 3 grupuri de replicare a 5 procese Tarantool:

Cartușul Tarantool: fragmentarea unui backend Lua în trei rânduri

Nu dorim să pierdem date, așa că tratăm cu grijă informațiile despre rularea proceselor. Cartușul ține evidența configurației utilizând un commit în două faze. Odată ce dorim să actualizăm configurația, mai întâi verifică dacă toate instanțele sunt disponibile și gata să accepte noua configurație. După aceasta, a doua fază aplică configurația. Astfel, chiar dacă o copie se dovedește a fi temporar indisponibilă, nu se va întâmpla nimic rău. Configurația pur și simplu nu va fi aplicată și veți vedea o eroare în avans.

De asemenea, în secțiunea de topologie, este indicat un parametru atât de important precum liderul fiecărui grup de replicare. De obicei, aceasta este copia care este înregistrată. Restul sunt cel mai adesea doar în citire, deși pot exista și excepții. Uneori, dezvoltatorii curajoși nu se tem de conflicte și pot scrie date pe mai multe replici în paralel, dar există unele operațiuni care, indiferent de ce, nu ar trebui efectuate de două ori. Pentru aceasta există semnul unui lider.

Cartușul Tarantool: fragmentarea unui backend Lua în trei rânduri

Viața rolurilor

Pentru ca un rol abstract să existe într-o astfel de arhitectură, cadrul trebuie să le gestioneze cumva. Desigur, controlul are loc fără a reporni procesul Tarantool. Există 4 apeluri inverse pentru a gestiona rolurile. Cartușul însuși le va apela în funcție de ceea ce este scris în configurația sa distribuită, aplicând astfel configurația unor roluri specifice.

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

Fiecare rol are o funcție init. Este apelat o dată fie când rolul este activat, fie când Tarantool este repornit. Este convenabil acolo, de exemplu, să inițializați box.space.create, sau planificatorul poate lansa o fibră de fundal care va lucra la anumite intervale de timp.

O singură funcție init poate să nu fie suficient. Cartridge permite rolurilor să profite de configurația distribuită pe care o folosește pentru a stoca topologia. Putem declara o nouă secțiune în aceeași configurație și stocăm un fragment din configurația business în ea. În exemplul meu, aceasta ar putea fi o schemă de date sau setări de programare pentru rolul de planificator.

Apeluri în grup validate_config и apply_config de fiecare dată când configurația distribuită se modifică. Atunci când o configurație este aplicată printr-un commit în două faze, clusterul verifică dacă fiecare rol este pregătit să accepte această nouă configurație și, dacă este necesar, raportează o eroare utilizatorului. Când toată lumea este de acord că configurația este normală, atunci apply_config.

De asemenea, rolurile au o metodă stop, care este necesar pentru a curăța rezultatul rolului. Dacă spunem că planificatorul nu mai este necesar pe acest server, acesta poate opri acele fibre cu care a început init.

Rolurile pot interacționa între ele. Suntem obișnuiți să scriem apeluri de funcții în Lua, dar se poate întâmpla ca un anumit proces să nu aibă rolul de care avem nevoie. Pentru a facilita apelurile prin rețea, folosim modulul auxiliar rpc (apel de procedură la distanță), care este construit pe baza netbox-ului standard încorporat în Tarantool. Acest lucru poate fi util dacă, de exemplu, gateway-ul dvs. dorește să ceară direct programatorului să facă treaba chiar acum, în loc să aștepte o zi.

Un alt punct important este asigurarea toleranței la erori. Cartușul folosește protocolul SWIM pentru a monitoriza starea de sănătate [4]. Pe scurt, procesele fac schimb de „zvonuri” între ele prin UDP – fiecare proces le spune vecinilor cele mai recente știri, iar aceștia răspund. Dacă deodată răspunsul nu vine, Tarantool începe să bănuiască că ceva nu este în regulă, iar după un timp recită moartea și începe să spună tuturor din jur această știre.

Cartușul Tarantool: fragmentarea unui backend Lua în trei rânduri

Pe baza acestui protocol, Cartridge organizează procesarea automată a erorilor. Fiecare proces își monitorizează mediul și, dacă liderul încetează brusc să mai răspundă, replica își poate prelua rolul, iar Cartridge configurează rolurile de rulare în consecință.

Cartușul Tarantool: fragmentarea unui backend Lua în trei rânduri

Trebuie să fiți atenți aici, deoarece comutarea frecventă înainte și înapoi poate duce la conflicte de date în timpul replicării. Desigur, nu ar trebui să activați failover-ul automat la întâmplare. Trebuie să înțelegem clar ce se întâmplă și să fim siguri că replicarea nu se va întrerupe după ce liderul este restaurat și coroana îi este returnată.

Din toate acestea, puteți avea senzația că rolurile sunt similare cu microservicii. Într-un fel, ele sunt doar atât, doar ca module în interiorul proceselor Tarantool. Dar există și o serie de diferențe fundamentale. În primul rând, toate rolurile de proiect trebuie să locuiască în aceeași bază de cod. Și toate procesele Tarantool ar trebui să fie lansate din aceeași bază de cod, astfel încât să nu existe surprize ca acelea când încercăm să inițializam planificatorul, dar pur și simplu nu există. De asemenea, nu ar trebui să permiteți diferențe în versiunile de cod, deoarece comportamentul sistemului într-o astfel de situație este foarte greu de prezis și de depanat.

Spre deosebire de Docker, nu putem doar să luăm o „imagine” de rol, să o ducem la o altă mașină și să o rulăm acolo. Rolurile noastre nu sunt la fel de izolate ca containerele Docker. De asemenea, nu putem rula două roluri identice pe o singură instanță. Un rol fie există, fie nu există, într-un sens, este un singleton. Și în al treilea rând, rolurile trebuie să fie aceleași în cadrul întregului grup de replicare, pentru că altfel ar fi absurd - datele sunt aceleași, dar configurația este diferită.

Instrumente de implementare

Am promis că voi arăta cum Cartridge ajută la implementarea aplicațiilor. Pentru a face viața mai ușoară altora, cadrul pachetelor pachete RPM:

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

Pachetul instalat conține aproape tot ce aveți nevoie: atât aplicația, cât și dependențele instalate. Tarantool va ajunge și pe server ca o dependență a pachetului RPM, iar serviciul nostru este gata de lansare. Acest lucru se face prin systemd, dar mai întâi trebuie să scrieți o mică configurație. Specificați cel puțin URI-ul fiecărui proces. Trei sunt suficiente, de exemplu.

$ 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

Există o nuanță interesantă aici. În loc să specificăm doar portul de protocol binar, specificăm întreaga adresă publică a procesului, inclusiv numele gazdei. Acest lucru este necesar pentru ca nodurile clusterului să știe cum să se conecteze între ele. Este o idee proastă să folosiți 0.0.0.0 ca adresă advertise_uri, ar trebui să fie o adresă IP externă, nu o legătură de socket. Fără el, nimic nu va funcționa, așa că Cartridge pur și simplu nu vă va permite să lansați un nod cu advertise_uri greșit.

Acum că configurația este gata, puteți începe procesele. Deoarece o unitate obișnuită de sistem nu permite mai mult de un proces să înceapă, aplicațiile de pe Cartuș sunt instalate de așa-numitul. unități instanțiate care funcționează astfel:

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

În configurare, am specificat portul HTTP pe care Cartridge servește interfața web - 8080. Să mergem la el și să aruncăm o privire:

Cartușul Tarantool: fragmentarea unui backend Lua în trei rânduri

Vedem că, deși procesele rulează, acestea nu sunt încă configurate. Cartușul nu știe încă cine ar trebui să reproducă cu cine și nu poate lua singur o decizie, așa că așteaptă acțiunile noastre. Dar nu avem prea multe de ales: viața unui nou cluster începe cu configurarea primului nod. Apoi îi vom adăuga pe ceilalți la cluster, le vom atribui roluri și, în acest moment, implementarea poate fi considerată finalizată cu succes.

Să turnăm un pahar din băutura ta preferată și să ne relaxăm după o săptămână lungă de muncă. Aplicația poate fi folosită.

Cartușul Tarantool: fragmentarea unui backend Lua în trei rânduri

Rezultatele

Care sunt rezultatele? Încearcă, folosește-l, lasă feedback, creează bilete pe Github.

referințe

[1] Tarantool » 2.2 » Referință » Referință Rocks » Modul vshard

[2] Cum am implementat nucleul afacerii de investiții a Alfa-Bank pe baza Tarantool

[3] Arhitectură de facturare de nouă generație: transformare odată cu trecerea la Tarantool

[4] SWIM - protocol de construcție cluster

[5] GitHub - tarantool/cartridge-cli

[6] GitHub - tarantool/cartuș

Sursa: www.habr.com

Adauga un comentariu