Tarantool Cartridge: sharding van een Lua-backend in drie regels

Tarantool Cartridge: sharding van een Lua-backend in drie regels

Bij Mail.ru Group hebben we Tarantool - een applicatieserver in Lua, die ook dienstdoet als database (of andersom?). Het is snel en geweldig, maar de mogelijkheden van één server zijn nog steeds niet onbeperkt. Verticale schaalvergroting is ook geen panacee, daarom zijn er in Tarantool tools voor horizontale schaalvergroting - de vshard-module. [1]. Hiermee kunnen gegevens over meerdere servers worden geshard, maar het vereist wel enige inspanning om het in te stellen en de bedrijfslogica eraan te koppelen.

Goed nieuws: we hebben wat ervaring opgedaan (bijvoorbeeld [2], [3]) en we hebben een nieuw framework ontwikkeld dat het oplossen van dit probleem aanzienlijk zal vereenvoudigen.

Tarantool Cartridge is een nieuw framework voor het ontwikkelen van complexe gedistribueerde systemen. Het stelt je in staat om je te concentreren op het schrijven van bedrijfslogica in plaats van op infrastructuurproblemen. Onderstaande zal ik uitleggen hoe dit framework is opgebouwd en hoe je het kunt gebruiken om gedistribueerde services te schrijven.

En waar ligt het probleem eigenlijk?

We hebben een Tarantool, we hebben vshard - wat wil je nog meer?

Ten eerste gaat het om gebruiksgemak. De configuratie van vshard wordt ingesteld via Lua-tabellen. Om ervoor te zorgen dat een gedistribueerd systeem van meerdere Tarantool-processen correct werkt, moet de configuratie overal gelijk zijn. Niemand wil dit handmatig doen. Daarom komen allerlei scripts, Ansible en implementatiesystemen van pas.

Cartridge beheert zelf de configuratie van vshard, gebaseerd op zijn eigen gedistribueerde configuratie.In wezen is dit een eenvoudige YAML-bestand, waarvan een kopie wordt opgeslagen in elke Tarantool-instance. De vereenvoudiging ligt in het feit dat het framework zelf zijn configuratie in de gaten houdt en ervoor zorgt dat deze overal consistent is.

Ten tweede draait het opnieuw om gebruiksgemak. De vshard-configuratie heeft niets te maken met het ontwikkelen van bedrijfslogica en leidt de programmeur alleen maar af van zijn werk. Wanneer we de architectuur van een bepaald project bespreken, gaat het meestal over afzonderlijke componenten en hun interactie. Het is nog te vroeg om aan de uitrol van een cluster in 3 datacenters te denken.

We hebben deze problemen keer op keer aangepakt, en op een gegeven moment zijn we in staat geweest om een benadering te ontwikkelen die het werken met de applicatie gedurende zijn hele levenscyclus vereenvoudigt: creatie, ontwikkeling, testen, CI/CD, en onderhoud.

Cartridge introduceert het concept van rollen voor elk proces in Tarantool. Rollen zijn het idee dat het de ontwikkelaar mogelijk maakt zich te concentreren op het schrijven van code. Alle beschikbare rollen in het project kunnen op één exemplaar van Tarantool worden uitgevoerd, en dat is voldoende voor tests.

Belangrijkste mogelijkheden van Tarantool Cartridge:

  • geautomatiseerde orkestratie van een cluster;
  • uitbreiding van de functionaliteit van de applicatie met nieuwe rollen;
  • applicatiesjabloon voor ontwikkeling en implementatie;
  • ingebouwde automatische sharding;
  • integratie met het testframework Luatest;
  • beheer van het cluster via WebUI en API;
  • hulpmiddelen voor verpakking en implementatie.

Hello, World!

Ik kan niet wachten om het framework zelf te laten zien, dus we houden de architectuurbespreking voor later en beginnen eenvoudig. Laten we aannemen dat Tarantool al is geïnstalleerd, dan hoeft u alleen nog maar

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

Deze twee commando's installeren de commandoregelhulpmiddelen en stellen u in staat uw eerste applicatie uit het sjabloon te maken:

$ cartridge create --name myapp

En dit is wat we krijgen:

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/

Dit is een git-repository met een kant-en-klare 'Hello, World!' applicatie. Laten we deze meteen proberen uit te voeren, nadat we de afhankelijkheden (inclusief het framework zelf) hebben geïnstalleerd:

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

Dus, we hebben één node van de toekomstige geshardde applicatie draaiende. Een nieuwsgierige toeschouwer kan meteen de webinterface openen, het cluster van één node met de muis configureren en genieten van het resultaat, maar blijdschap is voorlopig te vroeg. Voor nu kan de applicatie nog niets nuttigs doen, dus ik zal later over de implementatie vertellen, maar nu is het tijd om code te schrijven.

Applicatieontwikkeling

Stel je voor dat we een project ontwerpen dat gegevens moet ontvangen, opslaan en eens per dag een rapport moet opstellen.

Tarantool Cartridge: sharding van een Lua-backend in drie regels

We beginnen met het tekenen van een schema en plaatsen daar drie componenten op: gateway, storage en scheduler. We werken de architectuur verder uit. Aangezien we vshard als opslag gebruiken, voegen we vshard-router en vshard-storage aan het schema toe. Noch de gateway, noch de scheduler zullen direct naar de opslag communiceren; daarvoor is er een router, die daarvoor is gemaakt.

Tarantool Cartridge: sharding van een Lua-backend in drie regels

Dit diagram weerspiegelt nog steeds niet helemaal wat we in het project gaan creëren, omdat de componenten abstract lijken. We moeten kijken hoe dit zich translateert naar de echte Tarantool — laten we onze componenten groeperen op basis van processen.

Tarantool Cartridge: sharding van een Lua-backend in drie regels

Het heeft weinig zin om de vshard-router en gateway op aparte instanties te houden. Waarom zouden we onnodig door het netwerk moeten gaan, als dit al tot de taken van de router behoort? Ze moeten binnen één proces worden uitgevoerd. Dat wil zeggen, in hetzelfde proces worden zowel de gateway als vshard.router.cfg geïnitialiseerd, en laten we ervoor zorgen dat ze lokaal met elkaar communiceren.

Tijdens de ontwerpfase was het handig om met drie componenten te werken, maar ik wil als ontwikkelaar niet na te denken over het opstarten van drie instanties van Tarantool terwijl ik code schrijf. Ik moet tests uitvoeren en controleren of ik de gateway correct heb geschreven. Of misschien wil ik een functie aan mijn collega's demonstreren. Waarom zou ik het moeilijk maken met de implementatie van drie instanties? Zo is het concept van rollen ontstaan. Een rol is een gewone Lua-module, wiens levenscyclus wordt beheerd door Cartridge. In dit voorbeeld zijn er vier — gateway, router, storage, scheduler. In een ander project kunnen er meer zijn. Alle rollen kunnen in één proces worden uitgevoerd, en dat is voldoende.

Tarantool Cartridge: sharding van een Lua-backend in drie regels

En wanneer het aankomt op uitrol naar staging of productie, dan zullen we elke Tarantool-proces zijn eigen set rollen toekennen, afhankelijk van de hardwarecapaciteiten:

Tarantool Cartridge: sharding van een Lua-backend in drie regels

Beheer van de topologie

We moeten ergens informatie opslaan over waar welke rollen draaien. En dat 'ergens' is een gedistribueerde configuratie, die ik hierboven al noemde. Het belangrijkste daarin is de topologie van het cluster. Hier zijn 3 replicagroepen weergegeven bestaande uit 5 Tarantool-processen:

Tarantool Cartridge: sharding van een Lua-backend in drie regels

We willen geen gegevens verliezen, daarom zijn we voorzichtig met de informatie over de draaiende processen. Cartridge houdt de configuratie in de gaten met behulp van een tweefasecommitting. Zodra we de configuratie willen bijwerken, controleert hij eerst de beschikbaarheid van alle instanties en hun gereedheid om de nieuwe configuratie te accepteren. Vervolgens wordt in de tweede fase de configuratie toegepast. Op deze manier, zelfs als een instantie tijdelijk niet beschikbaar is, gebeurt er niets ergs. De configuratie zal gewoon niet worden toegepast en je ziet van tevoren de fout.

In de sectie topologie is ook een belangrijk parameter aangegeven, namelijk de leider van elke replicagroep. Dit is meestal de instantie waarop gegevens worden geschreven. De andere zijn vaak alleen-lezen, hoewel er uitzonderingen kunnen zijn. Soms zijn moedige ontwikkelaars niet bang voor conflicten en kunnen ze gegevens parallel naar meerdere replicas schrijven, maar er zijn bepaalde handelingen die hoe dan ook niet twee keer uitgevoerd mogen worden. Hiervoor is er een aanduiding van de leider.

Tarantool Cartridge: sharding van een Lua-backend in drie regels

Levenscyclus van rollen

Om een abstracte rol in zo'n architectuur te laten bestaan, moet het framework ze op de een of andere manier beheren. Natuurlijk gebeurt het beheer zonder de Tarantool-proces opnieuw op te starten. Voor rolbeheer zijn er 4 callbacks. Cartridge zal deze aanroepen afhankelijk van wat er in de gedistribueerde configuratie is geschreven, en daarmee de configuratie toepassen op specifieke rollen.

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

Elke rol heeft een functie , wat erg handig is. Bovendien kunnen meerdere configuraties worden geschreven: ontwikkelen op de standaardconfiguratie, en daarna implementeren met het commando. Deze wordt een keer aangeroepen of bij het inschakelen van de rol, of bij het opnieuw opstarten van Tarantool. Hierin is het handig om bijvoorbeeld box.space.create te initialiseren, of de scheduler kan een achtergrond fibre starten die taken uitvoert met bepaalde tussenpozen.

Een functie , wat erg handig is. Bovendien kunnen meerdere configuraties worden geschreven: ontwikkelen op de standaardconfiguratie, en daarna implementeren met het commando kan niet genoeg zijn. Cartridge stelt rollen in staat gebruik te maken van de gedistribueerde configuratie die het gebruikt voor het opslaan van topologie. We kunnen in deze configuratie een nieuwe sectie verklaren en daarin een fragment van de businessconfiguratie opslaan. In mijn voorbeeld kan dit een datamodel zijn, of instellingen voor de schema van de rol scheduler.

Het cluster roept validate_config en apply_config bij elke wijziging van de gedistribueerde configuratie. Wanneer de configuratie wordt toegepast met een twee-fasen commit, controleert het cluster of elke rol klaar is om deze nieuwe configuratie te accepteren en meldt indien nodig de gebruiker over een fout. Wanneer iedereen het eens is dat de configuratie normaal is, wordt uitgevoerd apply_config.

Daarnaast hebben rollen een methode stop, die nodig is voor het opruimen van de resultaten van de activiteit van de rol. Als we zeggen dat de scheduler op deze server niet meer nodig is, kan hij de fibers stoppen die hij met behulp van , wat erg handig is. Bovendien kunnen meerdere configuraties worden geschreven: ontwikkelen op de standaardconfiguratie, en daarna implementeren met het commando.

Rollen kunnen met elkaar communiceren. We zijn gewend om functie-aanroepen in Lua te schrijven, maar het kan zomaar gebeuren dat de benodigde rol in dit proces ontbreekt. Om de netwerkverkeer te vergemakkelijken, gebruiken we de hulpfunctie rpc (remote procedure call), die is gebouwd op de standaard netbox die in Tarantool is ingebouwd. Dit kan nuttig zijn als, bijvoorbeeld, je gateway direct de scheduler vraagt om nu te werken, en niet een dag te wachten.

Een ander belangrijk punt is het waarborgen van de beschikbaarheid. Voor gezondheidsmonitoring gebruikt Cartridge het SWIM-protocol. [4]Om het kort samen te vatten, processen wisselen 'geruchten' met elkaar uit via UDP - elk proces vertelt zijn buren het laatste nieuws, en zij antwoorden. Als er plotseling geen antwoord komt, begint Tarantool iets verdachts te vermoeden, en na een tijdje verkondigt het de dood en vertelt het aan alle omstanders dit nieuws.

Tarantool Cartridge: sharding van een Lua-backend in drie regels

Op basis van dit protocol organiseert Cartridge automatische foutafhandeling. Elk proces houdt zijn omgeving in de gaten, en als de leider plotseling niet meer reageert, kan een replica zijn rol overnemen, en Cartridge configureert de actieve rollen dienovereenkomstig.

Tarantool Cartridge: sharding van een Lua-backend in drie regels

Hier moet voorzichtig mee omgegaan worden, want frequente schakelingen heen en weer kunnen leiden tot dataconflicten bij replicatie. Automatische failover zomaar inschakelen is natuurlijk niet verstandig. We moeten goed begrijpen wat er gebeurt en zeker zijn dat de replicatie niet kapot gaat zodra de leider herstelt en zijn kroon terugkrijgt.

Uit het bovenstaande kan de indruk ontstaan dat rollen op microservices lijken. In zekere zin zijn ze dat ook, alleen als modules binnen Tarantool-processen. Maar er zijn ook enkele fundamentele verschillen. Ten eerste moeten alle rollen in het project binnen dezelfde codebase leven. En alle Tarantool-processen moeten vanuit dezelfde codebase worden gestart om verrassingen te voorkomen, zoals wanneer we proberen de scheduler te initialiseren, maar deze gewoon niet bestaat. Bovendien moeten er geen verschillen in codeversies zijn, want het gedrag van het systeem in zo'n situatie is zeer moeilijk te voorspellen en te debuggen.

In tegenstelling tot Docker kunnen we niet gewoon een 'image' van een rol nemen, deze naar een andere machine brengen en daar uitvoeren. Onze rollen zijn niet zo geïsoleerd als Docker-containers. Ook kunnen we op één instantie niet twee identieke rollen draaien. Een rol bestaat of niet, in zekere zin is dit een singleton. Ten derde moeten de rollen binnen de hele replicatiegroep identiek zijn, want anders zou het absurd zijn — de gegevens zijn hetzelfde, maar de configuratie verschilt.

Deploy-tools

Ik beloofde te laten zien hoe Cartridge helpt bij het uitrollen van applicaties. Om het leven voor anderen gemakkelijker te maken, verpakt het framework RPM-pakketten:

$ cartridge pack rpm myapp -- verpakt voor ons .\/myapp-0.1.0-1.rpm
$ sudo yum install .\/myapp-0.1.0-1.rpm

Het geïnstalleerde pakket bevat bijna alles wat nodig is: zowel de applicatie als de geïnstalleerde Lua-afhankelijkheden. Tarantool komt ook mee naar de server als een RPM-pakketafhankelijkheid, en onze service is klaar om te starten. Dit gebeurt via systemd, maar eerst moet er een beetje configuratie worden geschreven. Minimaal moet de URI van elk proces worden aangegeven. Drie is genoeg voor dit voorbeeld.

$ 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

Hier is een interessant detail. In plaats van alleen de poort van het binaire protocol op te geven, geven we het volledige openbare adres van het proces op, inclusief de hostname. Dit is nodig zodat de knooppunten van de cluster weten hoe ze met elkaar moeten verbinden. Het is een slecht idee om als advertise_uri het adres 0.0.0.0 te gebruiken; dit moet een extern IP-adres zijn en geen bind-socket. Zonder dit zal niets werken, daarom staat Cartridge het niet toe om een knooppunt te starten met een onjuiste advertise_uri.

Nu de configuratie klaar is, kunnen we de processen starten. Aangezien een gewone systemd-eenheid niet meer dan één proces kan starten, stelt Cartridge zogenaamde instantiated-eenheden in, die als volgt werken:

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

In de configuratie hebben we de HTTP-poort opgegeven waarop Cartridge de webinterface bedient — 8080. Laten we daarheen gaan en kijken:

Tarantool Cartridge: sharding van een Lua-backend in drie regels

We zien dat de processen zijn gestart, maar nog niet geconfigureerd. De cartridge weet nog niet wie er met wie moet repliceren en kan zelf geen beslissing nemen, dus wacht op onze acties. En onze keuze is niet groot: het leven van een nieuwe cluster begint met de configuratie van de eerste node. Daarna voegen we de rest van de cluster toe, wijzen we hun rollen toe, en dan kunnen we de deployment als succesvol beschouwen.

Laten we een kopje van onze favoriete drank inschenken en ontspannen na een lange werkweek. De applicatie kan worden gebruikt.

Tarantool Cartridge: sharding van een Lua-backend in drie regels

Conclusies

Wat zijn de resultaten? Probeer het uit, gebruik het, laat feedback achter en maak tickets aan op GitHub.

Links

[1] Tarantool » 2.2 » Referentie » Rocks referentie » Module vshard

[2] Hoe we de kern van het investeringsbedrijf van Alfa-Bank implementeerden op basis van Tarantool

[3] Architectuur van generatiebilling: transformatie naar Tarantool

[4] SWIM — cluster bouwprotocol

[5] GitHub — tarantool/cartridge-cli

[6] GitHub — tarantool/cartridge

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster