Service de cache intelligent basé sur ZeroMQ et Tarantool

Ruslan Aromatov, développeur en chef, ICD



Bonjour, Habr! Je travaille en tant que développeur backend à la Banque de crédit de Moscou, et au cours de mon travail, j'ai acquis une expérience que je voudrais partager avec la communauté. Aujourd'hui, je vais vous dire comment nous avons écrit notre propre service de cache pour les serveurs frontaux de nos clients à l'aide de l'application mobile MKB Online. Cet article peut être utile à ceux qui sont impliqués dans la conception de services et qui connaissent l'architecture des microservices, la base de données en mémoire Tarantool et la bibliothèque ZeroMQ. Dans l'article, il n'y aura pratiquement aucun exemple de code et d'explication des bases, mais seulement une description de la logique des services et de leur interaction avec un exemple spécifique qui travaille sur notre bataille depuis plus de deux ans.

Comment tout a commencé


Il y a environ 6 ans, le schéma était simple. En tant qu'héritage de la société d'externalisation, nous avons obtenu deux clients de services bancaires mobiles pour iOS et Android, ainsi qu'un serveur frontal qui les dessert. Le serveur lui-même a été écrit en java, s'est rendu sur son backend de différentes manières (principalement soap) et a communiqué avec les clients en transmettant du xml via https.

Les applications clientes ont pu s'authentifier d'une manière ou d'une autre, afficher une liste de produits et ... elles semblaient être en mesure d'effectuer certains transferts et paiements, mais en fait, elles ne l'ont pas très bien fait et pas toujours. Par conséquent, le serveur frontal n'a connu ni un grand nombre d'utilisateurs ni aucune charge sérieuse (ce qui, cependant, ne l'a pas empêché de tomber environ une fois tous les deux jours).

Il est clair que nous (et à l'époque notre équipe était composée de quatre personnes), en tant que responsables de la banque mobile, ne convenions pas à cette situation, et pour commencer, nous avons mis en ordre les applications actuelles, mais le serveur frontal s'est avéré être vraiment mauvais, il fallait donc réécrivez rapidement l'ensemble, en remplaçant simultanément xml par json et en passant au serveur d'applications WildFly . Réparti sur quelques années, le refactoring ne s'appuie pas sur un poste séparé, car tout a été fait principalement pour s'assurer que le système fonctionne de manière stable.

Peu à peu, les applications et le serveur se sont développés, ont commencé à fonctionner de manière plus stable, et leurs fonctionnalités étaient en constante expansion, ce qui a payé - il y avait de plus en plus d'utilisateurs.

Dans le même temps, des problèmes tels que la tolérance aux pannes, la redondance, la réplication et - effrayant de penser - la surcharge ont commencé à apparaître.

Une solution rapide au problème a été d'ajouter un deuxième serveur WildFly et les applications ont appris à basculer entre eux. Le problème du travail simultané avec des sessions client a été résolu par le module Infinispan intégré à WildFly.

Comme avant

Il semblait que la vie s'améliorait ...

Tu ne peux pas vivre comme ça


Cependant, cette option de travailler avec des sessions n'était pas sans inconvénients. Je citerai ceux qui ne nous convenaient pas.

  1. Perte de sessions. Le moins le plus important. Par exemple, une application envoie deux demandes au serveur-1: la première demande est l'authentification et la seconde est une demande de liste de comptes. L'authentification a réussi, une session est créée sur le serveur-1. À ce moment, la deuxième demande du client s'arrête soudainement en raison d'une mauvaise communication et l'application bascule sur le serveur-2, renvoyant le transfert de la deuxième demande. Mais à une certaine charge de travail, Infinispan peut ne pas avoir le temps de synchroniser les données entre les nœuds. En conséquence, le serveur-2 ne peut pas vérifier la session client, envoie une réponse en colère au client, le client est triste et termine sa session. L'utilisateur doit se reconnecter. Triste
  2. Le redémarrage du serveur peut également entraîner la perte de sessions. Par exemple, après une mise à jour (et cela arrive assez souvent). Lorsque le serveur-2 démarre, il ne peut pas fonctionner tant que les données ne sont pas synchronisées avec le serveur-1. Il semble que le serveur ait démarré, mais ne devrait en fait pas accepter les demandes. C'est gênant.
  3. Il s'agit d'un module WildFly intégré qui nous empêche de nous éloigner de ce serveur d'applications vers des microservices.

De là, une liste de ce que nous aimerions a été en quelque sorte formée par elle-même.

  1. Nous voulons stocker les sessions client afin que tout serveur (quel que soit leur nombre) immédiatement après le lancement y ait accès.
  2. Nous voulons stocker toutes les données client entre les demandes (par exemple, les paramètres de paiement et tout cela).
  3. Nous voulons enregistrer toutes les données arbitraires sur une clé arbitraire en général.
  4. Et nous voulons également recevoir les données des clients avant que l'authentification ne passe. Par exemple, l'utilisateur est authentifié et tous ses produits sont là, frais et chaleureux.
  5. Et nous voulons évoluer en fonction de la charge.
  6. Et exécutez dans le menu fixe, et écrivez les journaux sur une seule pile, et comptez les métriques, etc.
  7. Oh oui, et pour que tout fonctionne rapidement.

Farine de choix


Auparavant, nous n'avions pas implémenté l'architecture de microservices, donc pour commencer nous nous sommes assis pour lire, regarder et essayer différentes options. Il était clair tout de suite que nous avions besoin d'un référentiel rapide et d'une sorte de module complémentaire qui traite de la logique métier et constitue l'interface d'accès au référentiel. De plus, il serait intéressant de fixer un transport rapide entre les services.

Ils ont choisi pendant longtemps, se sont beaucoup disputés et ont expérimenté. Je ne décrirai pas maintenant les avantages et les inconvénients de tous les candidats, cela ne s'applique pas au sujet de cet article, je dis simplement que le stockage sera tarantool , nous écrirons notre service en java et ZeroMQ fonctionnera comme un transport. Je ne dirai même pas que le choix est très ambigu, mais il a été largement influencé par le fait que nous n'aimons pas les différents cadres gros et lourds (pour leur poids et leur lenteur), les solutions en boîte (pour leur polyvalence et leur manque de personnalisation), mais en même temps Nous aimons contrôler autant que possible toutes les parties de notre système. Et pour contrôler le travail des services, nous avons choisi le serveur de collecte de métriques Prometheus avec ses agents pratiques qui peuvent être intégrés dans presque n'importe quel code. Les journaux de tout cela iront dans la pile ELK.

Eh bien, il me semble qu'il y avait déjà trop de théorie.

Début et fin


Le résultat de conception était approximativement un tel schéma.

Comment voulons-nous

Stockage

Il devrait être aussi stupide que possible, uniquement pour stocker des données et leurs états actuels, mais toujours fonctionner sans redémarrage. Conçu pour servir différentes versions de serveurs frontaux. Nous conservons toutes les données en mémoire, récupération en cas de redémarrage via les fichiers .snap et .xlog.

Table (espace) pour les sessions client:

  • ID de session
  • ID client;
  • version (service)
  • heure de mise à jour (horodatage);
  • durée de vie (ttl);
  • données de session sérialisées.

Ici, tout est simple: le client est authentifié, le serveur frontal crée une session et l'enregistre dans le stockage, en se souvenant de l'heure. À chaque demande de données, l'heure est mise à jour, de sorte que la session est maintenue en vie. Si, sur demande, les données s'avèrent obsolètes (ou qu'il n'y en aura pas du tout), nous retournerons un code de retour spécial, après quoi le client mettra fin à sa session.

Table de cache simple (pour toutes les données de session):

  • clé;
  • ID de session
  • type de données stockées (nombre arbitraire);
  • heure de mise à jour (horodatage);
  • durée de vie (ttl);
  • données sérialisées.

Tableau des données client à réchauffer avant la connexion:
  • ID client;
  • ID de session
  • version (service)
  • type de données stockées (nombre arbitraire);
  • heure de mise à jour (horodatage);
  • état;
  • données sérialisées.

Un domaine important ici est la condition. En fait, il n'y en a que deux - inactif et à jour. Ils sont placés par un service sous-jacent qui va au backend pour les données client afin qu'une autre instance de ce service ne fasse pas le même travail (déjà inutile) et ne charge pas le backend.

Tableau des périphériques:

  • ID client;
  • ID de l'appareil
  • heure de mise à jour (horodatage);

La table des appareils est nécessaire pour que, avant même que le client s'authentifie dans le système, connaisse son ID et commence à recevoir ses produits (réchauffement du cache). La logique est la suivante: la première entrée est toujours froide, car avant l'authentification, nous ne savons pas quel type de client provient d'un appareil inconnu (les clients mobiles transmettent toujours des ID d'appareil dans toutes les demandes). Toutes les entrées suivantes de cet appareil seront accompagnées d'un cache de préchauffage pour le client qui lui est associé.

Le travail avec les données est isolé du service java par les procédures du serveur. Oui, j'ai dû apprendre le lua, mais cela n'a pas pris beaucoup de temps. En plus de la gestion des données elle-même, les lua-procedures sont également responsables du retour des états actuels, des sélections d'index, du nettoyage des enregistrements obsolètes dans les processus d'arrière-plan (fibres) et du fonctionnement du serveur Web intégré via lequel un accès direct aux données est effectué. Le voici - le charme d'écrire tout avec vos mains - la possibilité d'un contrôle illimité. Mais le moins est le même - vous devez tout écrire vous-même.

Tarantool lui-même fonctionne dans un conteneur docker, tous les fichiers lua nécessaires y sont placés au stade de l'assemblage de l'image. L'ensemble de l'assemblage à travers des scripts gradle.

Réplication maître-esclave. Sur l'autre hôte, le même conteneur s'exécute exactement comme la réplique du stockage principal. Il est nécessaire en cas de panne d'urgence du maître - puis les services java passent en esclave, et il devient le maître. Il y a un troisième esclave au cas où. Cependant, même une perte de données complète dans notre cas est triste, mais pas fatale. Selon le pire des cas, les utilisateurs devront se connecter et récupérer toutes les données qui vont à nouveau dans le cache.

Service Java

Conçu comme un microservice sans état typique. Il n'a pas de configuration, tous les paramètres nécessaires (et il y en a 6) sont passés par les variables d'environnement lors de la création du conteneur Docker. Il fonctionne avec le serveur frontal via le transport ZeroMQ (org.zeromq.jzmq - l'interface java du libzmq.so.5.1.1 natif, que nous avons nous-mêmes construit) en utilisant notre propre protocole. Il fonctionne avec une tarentule via un connecteur java (org.tarantool.connector).

L'initialisation du service est assez simple:

  • Nous démarrons un enregistreur (log4j2);
  • A partir des variables d'environnement (nous sommes dans le docker) nous lisons les paramètres nécessaires au travail;
  • Nous démarrons le serveur de métriques (jetée);
  • Connectez-vous à la tarentule (de manière asynchrone);
  • Nous démarrons le nombre requis de gestionnaires de threads (travailleurs);
  • Nous commençons un courtier (zmq) - un cycle de traitement de messages sans fin.

De tout ce qui précède, seul le moteur de traitement des messages est intéressant. Voici un schéma du microservice.

Logique du courtier de messages

Commençons par le début du courtier. Notre courtier est un ensemble de sockets zmq de type ROUTER, qui accepte les connexions de divers clients et est responsable de la planification des messages provenant d'eux.

Dans notre cas, nous avons une socket d'écoute sur l'interface externe qui reçoit des messages des clients utilisant le protocole tcp et l'autre reçoit des messages des threads de travail utilisant le protocole inproc (c'est beaucoup plus rapide que tcp).

/** //   (   ,   ) ZContext zctx = new ZContext(); //    ZMQ.Socket clientServicePoint = zctx.createSocket(ZMQ.ROUTER); //    ZMQ.Socket workerServicePoint= zctx.createSocket(ZMQ.ROUTER); //     clientServicePoint.bind("tcp://*:" + Config.ZMQ_LISTEN_PORT); //     workerServicePoint.bind("inproc://worker-proc"); 

Après avoir initialisé les sockets, nous commençons une boucle d'événements sans fin.

 /** *      */ public int run() { int status;  try {   ZMQ.Poller poller = new ZMQ.Poller(2);    poller.register(workerServicePoint, ZMQ.Poller.POLLIN);    poller.register(clientServicePoint, ZMQ.Poller.POLLIN);    int rc;    while (true) {      //        rc = poller.poll(POLL_INTERVAL);      if (rc == -1) {        status = -1;        logger.errorInternal("Broker run error rc = -1");        break; //  -     }    //     ()    if (poller.pollin(0)) {       processBackendMessage(ZMsg.recvMsg(workerServicePoint));    }    //        if (poller.pollin(1)) {       processFrontendMessage(ZMsg.recvMsg(clientServicePoint));    }    processQueueForBackend(); }  } catch (Exception e) {    status = -1;  } finally {    clientServicePoint.close();    workerServicePoint.close();  }  return status; } 

La logique du travail est très simple: nous recevons des messages de différents endroits et faisons quelque chose avec eux. En cas de rupture critique avec nous, nous quittons la boucle, ce qui provoque le crash du processus, qui sera automatiquement redémarré par le démon docker.

L'idée principale est que le courtier ne gère aucune logique métier, il analyse uniquement l'en-tête du message et distribue les tâches aux threads de travail qui ont été lancés plus tôt au démarrage du service. En cela, une seule file d'attente de messages avec hiérarchisation d'une longueur fixe l'aide.

Analysons l'algorithme en utilisant l'exemple du schéma et du code ci-dessus.

Après le démarrage, les threads qui ont démarré après le courtier sont initialisés et envoient un message de préparation au courtier. Le courtier les accepte, les analyse et ajoute chaque travailleur à la liste.

Un événement se produit sur le socket client - nous avons reçu le message 1. Le courtier appelle le gestionnaire de messages entrants, dont la tâche est:

  • analyse de l'en-tête du message;
  • placer un message dans un objet titulaire avec une priorité donnée (basée sur l'analyse d'en-tête) et une durée de vie;
  • placer le titulaire dans la file d'attente de messages;
  • si la file d'attente n'est pas pleine, la tâche du gestionnaire est terminée;
  • si la file d'attente est pleine, nous appelons la méthode pour envoyer un message d'erreur au client.

Dans la même itération de la boucle, nous appelons le gestionnaire de file d'attente de messages:

  • nous demandons le message le plus récent à la file d'attente (la file d'attente décide de lui-même en fonction de la priorité et de l'ordre d'ajout du message);
  • vérifier la durée de vie du message (s'il a expiré, appelez la méthode pour envoyer un message d'erreur au client);
  • si le message à traiter est pertinent, essayez de préparer le premier travailleur libre à travailler;
  • s'il n'y en a pas, remettez le message dans la file d'attente (plus précisément, ne le supprimez pas de là, il y restera jusqu'à l'expiration de sa durée de vie);
  • si nous avons un travailleur prêt à travailler, nous le marquons comme occupé et lui envoyons un message pour traitement;
  • supprimez le message de la file d'attente.

Nous faisons de même avec tous les messages suivants. Le thread thread lui-même est conçu de la même manière qu'un courtier - il a le même cycle de traitement de messages sans fin. Mais en elle, nous n'avons plus besoin d'un traitement instantané, il est conçu pour effectuer de longues tâches.

Une fois que le travailleur a terminé sa tâche (par exemple, est allé au backend pour les produits du client ou dans la tarentule pour la session), il envoie un message au courtier, que le courtier renvoie au client. L'adresse du client à qui la réponse doit être envoyée est mémorisée dès l'instant où le message arrive du client dans l'objet titulaire, qui est envoyé au travailleur sous forme de message dans un format légèrement différent, puis revient.

Le format des messages que je mentionne constamment est notre propre production. Hors de la boîte, ZeroMQ nous fournit les classes ZMsg - le message lui-même et ZFrame - une partie intégrante de ce message, essentiellement juste un tableau d'octets, que je suis libre d'utiliser si je le souhaite. Notre message se compose de deux parties (deux ZFrames), dont la première est un en-tête binaire et la seconde est des données (le corps de la demande, par exemple, sous la forme d'une chaîne json représentée par un tableau d'octets). L'en-tête du message est universel et se déplace à la fois du client au serveur et du serveur au client.

En fait, nous n'avons pas le concept de «demande» ou de «réponse», seulement des messages. L'en-tête contient: version du protocole, type de système (auquel système est adressé), type de message, code d'erreur au niveau du transport (si ce n'est pas 0, quelque chose s'est produit dans le moteur de transfert de message), ID de la demande (identifiant de transfert provenant du client - nécessaire pour le traçage), l'ID de session client (facultatif), ainsi qu'un signe d'erreur de niveau de données (par exemple, si la réponse du backend n'a pas pu être analysée, nous définissons ce drapeau pour que l'analyseur côté client ne désérialise pas la réponse, mais reçoive des données d'erreur d'une autre manière).

Grâce à un protocole unique entre tous les microservices et un tel en-tête, nous pouvons tout simplement manipuler les composants de nos services. Par exemple, vous pouvez intégrer le courtier dans un processus distinct et en faire un courtier de messages unique au niveau de l'ensemble du système de microservices. Ou, par exemple, exécutez les travailleurs non pas sous la forme de threads à l'intérieur du processus, mais en tant que processus indépendants distincts. Et tandis que le code à l'intérieur d'eux ne change pas. En général, il y a place pour la créativité.

Un peu sur les performances et les ressources


Le courtier lui-même est rapide et la bande passante totale du service est limitée par la vitesse du backend et le nombre de travailleurs. De manière pratique, toute la quantité de mémoire nécessaire est allouée immédiatement au démarrage du service et tous les threads sont immédiatement démarrés. La taille de la file d'attente est également fixe. Lors de l'exécution, seuls les messages sont en cours de traitement.

À titre d'exemple: en plus du thread principal, notre service de combat de cache actuel lance 100 autres threads de travail et la taille de la file d'attente est limitée à trois mille messages. En fonctionnement normal, chaque instance traite jusqu'à 200 messages par seconde et consomme environ 250 Mo de mémoire et environ 2 à 3% du processeur. Parfois, aux charges de pointe, il passe à 7-8%. Tout cela fonctionne sur une sorte de xeon virtuel dual-core.

Le travail régulier du service implique l'emploi simultané de 3 à 5 travailleurs (sur 100) avec le nombre de messages dans la file d'attente 0 (c'est-à-dire qu'ils sont immédiatement traités). Si le backend commence à ralentir, le nombre de travailleurs occupés augmente proportionnellement au temps de sa réponse. Dans les cas où un accident se produit et que le backend monte, tous les travailleurs se terminent d'abord, après quoi la file d'attente de messages commence à se boucher. Lorsqu'il se bouche complètement, nous commençons à répondre aux clients qui refusent de traiter. Dans le même temps, nous ne commençons pas à consommer de la mémoire ou des ressources CPU, en fournissant de manière stable des mesures et en répondant clairement aux clients ce qui se passe.

La première capture d'écran montre le fonctionnement régulier du service.

Le travail régulier du service

Et sur le second, un accident s'est produit - le backend pour une raison quelconque n'a pas répondu en 30 secondes. On voit qu'au début, tous les travailleurs étaient épuisés, après quoi la file d'attente de messages a commencé à se boucher.

Accident

Tests de performance


Les tests synthétiques sur ma machine de travail (CentOS 7, Core i5, 16 Go de RAM) ont montré ce qui suit.

Travailler avec le référentiel (écrire sur la tarentule et lire immédiatement cet enregistrement de 100 octets de taille - simuler le travail avec la session) - 12000 rps.

La même chose, seule la vitesse a été mesurée non pas entre le service - les points de tarentule, mais entre le client et le service. Bien sûr, j'ai dû écrire moi-même un client pour faire un test de stress. Dans une seule machine, il était possible d'obtenir 7000 rps. Sur un réseau local (et nous avons de nombreuses machines virtuelles différentes dont la connexion physique n'est pas claire), les résultats varient, mais jusqu'à 5000 rps pour une instance est tout à fait possible. Dieu sait quel genre de performance, mais il couvre plus de dix fois nos charges de pointe. Et ce n'est que si une instance du service est en cours d'exécution, mais nous en avons plusieurs, et à tout moment vous pouvez en exécuter autant que vous le souhaitez. Lorsque les services bloquent la vitesse de stockage, il sera possible de mettre à l'échelle la tarentule horizontalement (fragment basé sur l'ID client, par exemple).

Service Intelligence


Le lecteur attentif pose probablement déjà la question - quelle est «l'intelligence» de ce service, qui est mentionnée dans le titre. Je l'ai déjà mentionné en passant, mais maintenant je vais vous en dire plus.

L'une des principales tâches du service était de réduire le temps nécessaire pour émettre leurs produits aux utilisateurs (listes de comptes, cartes, dépôts, prêts, packages de services, etc.) tout en réduisant la charge sur le backend (en réduisant le nombre de demandes dans les grands et lourds Oracle) en raison de la mise en cache dans la tarentule.

Et il l'a assez bien fait. La logique de réchauffement du cache client est la suivante:

  • l'utilisateur lance l'application mobile;
  • Une demande AppStart contenant l'ID de périphérique est envoyée au serveur frontal;
  • le serveur frontal envoie un message avec cet ID au service de cache;
  • le service recherche dans la table des appareils l'ID client de cet appareil;
  • s'il n'est pas là, rien ne se passe (la réponse n'est même pas envoyée, le serveur ne l'attend pas);
  • si l'ID client est localisé, le travailleur crée un ensemble de messages pour recevoir des listes de produits utilisateur qui entrent immédiatement en traitement par le courtier et sont distribués aux travailleurs en mode normal;
  • chaque travailleur envoie une demande pour un certain type de données à l'utilisateur, mettant le statut de «mise à jour» dans la base de données (ce statut protège le backend de la répétition des mêmes requêtes si elles proviennent d'autres instances du service);
  • après réception des données, elles sont enregistrées dans la tarentule;
  • l'utilisateur se connecte au système, et l'application envoie des demandes de réception de ses produits, et le serveur envoie ces demandes sous forme de messages au service de cache;
  • si les données utilisateur ont déjà été reçues, nous les envoyons simplement depuis le cache;
  • si les données sont en cours de réception (état «mise à jour»), alors un cycle d'attente de données est démarré à l'intérieur du travailleur (il est égal au délai de requête au backend);
  • dès que les données sont reçues (c'est-à-dire que le statut de cet enregistrement (tuple) dans la table passe à "inactif", le service les remet au client;
  • si les données ne sont pas reçues dans un certain intervalle de temps, une erreur sera renvoyée au client.

Ainsi, dans la pratique, nous avons pu réduire le temps moyen de réception des produits pour le serveur frontal de 200 ms à 20 ms, soit environ 10 fois, et le nombre de demandes au backend d'environ 4 fois.

Les problèmes


Le service de cache fonctionne au combat depuis environ deux ans et répond actuellement à nos besoins.

Bien sûr, il y a encore des problèmes non résolus, parfois des problèmes surviennent. Les services Java dans la bataille ne sont pas encore tombés. La tarentule est tombée plusieurs fois sur SIGSEGV, mais c'était une ancienne version, et après la mise à jour, cela ne s'est plus produit. Pendant les tests de résistance, la réplication tombe, un tuyau cassé s'est produit sur le maître, après quoi l'esclave est tombé, bien que le maître ait continué à travailler. Il a été décidé en redémarrant l'esclave.

Une fois qu'il y a eu une sorte d'accident dans le centre de données, il s'est avéré que le système d'exploitation (CentOS 7) avait cessé de voir les disques durs. Le système de fichiers est passé en mode lecture seule. Le plus surprenant, c'est que les services ont continué de fonctionner, car nous conservons toutes les données en mémoire. La tarentule n'a pas pu écrire de fichiers .xlog, personne n'a rien enregistré, mais d'une manière ou d'une autre, tout a fonctionné. Mais la tentative de redémarrage a échoué - personne n'a pu démarrer.

Il y a un gros problème non résolu, et j'aimerais écouter l'opinion de la communauté à ce sujet. Lorsque la tarentule maître se bloque, les services java peuvent passer en esclave, qui continue de fonctionner en tant que maître. Cependant, cela ne se produit que si le maître se bloque et ne peut pas fonctionner.

Problème non résolu

Supposons que nous ayons 3 instances d'un service qui fonctionnent avec des données sur une tarentule principale. Les services eux-mêmes ne tombent pas, la réplication de la base de données est en cours, tout va bien. Mais soudain, nous avons un réseau qui se désagrège entre le nœud-1 et le nœud-4, où l'assistant fonctionne. Service-1 après un certain nombre de tentatives infructueuses décide de basculer vers la base de données de sauvegarde et commence à y envoyer des demandes.

Immédiatement après cela, l'esclave tarentule commence à accepter les demandes de modification des données, à la suite de quoi la réplication du maître s'effondre et nous obtenons des données incohérentes. Dans le même temps, les services-2 et 3 fonctionnent parfaitement avec le maître, et le service-1 communique bien avec l'ancien esclave. Il est clair que dans ce cas, nous commençons à perdre les sessions client et toutes les autres données, bien que tout fonctionne du côté technique. Nous n'avons pas encore résolu un tel problème potentiel. Heureusement, cela ne s'est pas produit depuis 2 ans, mais la situation est bien réelle. Désormais, chaque service connaît le numéro du magasin auquel il va et nous avons une alerte pour cette métrique, qui fonctionnera lors du passage du maître à l'esclave. Et vous devez tout réparer avec vos mains. Comment résolvez-vous de tels problèmes?

Plans


Nous prévoyons de travailler sur le problème décrit ci-dessus, en limitant le nombre de travailleurs simultanément occupés par un type de demande, en toute sécurité (sans perdre les demandes en cours) arrêtant le service et en poursuivant le polissage.

Conclusion


C'est peut-être tout, même si j'ai abordé le sujet plutôt superficiellement, mais la logique générale du travail doit être claire. Par conséquent, si possible, je suis prêt à répondre dans les commentaires. J'ai brièvement décrit comment un petit sous-système auxiliaire des serveurs frontaux de la banque fonctionne pour servir les clients mobiles.

Si le sujet intéresse la communauté, je peux vous parler de plusieurs de nos solutions qui contribuent à améliorer la qualité du service client de la banque.

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


All Articles