
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. . 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 , ) et avons développé un nouveau framework qui simplifie considérablement cette problématique.
â 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/:$PATHCes 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 myappEt 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 8080Ainsi, 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.

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.

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.

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.

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 :

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 :

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.

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. . 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.

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.

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.rpmLe 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}
CONFIGIl 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_BDans la configuration, nous avons spĂ©cifiĂ© le port HTTP que Cartridge utilise pour gĂ©rer l'interface web â 8080. AccĂ©dons-y et voyons :

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.

Résultats
Alors, quels sont les résultats ? Essayez, utilisez, laissez votre retour, ouvrez des tickets sur GitHub.
Liens
[1]
[2]
[3]
[4]
[5]
[6]
Source : habr.com
