IBiS' Reise in Richtung Quorum Queues

L'année dernière, chez IT-Clouds, un groupe responsable du développement des produits cloud de Swisscom, nous avons activé le support de la Quorum Queue de RabbitMQ [1] dans notre solution PaaS interne. Ensuite, nous avons migré l'un de nos systèmes logiciels pour l'utiliser dans la production. Cet article décrit notre système, notre motivation et les étapes que nous avons suivies pour migrer une série d'applications basées sur Java/Spring uniquement avec du code.

A propos d'IBiS

IBiS est l'abréviation de "Integration of Business Services". Il s'agit d'un système backend important pour les offres cloud de Swisscom (Enterprise Service Cloud, Dynamic Computing Services), qui soutient les fonctions de facturation et de rapport de nos produits. Le système:

  • suit le cycle de vie des ressources qui s'exécutent dans le cloud (machines virtuelles, clusters Kubernetes, etc.).
  • mesure et évalue l'utilisation des ressources (CPU, mémoire, stockage, etc.)
  • maintient les systèmes existants et commerciaux de tiers à niveau (systèmes de gestion de configuration, systèmes de facturation, etc.)
  • offre différentes fonctions de rapport (par exemple, des rapports de facturation ou de licence pour les systèmes d'exploitation utilisés) pour le portail client et les systèmes commerciaux ultérieurs.

Pour atteindre ses objectifs, IBiS écoute tous les types d'événements qui proviennent des services cloud et y répond en conséquence. Il est constitué d'une série de composants dont la structure est simple et basée sur les événements:

IBiS est développé chez Swisscom, il est écrit en Java moderne et se base sur les dernières versions du Spring Framework.

RabbitMQ chez IBiS

Exigences pour une transmission fiable des messages

Le cœur d'IBiS est RabbitMQ [2], un courtier de messagerie open source qui supporte le protocole de messagerie AMQP [3]. Nous utilisons RabbitMQ comme service au sein de notre Application Cloud interne (iAPC), une solution PaaS basée sur Cloud Foundry [4]. Il est responsable du transport fiable des événements au sein du système, depuis les composants qui les accueillent (par exemple d'Apache Kafka [5]) jusqu'aux composants qui les traitent en appliquant la logique commerciale correspondante. L'une des exigences commerciales les plus importantes d'IBiS est le suivi précis de ce qui se passe dans les clouds de Swisscom. Pour y parvenir, nous devons nous assurer que les événements sont fournis de manière fiable. La perte d'un événement peut avoir de graves conséquences et peut facilement conduire à des erreurs de facturation ou de rapport (par exemple, une VM a été supprimée dans le cloud, mais IBiS n'en a jamais été informé car l'événement a été perdu).

À l'origine, IBiS utilisait des files d'attente en miroir permanent pour obtenir la fiabilité souhaitée. C'était une bonne solution pendant longtemps, mais les dernières versions de RabbitMQ (3.8.0+) supportent les files d'attente Quorum. Les files d'attente Quorum sont considérées comme l'alternative moderne aux files d'attente en miroir. Elles se concentrent sur la sécurité des données et ont été spécialement conçues pour répondre aux besoins de systèmes comme IBiS, où la fiabilité joue un rôle important. Comme notre charge de travail est exactement ce pour quoi les files d'attente Quorum ont été construites, nous avons décidé de nous éloigner des files d'attente en miroir. Le reste de cet article décrit comment nous l'avons fait.

Notre configuration

Avant de parler de la migration elle-même, il est important de décrire brièvement le système RabbitMQ qu'IBiS utilise. Notre architecture de messages interne est basée sur plusieurs Topic-Exchanges [6], auxquels sont envoyés des messages (événements). Chaque message est accompagné d'une clé de routage, qui dans notre cas est le type d'événement. Les échangeurs transmettent les messages à une série de files d'attente [7]. Les files d'attente sont créées (déclarées) par nos composants backend, chaque file d'attente appartenant à exactement un composant et écoutant un sous-ensemble de clés de routage. Il s'agit essentiellement d'un système Publish/Subscribe dans lequel chaque composant backend peut s'abonner à certaines clés de routage (types d'événements) et n'écouter que les messages qui l'intéressent. L'architecture d'un tel échange est représentée dans l'illustration suivante:

Lors du traitement des messages, nous devons également gérer les erreurs (par exemple si un événement est mal formé). Pour cela, nous utilisons des files d'attente de lettres mortes. Si un message ne peut pas être traité par un composant backend, il est envoyé à un échange de lettres mortes [8] qui le pousse dans une file d'attente de lettres mortes appartenant au composant dans lequel le problème a été découvert. Le message y reste jusqu'à ce que nous comprenions le problème et que nous puissions le traiter manuellement.

En fin de compte, notre architecture peut être résumée comme suit.

  • Nous utilisons un échange de sujets auquel nous envoyons de nouveaux messages.
  • Nous utilisons un échange de lettres mortes pour traiter les messages erronés.
  • Chaque composant backend déclare deux files d'attente : une pour traiter les nouveaux messages qui proviennent de l'échange de sujets, et une pour traiter à nouveau les messages erronés qui proviennent de l'échange de lettres mortes.

Migration vers les files d'attente de quorum avec Spring AMQP

Nos recherches sur le passage des files d'attente en miroir aux files d'attente en quorum ont rapidement montré que RabbitMQ ne pouvait pas nous aider seul dans cette tâche. Les files d'attente RabbitMQ ne peuvent pas simplement changer de type. Elles sont immuables, c'est-à-dire que pour passer aux files d'attente Quorum, nous devons déclarer de nouvelles files d'attente, déplacer les messages des anciennes files d'attente et supprimer les anciennes files d'attente. Une façon de résoudre ce problème serait d'utiliser le plugin Shovel [9]. Cependant, cela nécessiterait que l'équipe RabbitMQ de Swisscom s'active (planifier la maintenance, installer le plugin, le tester, etc.), sans aucune garantie que l'approche fonctionne réellement pour notre cas d'utilisation. C'est pourquoi nous avons décidé de prendre une autre voie et de tout traiter avec Spring AMQP en tant que code.

Spring AMQP

Spring AMQP [10] est un projet Spring qui vise à soutenir le développement de solutions basées sur AMQP. Il propose des abstractions pratiques qui sont faciles à utiliser, s'intègrent bien dans le Spring Framework et permettent d'exploiter tout le potentiel de l'AMQP. Même si cela dépasse le cadre de cet article de parler en détail de Spring AMQP, il est important de mentionner qu'il offre des classes avec lesquelles nous pouvons déclarer et gérer différents objets RabbitMQ. Par exemple, nous pouvons facilement déclarer un nouvel échange de topic, des règles de routage (liaisons) et une nouvelle file d'attente:

Une fois que nous avons déclaré notre setup (qui est alors automatiquement fourni au démarrage de l'application de manière idempotente), nous pouvons accéder à RabbitMQ avec la classe RabbitTemplate. Essayons d'assembler ces parties pour transformer les files d'attente en miroir en files d'attente Quorum.

Une migration transparente

Nous pouvons noter ce qui doit se passer pour migrer de manière transparente et sûre des files d'attente en miroir vers les files d'attente en quorum. C'est ce que nous devons faire:

  1. créer de nouvelles files d'attente de type quorum.
  2. arrêter le routage vers les files d'attente en miroir existantes.
  3. traiter les messages restants dans les files d'attente miroir existantes (ils peuvent encore arriver pendant que nous interrompons le transfert).
  4. supprimer les files d'attente en miroir pour qu'il ne reste que les files d'attente du quorum.

En utilisant notre architecture de messagerie et Spring AMQP, nous pouvons traduire ces étapes dans les suivantes. Pour chaque composant backend, nous voulons:

  1. fournir une nouvelle file d'attente de quorum au démarrage de l'application et supprimer la file d'attente précédente de la configuration. Cela crée la nouvelle file d'attente que nous écoutons et met fin à l'écoute de l'ancienne file d'attente.
  2. supprimer le lien entre l'ancienne file d'attente et l'échange de sujets, c'est-à-dire supprimer les règles de routage qui font que l'échange de sujets continue à transmettre des messages à l'ancienne file d'attente.
  3. supprimer tous les messages de la file d'attente en miroir en les rejetant (c'est-à-dire en signalant une erreur) et en les déplaçant ainsi dans la file d'attente des lettres mortes.
  4. consommer les messages de la file d'attente des lettres mortes.
  5. supprimer la file d'attente en miroir.

Les étapes décrites ci-dessus sont représentées dans le diagramme suivant:

On pourrait se demander pourquoi nous avons décidé de vider les anciennes files d'attente en envoyant les messages à des files d'attente de lettres mortes plutôt que de les consommer directement. Nous avons choisi cette approche pour des raisons de maintenance. Comme nous nous appuyons fortement sur les files d'attente Dead Letter pour traiter les messages erronés, nous disposons déjà d'un code robuste et éprouvé que nous pouvons réutiliser. Il serait possible de récupérer directement les files d'attente en miroir, mais il y a un risque que certains cas marginaux ne soient pas traités et que des messages soient ainsi perdus.

Nous pouvons maintenant passer en revue le code que nous avons utilisé pour la migration de nos files d'attente.

Commençons par définir les noms des files d'attente. Supposons que nous ayons déclaré la nouvelle file d'attente de quorum sous le nom de "my-quorum-queue" et que la précédente file d'attente classique en miroir "my-classic-mirrored-queue" existe déjà:

Ensuite, nous pouvons utiliser l'objet RabbitTemplate autowired (voir les documents Spring AMQP pour plus de détails) pour obtenir un client d'administration:

Ensuite, nous vérifions d'abord si la file d'attente classique existe. Si ce n'est pas le cas, nous n'avons rien à faire.

Ensuite, nous utilisons les liens que nous déclarons dans la configuration de notre application. Si nous les créons de manière similaire à l'exemple de la section sur Spring AMQP, nous pouvons créer une liste de liens et l'utiliser pour supprimer les liens de l'ancienne file d'attente. L'hypothèse la plus importante est que les liens sont les mêmes, c'est-à-dire que la nouvelle file d'attente a le même ensemble de liens que l'ancienne.

Ensuite, nous partons du principe que le retrait des liens prendra du temps et que certains messages pourraient encore arriver pendant que nous avançons. C'est pourquoi nous attendons un peu.

A ce moment-là, l'ancienne file d'attente devrait atteindre un état cohérent. Il est supposé que tous les messages ont été reçus et qu'aucun nouveau message n'arrive, car il n'y a pas de liens. Par conséquent, nous pouvons rejeter tous les messages et les déplacer dans la file d'attente des lettres mortes:

À ce moment-là, l'ancienne file d'attente devrait être vidée et tous les messages en attente devraient être déplacés vers la file d'attente des lettres mortes. Comme mentionné précédemment, notre base de code IBiS contient une logique pour la file d'attente des lettres mortes. Cela se fait via l'objet `deadLetterQueueService` (qui, sous le capot, fournit simplement quelques méthodes utilitaires pour lire dans la file d'attente des lettres mortes ou obtenir quelques statistiques). Nous l'utilisons pour nous assurer que la migration a réussi et pour consommer les messages:

Pour finir, nous supprimons l'ancienne file d'attente:

Nous avons fourni cette migration en tant que point final REST, qui se déclenche automatiquement (après le lancement de l'application) ou manuellement (si nous devons la répéter après une erreur). La migration est idempotente, c'est-à-dire qu'elle peut être déclenchée plusieurs fois et qu'elle aboutit toujours au même résultat.

Tu trouveras le code complet ici(ouvre une nouvelle fenêtre).

Conclusion

Dans cet article, nous avons décrit comment nous avons migré IBiS, une plateforme logicielle que nous développons pour soutenir les offres cloud de Swisscom en matière de facturation et de reporting, vers les files d'attente de quorum de RabbitMQ. Nous avons discuté de la façon dont nous utilisons RabbitMQ en interne, de nos exigences et de notre décision de migrer. Enfin, nous avons montré, à l'aide d'exemples de code tirés de la pratique, comment une telle migration peut être réalisée avec Java et le projet Spring AMQP.

Références

Adam Krajewski

Adam Krajewski

Software Engineer

Plus d’articles getIT

Prêts pour Swisscom

Trouve le Job ou l’univers professionnel qui te convient. Où tu veux co-créer et évoluer.

Ce qui nous définit, c’est toi.

Vers les univers professionnels

Vers les postes vacants cybersécurité