Cartouche Tarantool: déchiquetage du backend Lua en trois lignes



Chez Mail.ru Group, nous avons Tarantool - c'est un tel serveur d'applications sur Lua, qui a également une base de données (ou vice versa?). C'est rapide et cool, mais les capacités d'un serveur ne sont toujours pas illimitées. La mise à l'échelle verticale n'est pas non plus une panacée, donc Tarantool dispose d'outils pour la mise à l'échelle horizontale - le module vshard [1] . Il vous permet de partager des données sur plusieurs serveurs, mais vous devez le bricoler pour le configurer et fixer la logique métier.

Bonne nouvelle: nous avons collecté les cônes (par exemple [2] , [3] ) et scié un autre framework qui simplifiera considérablement la solution à ce problème.

Tarantool Cartridge est un nouveau cadre de développement de systèmes distribués complexes. Il vous permet de vous concentrer sur l'écriture de la logique métier au lieu de résoudre les problèmes d'infrastructure. Sous la coupe, je vais vous dire comment ce cadre est organisé et comment écrire des services distribués avec.

Et quel est en fait le problème?


Nous avons une tarentule, il y a du vshard - que demander de plus?

Tout d'abord, le point est la commodité. La configuration Vshard est configurée via les tables Lua. Pour qu'un système distribué de plusieurs processus Tarantool fonctionne correctement, la configuration doit être la même partout. Personne ne veut le faire manuellement. Par conséquent, toutes sortes de scripts, Ansible, des systèmes de déploiement sont utilisés.

La cartouche elle-même gère la configuration vshard; elle le fait sur la base de sa propre configuration distribuée . Il s'agit essentiellement d'un simple fichier YAML, dont une copie est stockée dans chaque instance de Tarantool. La simplification réside dans le fait que le framework lui-même surveille sa configuration et qu'il en soit de même partout.

Deuxièmement, le point est de nouveau pratique. La configuration n'a aucun rapport avec le développement de la logique métier et distrait uniquement le programmeur du travail. Lorsque nous discutons de l'architecture d'un projet, nous parlons le plus souvent de composants individuels et de leur interaction. Il est trop tôt pour penser à déployer un cluster dans 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 pour simplifier le travail avec l'application tout au long de son cycle de vie: création, développement, test, CI / CD, maintenance.

La cartouche présente le concept de rôle pour chaque processus Tarantool. Les rôles sont un concept qui permet au développeur de se concentrer sur l'écriture de code. Tous les rôles disponibles dans le projet peuvent être exécutés sur une seule instance de Tarantool, et cela suffira pour les tests.

Caractéristiques principales de la cartouche Tarantool:

  • orchestration automatisĂ©e des clusters;
  • Ă©tendre les fonctionnalitĂ©s de l'application avec de nouveaux rĂ´les
  • modèle de dĂ©veloppement et de dĂ©ploiement d'applications;
  • sharding automatique intĂ©grĂ©;
  • intĂ©gration avec le framework de test Luatest;
  • gestion de cluster Ă  l'aide de WebUI et API;
  • outils de packaging et de dĂ©ploiement.

Bonjour tout le monde!


Je suis impatient de montrer le cadre lui-même, alors laissons l'histoire de l'architecture pour plus tard, et commençons par une simple. En supposant que Tarantool lui-même est déjà installé, tout ce qui reste à faire est

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

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

 $ cartridge create --name myapp 

Et voici ce que nous obtenons:

 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/ 

Ceci est un référentiel git avec le "Hello, World!" Terminé application. Essayons immédiatement de l'exécuter, en préinstallant les dépendances (y compris le framework lui-même):

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

Nous avons donc lancé un nœud de la future application fragmentée. Un profane curieux peut immédiatement ouvrir l'interface Web, utiliser la souris pour configurer un cluster à partir d'un nœud et profiter du résultat, mais il est trop tôt pour se réjouir. Jusqu'à présent, l'application ne sait rien faire d'utile, je vous parlerai donc de déploiement plus tard, et maintenant il est temps d'écrire du code.

Développement d'applications


Imaginez, nous allons concevoir un projet qui devrait recevoir des données, les enregistrer et créer un rapport une fois par jour.



Nous commençons à dessiner un diagramme et à y placer trois composants: passerelle, stockage et ordonnanceur. Nous travaillons davantage sur l'architecture. Puisque nous utilisons vshard comme stockage, nous ajoutons vshard-router et vshard-storage au schéma. Ni la passerelle ni l'ordonnanceur n'accéderont directement au référentiel, il y a un routeur pour ça, il a été créé pour ça.



Ce schéma ne reflète toujours pas exactement ce que nous allons créer dans le projet, car les composants semblent abstraits. Nous devons également voir comment cela est projeté sur un véritable Tarantool - nous allons regrouper nos composants par processus.



Garder vshard-router et gateway sur des instances distinctes n'a pas beaucoup de sens. Pourquoi devons-nous à nouveau parcourir le réseau, si c'est déjà la responsabilité du routeur? Ils doivent être exécutés dans le même processus. Autrement dit, dans le même processus, la passerelle et vshard.router.cfg sont initialisés et les laissent interagir localement.

C'était pratique de travailler avec trois composants au stade de la conception, mais en tant que développeur, pendant que j'écris du code, je ne veux pas penser à lancer trois instances de Tarnatool. Je dois exécuter des tests et vérifier que j'ai correctement écrit la passerelle. Ou peut-être que je veux démontrer une fonctionnalité à mes collègues. Pourquoi devrais-je souffrir avec le déploiement de trois copies? C'est ainsi qu'est né le concept de rôles. Un rôle est un module Loach régulier 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 peut y en avoir plus. Tous les rôles peuvent être lancés en un seul processus, et cela suffira.



Et quand il s'agit de déployer en staging ou en opération, nous assignerons chaque ensemble de rôles à chaque processus Tarantool, en fonction des capacités matérielles:



Gestion de la topologie


Les informations sur l'endroit où les rôles sont lancés doivent être stockées quelque part. Et ce «quelque part» est la configuration distribuée que j'ai mentionnée ci-dessus. La chose la plus importante est la topologie de cluster. Voici 3 groupes de réplication de 5 processus Tarantool:



Nous ne voulons pas perdre de données, nous traitons donc soigneusement les informations sur les processus en cours d'exécution. La cartouche surveille la configuration avec 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 disponibilité à accepter la nouvelle configuration. Après cela, la deuxième phase applique la configuration. Ainsi, même si une instance était temporairement indisponible, rien de terrible ne se produira. La configuration ne s'appliquera tout simplement pas et vous verrez une erreur à l'avance.

Dans la section topologie est également indiqué un paramètre aussi important que le leader de chaque groupe de réplication. Il s'agit généralement de l'instance qui est enregistrée. Les autres sont le plus souvent en lecture seule, bien qu'il puisse y avoir des exceptions. Parfois, les développeurs courageux n'ont pas peur des conflits et peuvent écrire des données sur plusieurs répliques en parallèle, mais il y a certaines opérations qui, malgré tout, ne devraient pas être effectuées deux fois. Il y a un signe d'un leader pour cela.



Vie de rĂ´le


Pour qu'un rôle abstrait existe dans une telle architecture, le cadre doit en quelque sorte les gérer. Naturellement, le contrôle s'effectue sans redémarrer le processus Tarantool. Il existe 4 rappels pour la gestion des rôles. La cartouche elle-même les appellera en fonction de ce qu'elle dit dans une 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 a une fonction init . Il est appelé une fois, soit lorsque le rôle est activé, soit lorsque Tarantool redémarre. Il est pratique, par exemple, d'initialiser box.space.create, ou le planificateur peut démarrer une fibre d'arrière-plan, qui fera le travail à certains intervalles.

La fonction init peut ne pas suffire. La cartouche permet aux rôles de tirer parti de la configuration distribuée qu'elle utilise pour stocker la topologie. Dans la même configuration, nous pouvons déclarer une nouvelle section et y stocker un fragment de la 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 de planificateur.

Le cluster appelle validate_config et apply_config chaque fois que la configuration distribuée change. Lorsqu'une configuration est appliquée par une validation en deux phases, le cluster vérifie que chaque rôle est prêt à accepter cette nouvelle configuration et, si nécessaire, signale une erreur à l'utilisateur. Lorsque tout le monde a convenu que la configuration est normale, apply_config est apply_config .

Les rôles ont également une méthode d' stop , qui est nécessaire pour effacer les signes vitaux du rôle. Si nous disons que le planificateur sur ce serveur n'est plus nécessaire, il peut arrêter les fibres qu'il a démarrées avec init .

Les rôles peuvent interagir les uns avec les autres. Nous avons l'habitude d'écrire des appels de fonction en Lua, mais il se peut que nous n'ayons pas le rôle dont nous avons besoin dans ce processus. Pour faciliter l'accès au réseau, nous utilisons le module auxiliaire rpc (appel de procédure à distance), qui est construit sur la base de la netbox standard intégrée à Tarantool. Cela peut être utile si, par exemple, votre passerelle souhaite demander directement au planificateur de faire le travail maintenant, plutôt que d'attendre un jour.

Un autre point important est d'assurer la tolérance aux pannes. La cartouche utilise le protocole SWIM [4] pour surveiller la santé. En bref, les processus échangent des «rumeurs» via UDP - chaque processus informe ses voisins des dernières nouvelles et ils répondent. Si la réponse ne vient pas, Tarantool commence à soupçonner que quelque chose ne va pas, et après un certain temps, il récite la mort et commence à dire à tout le monde autour de cette nouvelle.



Sur la base de ce protocole, Cartridge organise le basculement automatique. Chaque processus surveille son environnement et si le leader cesse soudainement de répondre, la réplique peut jouer son rôle sur elle-même et Cartridge configurera les rôles en cours d'exécution en conséquence.



Vous devez être prudent ici, car des allers-retours fréquents peuvent entraîner des conflits de données lors de la réplication. Activer le basculement automatique au hasard, bien sûr, n'en vaut pas la peine. Vous devez comprendre clairement ce qui se passe et être sûr que la réplication ne se cassera pas après que le leader se rétablit et que la couronne lui soit rendue.

De tout ce qui a été dit, il peut sembler que les rôles sont similaires aux microservices. Dans un sens, ils ne le sont qu'en tant que modules dans les processus Tarantool. Mais il existe un certain nombre de différences fondamentales. Tout d'abord, tous les rôles de 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, afin qu'il n'y ait pas de surprises comme celles-ci lorsque nous essayons d'initialiser le planificateur, mais ce n'est tout simplement pas le cas. En outre, n'autorisez pas les différences dans les versions du code, car le comportement du système dans une telle situation est très difficile à prévoir et à déboguer.

Contrairement à Docker, nous ne pouvons pas simplement prendre «l'image» d'un rôle, le transférer sur une autre machine et l'exécuter là-bas. Nos rôles ne sont pas aussi isolés que les conteneurs Docker. De plus, nous ne pouvons pas exécuter deux rôles identiques sur la même instance. Le rôle est là ou pas, en un sens c'est singleton. Et troisièmement, les rôles devraient être les mêmes au sein de l'ensemble du groupe de réplication, car sinon ce serait ridicule - les données sont les mêmes et la configuration est différente.

Outils de déploiement


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

 $ cartridge pack rpm myapp #    ./myapp-0.1.0-1.rpm $ sudo yum install ./myapp-0.1.0-1.rpm 

Le package installé contient presque tout ce dont vous avez besoin: à la fois l'application et les dépendances de lancement installées. Tarantool arrivera également sur le serveur en tant que dépendance de package RPM, et notre service est prêt à être lancé. Cela se fait via systemd, mais vous devez d'abord écrire une petite configuration. Au minimum, spécifiez l'URI de chaque processus. Trois par exemple suffit.

 $ 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 une nuance intéressante ici. Au lieu de spécifier uniquement le port du protocole binaire, nous spécifions l'adresse publique de l'ensemble du processus, 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, ce devrait être une adresse IP externe, pas un socket de liaison. Sans cela, rien ne fonctionnera, donc Cartridge ne laissera tout simplement pas le nœud avec le mauvais advertise_uri démarrer.

Maintenant que la configuration est prête, vous pouvez démarrer les processus. Étant donné qu'une unité systemd standard ne permet pas de démarrer plus d'un processus, les applications sur la cartouche installent ce que l'on appelle unités instanciées qui fonctionnent comme ceci:

 $ 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 sur lequel Cartridge sert l'interface Web - 8080. Passons en revue et voyons:



Nous voyons que les processus, bien qu'ils soient en cours d'exécution, ne sont pas encore configurés. La cartouche ne sait pas encore qui doit se répliquer avec qui et ne peut pas décider seule, elle attend donc notre action. Et notre choix n'est pas grand: la vie d'un nouveau cluster commence par la configuration du premier nœud. Ensuite, nous ajoutons le reste au cluster, nous leur attribuons des rôles, et ce déploiement peut être considéré comme terminé avec succès.

Versez un verre de votre boisson préférée et détendez-vous après une longue semaine de travail. L'application peut être exploitée.



Résumé


Et quels résultats? Essayez, utilisez, laissez des commentaires, démarrez des tickets sur le github.

Les références


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

[2] Comment nous avons mis en œuvre le cœur de métier d'investissement d'Alfa-Bank basé sur Tarantool

[3] Architecture de facturation de nouvelle génération: transition vers Tarantool

[4] SWIM - protocole de construction de cluster

[5] GitHub - tarantool / cartouche-cli

[6] GitHub - tarantool / cartouche

Source: https://habr.com/ru/post/fr465503/


All Articles