Tarantool Cartridge : sharding du backend Lua en trois lignes

Tarantool Cartridge : sharding du backend Lua en trois lignes

Dans Mail.ru Group, nous avons Tarantool — c'est un serveur d'applications en Lua qui fait Ă©galement office de base de donnĂ©es (ou vice versa ?). Il est rapide et performant, mais les capacitĂ©s d'un seul serveur ne sont pas illimitĂ©es. Le scaling vertical n'est pas non plus une panacĂ©e, c'est pourquoi Tarantool propose des outils pour le scaling horizontal — le module vshard. [1]. Il permet de sharder les donnĂ©es sur plusieurs serveurs, mais il faut s'investir pour le configurer et intĂ©grer la logique mĂ©tier.

Bonne nouvelle : nous avons rencontré des obstacles (par exemple [2], [3]) et avons développé un nouveau framework qui simplifie considérablement cette problématique.

Tarantool Cartridge — est un nouveau framework pour le dĂ©veloppement de systĂšmes distribuĂ©s complexes. Il permet de se concentrer sur l'Ă©criture de la logique mĂ©tier plutĂŽt que sur les problĂšmes d'infrastructure. Dans cet article, je vais expliquer comment ce framework fonctionne et comment l'utiliser pour Ă©crire des services distribuĂ©s.

Mais quel est donc le problĂšme ?

Nous avons Tarantool, nous avons vshard — que demander de plus ?

PremiĂšrement, il s'agit de la commoditĂ©. La configuration de vshard se rĂšgle via des tables Lua. Pour qu'un systĂšme distribuĂ© composĂ© de plusieurs processus Tarantool fonctionne correctement, la configuration doit ĂȘtre identique partout. Personne ne veut s'en occuper manuellement. C'est pourquoi divers scripts, Ansible et des systĂšmes de dĂ©ploiement sont utilisĂ©s.

Cartridge gĂšre la configuration de vshard de maniĂšre autonome, en se basant sur sa propre configuration distribuĂ©e. En fait, c'est un simple fichier YAML dont une copie est conservĂ©e dans chaque instance de Tarantool. La simplification rĂ©side dans le fait que le framework veille lui-mĂȘme Ă  sa configuration et Ă  son uniformitĂ©.

DeuxiÚmement, il s'agit encore de commodité. La configuration vshard n'a rien à voir avec le développement de la logique métier et ne fait que distraire le programmeur de son travail. Lorsque nous discutons de l'architecture d'un projet, il est généralement question de composants individuels et de leurs interactions. Il est encore tÎt pour penser au déploiement d'un cluster sur 3 centres de données.

Nous avons résolu ces problÚmes à maintes reprises, et à un moment donné, nous avons réussi à développer une approche qui simplifie le travail avec l'application tout au long de son cycle de vie : création, développement, test, CI/CD, maintenance.

Le Cartridge introduit le concept de rĂŽle pour chaque processus Tarantool. Les rĂŽles sont cette notion qui permet au dĂ©veloppeur de se concentrer sur l'Ă©criture de code. Tous les rĂŽles disponibles dans le projet peuvent ĂȘtre lancĂ©s sur une seule instance de Tarantool, et cela suffira pour les tests.

Les principales fonctionnalités de Tarantool Cartridge :

  • l'orchestration automatisĂ©e du cluster ;
  • l'extension des fonctionnalitĂ©s de l'application grĂące Ă  de nouveaux rĂŽles ;
  • un modĂšle d'application pour le dĂ©veloppement et le dĂ©ploiement ;
  • le sharding automatique intĂ©grĂ© ;
  • l'intĂ©gration avec le framework de test Luatest ;
  • la gestion du cluster via WebUI et API ;
  • des outils de packaging et de dĂ©ploiement.

Hello, World!

J'ai hĂąte de montrer le framework lui-mĂȘme, alors parlons de l'architecture plus tard, et commençons par quelque chose de simple. Si l'on suppose que Tarantool est dĂ©jĂ  installĂ©, il reste juste Ă  faire

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

Ces deux commandes installeront les utilitaires en ligne de commande et permettront de créer votre premiÚre application à partir du modÚle :

$ cartridge create --name myapp

Et voici ce que nous allons obtenir :

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/

C'est un dĂ©pĂŽt git avec une application "Hello, World!" prĂȘte. Essayons de la lancer tout de suite, aprĂšs avoir installĂ© les dĂ©pendances (y compris le framework lui-mĂȘme) :

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

Ainsi, nous avons une nƓud de l'application shardĂ©e en cours d'exĂ©cution. Un curieux peut immĂ©diatement ouvrir l'interface web, configurer le cluster Ă  partir d'un nƓud et profiter du rĂ©sultat, mais il est encore trop tĂŽt pour se rĂ©jouir. Pour l'instant, l'application ne peut rien faire d'utile, donc je parlerai du dĂ©ploiement plus tard, et maintenant il est temps d'Ă©crire du code.

Développement d'applications

Imaginez que nous concevons un projet qui doit recevoir des données, les enregistrer et générer un rapport une fois par jour.

Tarantool Cartridge : sharding du backend Lua en trois lignes

Nous commençons à dessiner le schéma et y plaçons trois composants : gateway, storage et scheduler. Nous développons davantage l'architecture. Puisque nous utilisons vshard comme stockage, nous ajoutons vshard-router et vshard-storage au schéma. Ni le gateway ni le scheduler ne communiqueront directement avec le stockage, c'est le rÎle du routeur, il est conçu pour ça.

Tarantool Cartridge : sharding du backend Lua en trois lignes

Ce schĂ©ma ne reflĂšte toujours pas tout Ă  fait ce que nous allons crĂ©er dans le projet, car les composants apparaissent abstraits. Nous devons encore examiner comment cela se projette sur le vĂ©ritable Tarantool — groupons nos composants par processus.

Tarantool Cartridge : sharding du backend Lua en trois lignes

Il n'a pas beaucoup de sens de garder le vshard-router et la passerelle sur des instances sĂ©parĂ©es. Pourquoi devrions-nous traverser le rĂ©seau un de plus, si cela fait dĂ©jĂ  partie des responsabilitĂ©s du routeur ? Ils doivent ĂȘtre lancĂ©s dans un seul processus. Cela signifie que la passerelle, ainsi que vshard.router.cfg, doivent s'initialiser dans un mĂȘme processus, et qu'ils puissent interagir localement.

À la phase de conception, travailler avec trois composants Ă©tait pratique, mais je, en tant que dĂ©veloppeur, ne veux pas rĂ©flĂ©chir au lancement de trois instances de Tarnatool pendant que j'Ă©cris du code. J'ai besoin de lancer des tests et de vĂ©rifier que j'ai correctement Ă©crit la passerelle. Ou peut-ĂȘtre que je veux prĂ©senter une caractĂ©ristique Ă  mes collĂšgues. Pourquoi devrais-je me compliquer la vie avec le dĂ©ploiement de trois instances ? C'est ainsi qu'est nĂ©e la concept de rĂŽles. Un rĂŽle est simplement un module Lua dont le cycle de vie est gĂ©rĂ© par Cartridge. Dans cet exemple, il y en a quatre : passerelle, routeur, stockage, planificateur. Dans un autre projet, il pourrait y en avoir plus. Tous les rĂŽles peuvent ĂȘtre lancĂ©s dans un seul processus, et cela suffira.

Tarantool Cartridge : sharding du backend Lua en trois lignes

Et quand il s'agira de déploiement en staging ou en production, nous attribuerons à chaque processus Tarantool son propre ensemble de rÎles en fonction des capacités matérielles :

Tarantool Cartridge : sharding du backend Lua en trois lignes

Gestion de la topologie

Les informations concernant l'endroit oĂč certaines rĂŽles sont exĂ©cutĂ©es doivent ĂȘtre stockĂ©es quelque part. Et ce « quelque part » est une configuration distribuĂ©e, dont j'ai dĂ©jĂ  parlĂ© prĂ©cĂ©demment. Le plus important dans cela est la topologie du cluster. Ici, trois groupes de rĂ©plication constituĂ©s de cinq processus Tarantool sont reprĂ©sentĂ©s :

Tarantool Cartridge : sharding du backend Lua en trois lignes

Nous ne voulons pas perdre de donnĂ©es, donc nous prenons soin des informations concernant les processus en cours d'exĂ©cution. Cartridge surveille la configuration grĂące Ă  un commit en deux phases. DĂšs que nous voulons mettre Ă  jour la configuration, il vĂ©rifie d'abord la disponibilitĂ© de toutes les instances et leur capacitĂ© Ă  accepter une nouvelle configuration. Ensuite, lors de la deuxiĂšme phase, la configuration est appliquĂ©e. Ainsi, mĂȘme si une instance devient temporairement indisponible, rien de grave ne se produira. La configuration ne sera tout simplement pas appliquĂ©e et vous verrez une erreur Ă  l'avance.

Dans la section topologie, un paramĂštre important est Ă©galement dĂ©fini, Ă  savoir le leader de chaque groupe de rĂ©plication. GĂ©nĂ©ralement, il s'agit de l'instance sur laquelle les Ă©critures sont effectuĂ©es. Les autres sont le plus souvent en mode lecture seule, bien qu'il puisse y avoir des exceptions. Parfois, des dĂ©veloppeurs audacieux n'ont pas peur des conflits et peuvent Ă©crire des donnĂ©es sur plusieurs rĂ©pliques en parallĂšle, mais certaines opĂ©rations, quelles qu'en soient les circonstances, ne doivent pas ĂȘtre exĂ©cutĂ©es deux fois. C'est ce qu'on appelle le leader.

Tarantool Cartridge : sharding du backend Lua en trois lignes

Cycle des rĂŽles

Pour qu'un rĂŽle abstrait puisse exister dans une telle architecture, le cadre doit les gĂ©rer d'une maniĂšre ou d'une autre. Évidemment, la gestion se fait sans redĂ©marrer le processus Tarantool. Il existe 4 callbacks pour la gestion des rĂŽles. Cartridge les appellera lui-mĂȘme en fonction de ce qui est Ă©crit dans sa configuration distribuĂ©e, appliquant ainsi la configuration Ă  des rĂŽles spĂ©cifiques.

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

Chaque rÎle possÚde une fonction init. Elle est appelée une fois lors de l'activation du rÎle ou lors du redémarrage de Tarantool. C'est pratique, par exemple, pour initialiser box.space.create, ou le planificateur peut démarrer un certain fiber en arriÚre-plan qui effectuera des tùches à intervalles réguliers.

Une seule fonction init peut ne pas suffire. Cartridge permet aux rĂŽles d'utiliser la configuration distribuĂ©e qu'il utilise pour stocker la topologie. Nous pouvons dĂ©clarer une nouvelle section dans cette mĂȘme configuration et y stocker un fragment de configuration mĂ©tier. Dans mon exemple, cela peut ĂȘtre un schĂ©ma de donnĂ©es ou des paramĂštres de planification pour le rĂŽle scheduler.

Le cluster appelle validate_config et apply_config Ă  chaque modification de la configuration distribuĂ©e. Lorsque la configuration est appliquĂ©e par un commit en deux phases, le cluster vĂ©rifie que chaque rĂŽle est prĂȘt Ă  accepter cette nouvelle configuration et informe l'utilisateur d'une erreur si nĂ©cessaire. Une fois que tout le monde est d'accord sur le fait que la configuration est normale, apply_config.

Les rĂŽles ont Ă©galement une mĂ©thode stop, qui est nĂ©cessaire pour nettoyer les rĂ©sultats de l'activitĂ© du rĂŽle. Si nous disons que le scheduler sur ce serveur n'est plus nĂ©cessaire, il peut arrĂȘter les fibers qu'il a lancĂ©s Ă  l'aide de init.

Les rĂŽles peuvent interagir entre eux. Nous avons l'habitude d'Ă©crire des appels de fonctions en Lua, mais il peut arriver qu'il n'y ait pas le rĂŽle dont nous avons besoin dans ce processus. Pour faciliter les appels Ă  distance, nous utilisons un module auxiliaire rpc (remote procedure call), basĂ© sur le netbox standard intĂ©grĂ© dans Tarantool. Cela peut ĂȘtre utile si, par exemple, votre gateway souhaite demander directement au scheduler d'exĂ©cuter une tĂąche immĂ©diatement, plutĂŽt que d'attendre une journĂ©e.

Un autre point important est l'assurance de la tolĂ©rance aux pannes. Pour le suivi de l'Ă©tat de santĂ©, Cartridge utilise le protocole SWIM. [4]. Pour rĂ©sumer, les processus Ă©changent entre eux des 'rumeurs' via UDP : chaque processus informe ses voisins des derniĂšres nouvelles, et ils rĂ©pondent. Si une rĂ©ponse ne parvient pas, Tarantool commence Ă  suspecter quelque chose d’anormal, et aprĂšs un certain temps, annonce la mort et commence Ă  relater cette nouvelle Ă  tous les environnants.

Tarantool Cartridge : sharding du backend Lua en trois lignes

Sur la base de ce protocole, Cartridge organise un traitement automatique des pannes. Chaque processus surveille son environnement et, si le leader cesse soudainement de répondre, un replica peut prendre son rÎle, et Cartridge configure les rÎles lancés en conséquence.

Tarantool Cartridge : sharding du backend Lua en trois lignes

Il faut ĂȘtre prudent ici, car des commutations frĂ©quentes peuvent conduire Ă  des conflits de donnĂ©es lors de la rĂ©plication. Activer le failover automatique de maniĂšre alĂ©atoire n'est bien sĂ»r pas conseillĂ©. Il est essentiel de comprendre ce qui se passe et de s’assurer que la rĂ©plication ne Ă©chouera pas aprĂšs que le leader se soit rĂ©tabli et qu'il ait rĂ©cupĂ©rĂ© sa couronne.

Tout ce qui a Ă©tĂ© dit peut donner l'impression que les rĂŽles ressemblent Ă  des microservices. En un sens, ils en sont, mais en tant que modules au sein des processus de Tarantool. Cependant, il existe plusieurs diffĂ©rences fondamentales. Tout d'abord, tous les rĂŽles du projet doivent vivre dans une seule base de code. Et tous les processus Tarantool doivent ĂȘtre lancĂ©s Ă  partir d'une seule base de code pour Ă©viter des surprises telles que celles qui se produisent lorsque nous tentons d'initialiser un scheduler, mais qu'il est tout simplement absent. Il est Ă©galement essentiel de ne pas permettre des diffĂ©rences dans les versions du code, car le comportement du systĂšme dans cette situation est trĂšs difficile Ă  prĂ©dire et Ă  dĂ©boguer.

Contrairement Ă  Docker, nous ne pouvons pas simplement prendre une "image" de rĂŽle, l'emporter sur une autre machine et lĂ -la dĂ©marrer. Nos rĂŽles ne sont pas aussi isolĂ©s que des conteneurs Docker. De plus, nous ne pouvons pas exĂ©cuter deux rĂŽles identiques sur une mĂȘme instance. Un rĂŽle existe soit, soit il n'existe pas ; en quelque sorte, c'est un singleton. Enfin, dans tout le groupe de rĂ©plication, les rĂŽles doivent ĂȘtre identiques, sinon ce serait absurde : les donnĂ©es sont identiques, mais la configuration est diffĂ©rente.

Outils de déploiement

J'ai promis de montrer comment Cartridge aide à déployer des applications. Pour faciliter la vie des autres, le framework emballe des packages RPM :

$ cartridge pack rpm myapp -- emballera pour nous .\/myapp-0.1.0-1.rpm
$ sudo yum install .\/myapp-0.1.0-1.rpm

Le package installĂ© contient presque tout le nĂ©cessaire : l'application et ses dĂ©pendances Lua installĂ©es. Tarantool sera Ă©galement installĂ© sur le serveur comme dĂ©pendance du package RPM, et notre service sera prĂȘt Ă  ĂȘtre lancĂ©. Cela se fait via systemd, mais il faut d'abord Ă©crire un peu de configuration. Au minimum, il faut indiquer l'URI de chaque processus. Trois exemples suffiront.

$ 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

Il y a ici un dĂ©tail intĂ©ressant. Au lieu d'indiquer simplement le port du protocole binaire, nous indiquons l'adresse publique du processus dans son intĂ©gralitĂ©, y compris le nom d'hĂŽte. Cela est nĂ©cessaire pour que les nƓuds du cluster sachent comment se connecter les uns aux autres. C'est une mauvaise idĂ©e d'utiliser l'adresse 0.0.0.0 comme advertise_uri, cela doit ĂȘtre une adresse IP externe, pas l'adresse de socket de liaison. Sans cela, rien ne fonctionnera, donc Cartridge ne permettra tout simplement pas de dĂ©marrer un nƓud avec un advertise_uri incorrect.

Maintenant que la configuration est prĂȘte, nous pouvons lancer les processus. Étant donnĂ© qu'une unitĂ© systemd normale ne permet pas de dĂ©marrer plus d'un processus, les applications sur Cartridge installent des unitĂ©s dites instanciĂ©es, qui fonctionnent ainsi :

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

Dans la configuration, nous avons spĂ©cifiĂ© le port HTTP que Cartridge utilise pour gĂ©rer l'interface web — 8080. AccĂ©dons-y et voyons :

Tarantool Cartridge : sharding du backend Lua en trois lignes

Nous constatons que les processus sont lancĂ©s mais pas encore configurĂ©s. Le cartouche ne sait pas encore qui doit se rĂ©pliquer avec qui et ne peut pas prendre de dĂ©cision par lui-mĂȘme, il attend donc nos actions. Nous avons peu de choix : la vie d'un nouveau cluster commence par la configuration du premier nƓud. Ensuite, nous ajouterons les autres au cluster, leur attribuerons des rĂŽles, et Ă  ce stade, le dĂ©ploiement pourra ĂȘtre considĂ©rĂ© comme achevĂ©.

Servons-nous une tasse de notre boisson prĂ©fĂ©rĂ©e et dĂ©tendons-nous aprĂšs une longue semaine de travail. L'application peut ĂȘtre exploitĂ©e.

Tarantool Cartridge : sharding du backend Lua en trois lignes

Résultats

Alors, quels sont les résultats ? Essayez, utilisez, laissez votre retour, ouvrez des tickets sur GitHub.

Liens

[1] Tarantool » 2.2 » Référence » Référence des Rocks » Module vshard

[2] Comment nous avons intégré le noyau de l'activité d'investissement d'Alfa-Bank sur la base de Tarantool

[3] Architecture de facturation de nouvelle génération : transformation avec le passage à Tarantool

[4] SWIM — protocole de construction de cluster

[5] GitHub — tarantool/cartridge-cli

[6] GitHub — tarantool/cartridge

Source : habr.com

Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS đŸ”„ Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster