Contexte du problème :
Le 1 du mois 9, vers 17:12, nous avons reçu un signalement du personnel de déploiement : certains appareils ne pouvaient pas se mettre à niveau normalement ou ne recevaient pas les commandes envoyées par la console. Un collègue a consulté les journaux du projet control (ci-après control) et constaté que control n’avait pas reçu les informations d’ombre remontées par les appareils, et n’avait donc envoyé aucune commande. Le projet control communique directement avec les appareils et leur envoie des commandes et des informations de configuration en fonction des informations qu’ils remontent, notamment pour les mises à niveau et l’envoi des journaux. Avant la mise en service d’IoT, control obtenait l’état courant des appareils grâce aux remontées des signaux de vie ; après sa mise en service, il dépend des informations d’ombre des appareils
pour obtenir les informations actuelles de l’appareil, control s’abonne aux informations de son jumeau numérique, mais celles-ci lui sont transmises par le service de jumeau numérique (abrégé IoT). Il ne reçoit pas directement les remontées du jumeau numérique de l’appareil ; pour le détail du cheminement, consultez les deux schémas ci-dessous :
L’application (control) récupère l’état de l’appareil

L’application (control) envoie des commandes à l’appareil

Après avoir appris que control ne recevait pas les messages d’ombre, je suis immédiatement allé vérifier la présence de messages dans la console rabbitmq afin de confirmer deux points : 1.Les appareils avaient-ils remonté des messages 2.rabbitmq fonctionnait-il normalement, les figures 1、2 ci-dessous sont deux captures de la console rabbitmq prises à ce moment-là, à partir de Figure 1 On peut clairement établir que l’appareil envoie bien des messages, mais beaucoup sont à l’état unacked (non acquittés)(Cela indique que le message a déjà été remis au consommateur, mais que celui-ci n’a pas encore envoyé d’ack ; en théorie, il suffit d’attendre un moment pour que tout rentre dans l’ordre); pour savoir précisément dans quelle file s’accumulent les messages unacked, consultez Figure 2,“spacebridgeiot-shadow” sert précisément à recevoir les informations d’ombre numérique envoyées par les appareils. Les messages s’étant accumulés dans la file d’attente, il était donc logique qu’ils n’aient pas été transférés à control. Après un certain temps d’observation, nous avons constaté que le nombre de messages unacked était passé à 0, alors que le nombre total indiqué par total n’avait guère changé. Cela donnait l’impression que les messages unacked étaient retournés dans la file d’attente pour être de nouveau livrés. Effectivement, quelques minutes plus tard, nous avons de nouveau constaté un grand nombre de messages unacked, puis, après quelques minutes, ces messages sont retournés dans la file d’attente. control ne recevait toujours aucun message. En consultant alors les journaux du côté IoT, nous avons découvert qu’aucun message d’ombre numérique n’arrivait. Dans la console rabbitmq, nous avons consulté «spacebridgeiot-shadow” Cette file n’avait étonnamment plus aucun consommateur, comme Figure 3 comme illustré. En consultant alors les journaux de RabbitMQ, on trouve effectivement des messages d’erreur, tels que Figure 4 comme indiqué, rabbitmq a fermé la connexion de sa propre initiative.
Figure 1:vue d’ensemble de rabbitmq

Figure 2 : graphique des statistiques des files d’attente RabbitMQ

Figure 3 :Présentation de spacebridgeiot-shadow

Figure 4:message d’erreur de rabbitmq

Solution temporaire :
Comme de nombreuses plaintes étaient déjà arrivées, nous avons adopté une solution assez brutale : « supprimer les messages accumulés ». Après leur suppression, tout est effectivement revenu à la normale(Remarque : les incidents en production doivent être résolus au plus vite ; nous n’avons pas le temps d’analyser les journaux puis de procéder méthodiquement à leur résolution, il faut faire vite)。

Reproduire le problème dans un environnement local :
1. Accumuler un grand nombre de messages(au moins plusieurs dizaines de milliers)dans la file « mirrorTestQueue » de RabbitMQ de l’environnement 10.200.41.166
2.Arrêter le consommateur de mirrorTestQueue, puis le redémarrer une fois les messages accumulés
3.Une fois l’accumulation terminée, redémarrer le consommateur
Comme nous l’avions prévu, plusieurs milliers de messages ont été transmis au consommateur en quelques secondes. Au bout de quelques minutes, rabbitmq a fermé de sa propre initiative la connexion avec le consommateur ; celui-ci n’apparaissait alors plus dans la console pour cette file. Comme nous avions activé la récupération automatique du consommateur, il s’est reconnecté automatiquement après un certain temps,
Mais la connexion était de nouveau coupée très vite, pratiquement comme dans le problème rencontré en production. Qu’est-ce qui provoquait donc ce problème ? À vrai dire, je n’avais alors guère de piste, et après avoir cherché sur Internet, je n’avais trouvé aucune réponse vraiment satisfaisante(À l’époque, je n’avais pas saisi le fond du problème et mes mots-clés de recherche étaient trop généraux),par la suite
Nous avons soupçonné un problème au niveau de TCP et avons donc décidé de capturer les paquets pour tenter de trouver des indices. Et effectivement, la chance nous a souri. Sans plus attendre, voici l’image.


Avant 13:06:25.643428, rabbitmq continuait à envoyer des messages au consommateur. À 13:06:25.643428, la fenêtre TCP du consommateur a commencé à être saturée. Cette situation a duré environ 30 secondes, puis rabbitmq a fermé la connexion de sa propre initiative (en envoyant un paquet RST). Le consommateur s’est ensuite reconnecté, puis la fenêtre a de nouveau été saturée et la connexion a encore été fermée après environ 30 secondes.


Cela semble assez régulier : chaque épisode dure 30s, ce qui fait penser à un paramètre configurable. En gros, on peut résumer ainsi : « tcp full window conduit le serveur à réinitialiser activement la connexion par un RST, et cela se produit de manière régulière »
Cette fois, j’ai changé les mots-clés de recherche et j’ai trouvé Réponse,rabbitmq possède un paramètre nommé tcp_listen_options.send_timeout est un paramètre qui contrôle le délai d’expiration des écritures ; lorsqu’il est dépassé, un RST TCP est déclenché(https://github.com/rabbitmq/rabbitmq-java-client/issues/341),modifions cela pour voir le résultat :
1. Régler le délai d’expiration des écritures sur 10s
tcp_listen_options.send_timeout = 10000
2.Capturer les paquets pour vérifier si cela fonctionne


Il s’écoule environ 10s entre le remplissage de la fenêtre et la fermeture de la connexion.
Retour sur le comportement observé:
Comme aucun prefetch n’était configuré côté consommateur de rabbitmq, rabbitmq lui a envoyé trop de messages à la fois. Cela a saturé la fenêtre TCP du consommateur, déclenchant alors tcp_listen_options.send_timeout de rabbitmq. Lorsque ce délai d’écriture atteint un certain seuil, rabbitmq ferme la connexion TCP du consommateur.
Solution ultime :
La suppression des messages n’était auparavant qu’une solution de dernier recours ; elle résolvait le problème, mais de manière trop brutale. Il nous faut trouver une solution élégante : puisque la fenêtre TCP se remplit parce que trop de messages sont envoyés au consommateur, nous devrions agir sur le débit de réception. Il suffit de dire à rabbitmq, lors de la connexion, de ne pas nous en envoyer trop.(Le passage qui suit est extrait de https://blog.csdn.net/james_searcher/article/details/70308565)rabbitmq possède un attribut nommé prefetch, qui désigne le nombre maximal de messages non acquittés qu’un seul consommateur peut prendre en charge. Comment le comprendre ? mq définit pour chaque consommateur un tampon dont la taille correspond à prefetch. À chaque réception d’un message, MQ place le message dans le tampon, puis le transmet au client. Lorsqu’il reçoit un message ack (le consommateur émet une commande baseack), mq libère une place dans le tampon, puis y ajoute un nouveau message. Mais si le tampon est plein à ce moment-là, MQ entre dans un état de blocage. Plus précisément, supposons que prefetch soit réglé sur 10 et qu’il y ait deux consommateurs. Cela signifie que chaque consommateur prélève à l’avance 10 messages de la file et les conserve localement en attendant de les traiter. Le nombre de messages non acquittés du canal passe alors à 20. L’ordre de livraison de Rabbit est le suivant : il livre d’abord 10 messages au consommateur 1, puis 10 messages au consommateur 2. Si un nouveau message doit alors être livré, il vérifie d’abord si le nombre de messages non acquittés du canal est égal à 20. Si c’est le cas, le message n’est livré à aucun consommateur et reste dans la file. Ensuite, lorsque l’un des consommateurs acquitte un message, le nombre de messages non acquittés devient 19. Rabbit détermine alors quel consommateur a moins de 10 messages non acquittés et lui livre le message.
Concrètement, dans le code, cela donne

Comment déterminer cette valeur ? Un article officiel de RabbitMQ l’explique très bien, je ne vais donc pas entrer dans les détails. Dans notre système, elle est actuellement fixée à 20.
https://www.rabbitmq.com/blog/2012/05/11/some-queuing-theory-throughput-latency-and-bandwidth/
Conclusion :
Nous débutons encore dans l’utilisation de rabbitmq. Avant de l’utiliser, il est indispensable de bien consulter son API, de se familiariser avec les API courantes et de multiplier les expériences dans un environnement hors production.