Le site Web et la boutique en ligne "Eldorado" représentent environ 40 000 achats par jour. Il n'est probablement pas nécessaire d'expliquer ce que cela signifie pour l'activité de l'entreprise.
Historiquement, le magasin fonctionne sur le moteur Bitrix avec une énorme quantité de code personnalisé et de modules complémentaires. Le stockage est un cluster MySQL avec quatre serveurs maîtres.
Un nombre important d'entreprises ont des applications monolithiques et beaucoup doivent travailler avec elles. Il existe de nombreuses façons de gérer le monolithe, mais, malheureusement, peu de gens écrivent sur ceux qui ont réussi. J'espère que l'histoire sur la façon dont nous soutenons notre monolithe (jusqu'à ce que nous l'ayons vu) vous intéressera.
Nous sommes bien conscients qu'une architecture massive peut entraîner de nombreux problèmes. Mais il est facile de le détruire, par une simple décision volontaire, c'est impossible: les ventes vont bon train, le site doit fonctionner, les changements pour les utilisateurs ne doivent pas être radicaux. Par conséquent, le passage d'un monolithe à un ensemble de microservices prend du temps, ce qu'il faut tenir: s'assurer que le système est opérationnel et sa résistance au stress.
Quel était le problème
Pendant très longtemps, le cluster de bases de données sur le site Web eldorado.ru a été construit selon le schéma suivant:
Tous les maîtres de ce schéma fonctionnent simultanément et tous sont en mode actif, jouant séquentiellement le flux de réplication ... M1-> M2- > M3-> M4-> M1 -> M2-> M3-> M4-> M1-> M2 ...
Dans notre configuration, cette configuration donnait le seul plus - elle permettait au système de fonctionner et de garder sa charge. Le fait est que l'équilibreur de demandes d'application, après toute mise à jour pour assurer la cohérence, bascule le flux de lecture entier vers ce maître, et un maître n'était pas suffisant une fois pour contenir tout le flux de lecture.
Mais un tel système ne pouvait assurer ni fiabilité ni rapidité de travail. Bien que cela paraisse simple, il présentait un certain nombre de défauts. Elle était très lente à mettre à jour les données dans le cluster: dans le pire des cas, il y avait jusqu'à cinq bras de réplication (selon le maître sur lequel les changements ont été initialement initiés). En raison de ces retards, de nombreux problèmes sont survenus à la fois dans le fonctionnement du site et lors de la passation de commandes dans la boutique en ligne.
Inconvénients de ce schéma :
- Les esclaves de la zone la plus éloignée du maître actif ne reçoivent les mises à jour des données dans le pire des cas qu'après 4 fois le temps d'exécution de la transaction, parfois il y a eu des délais de réplication frénétiques;
- Tout échec sur l'un des maîtres entraîne une incohérence des données dans tout le cluster jusqu'à ce qu'il soit éliminé;
- (- — );
- ;
- , ( , , );
- UPDATE/DELETE SELECT ;
- , slave_status seconds_behind_master.
Vous pouvez émuler ce comportement dans vos environnements de test en activant un délai de réplication artificiel de 1 à 2 secondes sur les esclaves (ce que nous avons fait), c'est un excellent moyen de tester la disponibilité de votre application pour de telles architectures distribuées via l'option MASTER_DELAY = N .
enfin, la transition vers une autre base de données dans notre cas est pas une option, parce que le système est trop grande échelle et bien en elle est liée à l'utilisation des fonctionnalités de MySQL , et même les nuances de son optimiseur de requête interne.
Comment nous l'avons résolu
Nous ne voulions pas changer le schéma nous-mêmes (c'est une opération risquée) et avons commencé par chercher une société de conseil qui pourrait proposer et déployer une nouvelle architecture, et le faire pour que le site reste accessible et que le switch ne soit pas perceptible. Parmi ces entreprises figuraient les plus grands intégrateurs et développeurs de logiciels.
Certaines entreprises ne nous ont tout simplement pas répondu (et c'est normal), tandis que d'autres ont écrit qu'elles n'étaient pas prêtes à entreprendre une telle tâche. Dans le même temps, la question du coût éventuel du projet ne s'est même pas posée.
Si les gros intégrateurs ne veulent pas s'impliquer dans une tâche, alors il est devenu doublement intéressant de la résoudre eux-mêmes. Nous avions un bon équipement à notre disposition, les problèmes de stabilité et de tolérance aux pannes étaient la deuxième priorité, et au tout début nous voulions accélérer en quelque sorte le transfert des données. Plusieurs options apparues dans les versions 5.6 et 5.7 de MySQL ont bien fonctionné pour cela.
Certes, la documentation a laissé entendre de manière transparente qu'il ne serait pas possible de les activer simplement, tk. dans le ring, il y aura certainement un esclave avec une version plus petite, mais ici c'est comme ça:
Le maître 5.7 est capable de lire les anciens journaux binaires écrits avant la mise à jour et de les envoyer aux esclaves 5.7. Les esclaves reconnaissent l'ancien format et le gèrent correctement.
Les journaux binaires créés par le maître après la mise à niveau sont au format 5.7. Ceux-ci sont également reconnus par les esclaves 5.7.
En d'autres termes, lors de la mise à niveau vers MySQL 5.7, les esclaves doivent être MySQL 5.7 avant de pouvoir mettre à niveau le maître vers 5.7.
Pour les tests, il nous suffisait de lever l'anneau de test, par exemple, via mysqld_multi, et d'exécuter des requêtes typiques dessus (vous pouvez même sur le même hôte, 4 instances sur différents ports avec différents jeux de décalage), quelque chose comme ceci:
mysql -h127.0.0.1 -P 3302 -e "RESET SLAVE; RESET MASTER; SHOW MASTER STATUS\G;"
mysql -h127.0.0.1 -P 3302 -e "CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=3301, MASTER_USER='root', MASTER_PASSWORD='', MASTER_LOG_FILE='master1-binlog.000001', MASTER_LOG_POS = 107;START SLAVE;"
mysql -h127.0.0.1 -P 3303 -e "RESET SLAVE; RESET MASTER; SHOW MASTER STATUS\G;"
mysql -h127.0.0.1 -P 3303 -e "CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=3302, MASTER_USER='root', MASTER_PASSWORD='', MASTER_LOG_FILE='master2-binlog.000001', MASTER_LOG_POS = 107;START SLAVE;"
mysql -h127.0.0.1 -P 3304 -e "RESET SLAVE; RESET MASTER; SHOW MASTER STATUS\G;"
mysql -h127.0.0.1 -P 3304 -e "CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=3303, MASTER_USER='root', MASTER_PASSWORD='', MASTER_LOG_FILE='master3-binlog.000001', MASTER_LOG_POS = 107;START SLAVE;"
mysql -h127.0.0.1 -P 3301 -e "RESET SLAVE; RESET MASTER; SHOW MASTER STATUS\G;"
mysql -h127.0.0.1 -P 3301 -e "CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=3304, MASTER_USER='root', MASTER_PASSWORD='', MASTER_LOG_FILE='master4-binlog.000001', MASTER_LOG_POS = 107;START SLAVE;"
Après cela, vous pouvez changer la version de n'importe quelle instance en exécutant la configuration avec le port souhaité avec un autre binaire, qui a été placé à côté, et en créant mysql_upgrade pour les tables de données / système.
En solde, nous pourrions, pour une durée limitée, envoyer tout le trafic à un seul maître, en mettre un autre à jour à ce moment-là, puis y basculer et remettre à jour tous les autres. Mais pour cela, il fallait s'assurer de la compatibilité du format binlog entre les versions, de sorte qu'absolument toutes les transactions soient perdues avec succès.
Un peu plus de documentation utile pour notre cas:
Pour éviter les incompatibilités, définissez les variables suivantes sur le maître MySQL 5.6:
binlog_checksum=NONE binlog_row_image=FULL binlog_rows_query_log_events=OFF log_bin_use_v1_row_events=1 (NDB Cluster only)
Cet indicateur s'est avéré obligatoire pour nous, bien que nous n'utilisions aucun NDB, sans lui, la réplication s'est terminée entre les serveurs 5.6 - 5.5, et mysqlbinlog lit le journal sans cette option avec l'erreur ERROR: Error in Log_event :: read_log_event () : 'La vérification d'intégrité a échoué', data_len: 8203, event_type: 30, si activé, alors tout démarre et fonctionne même.
Nous n'avons pas inclus le GTID, car outre l'exigence de compatibilité avec tous les anciens outils, objectivement, nous ne voyons pas suffisamment d'avantages pour la transition.
gtid_mode=OFF
Le test le plus simple pour vérifier l'exactitude de la réplication est de télécharger un vidage tour à tour sur le serveur avec 5.5 et le serveur avec 5.6 et voir si tout va bien.
Malheureusement, bien que prévu, les tests ont échoué.
Last_Error: Column 18 of table 'eldorado2.b_adv_banner' cannot be converted from type '<unknown type>' to type 'datetime'
datetime dans 5.6 est spécial, des microsecondes y sont ajoutées, donc dans 5.6 il a un nouveau datetime, inconnu dans 5.5
version 5.6 - il peut fonctionner dans un cluster dans un anneau en parallèle avec 5.5, si en même temps aucun champ n'est créé dans l'une des tables exécutées via la réplication avec de nouveaux types de champs. (datetime 5.6! = datetime 5.5, similaire à time, timestamp, il y a plus de 240 champs de ce type dans notre base de données).
Nous ne pouvions pas garantir totalement l'absence de DDL avec ces champs, et nous ne voulions pas compromettre les performances de l'ensemble du cluster. Mais nous avions un plan B plus sûr.
Cela signifiait la présence de matériel supplémentaire pour les manœuvres et la levée d'une copie complète du cluster à proximité, heureusement, nous avions un tel matériel. Et comme il existe une telle possibilité, il était alors nécessaire de créer un cluster "normal" à la fois.
Mais, en même temps, il est nécessaire d'assurer la préservation de l'opérabilité de tous les outils actuels de surveillance, de débogage et d'analyse de binlog et d'éliminer autant que possible toutes les lacunes existantes de l'architecture actuelle.
Réplication multicanal
Une solution miracle est nécessaire pour garder les sites intacts et les administrateurs à nourrir. Il s'agit de la réplication multicanal. Nous ne faisions a priori pas confiance aux nouvelles opportunités et n'étions pas sûrs de la technologie, nous ne pouvions trouver de telles informations ou cas nulle part, il y a peu d'expérience publique dans la grande production.
Par conséquent, nous avons pensé à tout nous-mêmes, le plan était le suivant:
- : 5.7, ;
- ;
- , — , , .
— , , , . , , ? . , « »! , - !
( « »)
Dans le schéma cible, 4 maîtres sont 4 flux d'enregistrement indépendants pour chaque esclave, qui sont traités indépendamment.
Sur tous les maîtres, il était désormais possible de désactiver log_slave_updates - ils n'ont tout simplement pas besoin de relayer quoi que ce soit, ils envoient tous les changements dans le flux principal (=> la charge sur le maître est encore plus faible).
Et en même temps, vous pouvez également activer le format de binlog minimum et le traitement parallèle des transactions en cours de route (tout à fait conditionnellement, vous devez le comprendre correctement):
slave_parallel_workers=5 slave_parallel_type=LOGICAL_CLOCK binlog_row_image=minimal
Avec cette configuration, nous pourrions basculer la charge vers un nouveau cluster à tout moment, mais cette route est à sens unique et ne prévoit pas de restauration.
Pour la durée de vie des connexions à l'ancien cluster, l'option log_slave_updates sur un point d'entrée principal du nouveau cluster est toujours présente, et par conséquent, toutes les modifications des connexions à «l'ancien» cluster sont parfaitement livrées au nouveau, et immédiatement après ils meurent cette option a été désactivée, l'application à ce moment a regardé 3 autres maîtres et les flux de données ne se sont en aucun cas croisés.
En conséquence, nous avons obtenu l'ensemble des avantages suivants:
- Si une longue requête bloque quelque chose, cela n'affecte qu'un seul thread de réplication sur 4 et n'affecte en aucune façon les autres;
- Le nouveau format de binlog, qui était auparavant impossible uniquement à cause de la version MySQL, prend maintenant plusieurs fois moins d'espace et, par conséquent, le trafic, de ce fait, beaucoup plus de changements peuvent passer dans tout le cluster;
- Maintenant, vous pouvez désactiver n'importe quel maître sans aucune douleur sans affecter tout le reste;
- Les accidents des maîtres ne sont plus si effrayants maintenant, vous pouvez maintenant cloner n'importe quel serveur en une minute dans n'importe quelle situation incompréhensible, régénérer le server_id, créer des crédits pour l'accès esclave et - le nouveau maître est prêt.
Il y a aussi un "moins":
- Chaque maître a beaucoup plus d'esclaves et il est plus facile de courir dans le canal (en fait, il ne s'agit pas d'une augmentation du trafic, mais d'une redistribution dans le temps et dans l'espace).
Qu'est-ce que le nouveau régime a donné
Le passage au nouveau schéma s'est avéré un succès, nous l'avons réalisé en une journée, le 28 août 2020. L'expérience de l'utilisation de la nouvelle architecture a montré que le nombre de problèmes de réplication a été réduit de trois à quatre fois (il est impossible de s'en débarrasser complètement). La stabilité du système a augmenté. Et le résultat principal était une augmentation du débit maximal du système. Si les développeurs précédents pouvaient blâmer des problèmes incompréhensibles sur la réplication, cela ne fonctionne plus pour eux.
Le nombre de problèmes clients causés par les retards de réplication a été réduit à plusieurs reprises, ce qui signifie que les clients subissent au moins un peu moins de douleur. Désormais, nous pouvons désactiver n'importe quel serveur maître à tout moment afin d'y effectuer des travaux. Cela n'affecte pas l'ensemble du cluster et n'arrête pas le processus de réplication.
Le cluster dessert le site principal "Eldorado" - pour la plupart, une ancienne application monolithique avec des fiches produits, un compte personnel, un panier, le traitement des commandes, un centre d'appels, etc. Au moment d'écrire ces lignes, la charge de lecture totale sur le cluster (uniquement sur les esclaves) est de 40k rps, environ 5k rps par serveur de base de données, à l'exclusion de la charge technique sur les esclaves techniques individuels, qui est nettement plus élevée aux heures de pointe. Cela peut ne pas sembler beaucoup, mais il faut tenir compte de la nature et de la complexité de ces requêtes.
Nous espérons vraiment que notre expérience sera utile à quelqu'un. En plus de la réplication multicanal, nous utilisons également de nombreuses choses intéressantes, telles que le trou noir et les tables fédérées, elles vous permettent également de supprimer beaucoup de maux de tête (et d'en ajouter un peu pour ceux qui ne comprennent pas pourquoi ils sont nécessaires), si quelqu'un est intéressé par les nuances et toutes autres questions sur notre MySQL - Bienvenue dans les commentaires.
Depuis six mois d'exploitation commerciale, nous n'avons encore rencontré aucun problème lié au multicanal, et nous pouvons certainement recommander une telle configuration comme suffisamment tolérante aux pannes et fiable.
Le monolithe lui-même est maintenant en train de se couper en un certain nombre de services séparés et indépendants, dont certains nous allons partager et vous parler de notre expérience - restez à l'écoute.
Un merci spécial à mon excellente équipe, nous n'aurions pas fait cela sans elle!
PS Au fait, nous avons encore vraiment besoin de programmeurs talentueux . Si vous êtes comme ça, venez , ce sera intéressant.