Réessai dans RocketMQ : mécanismes et bonnes pratiques

La gestion des échecs dans les systèmes distribués est cruciale pour la fiabilité. RocketMQ offre des mécanismes de réessai tant pour les producteurs que les consommateurs afin de traiter les pannes transitoires.

Réessai des producteurs

Lorsqu'un producteur envoie un message, des erreurs réseau ou des exceptions du serveur peuvent survenir. Pour garantir la livraison, RocketMQ intègre une logique de réessayage automatique côté client.

Configuration du nombre de tentatives

Le nombre maximal de tentatives de réessayage est configurable lors de l'initialisation du client. Par défaut, il est de 2. Cela s'applique aux envois synchrones et asynchrones. Pour les envois synchrones, le thread reste bloqué jusqu'à la réussite ou l'échec final. Pour les envois asynchrones, le résultat est retourné via des rappels.

Stratégies d'intervalle de réessayage

Le délai entre les tentatives dépend du contexte d'erreur. En cas de contrôle de flux (flow control) du côté serveur, une stratégie de backoff exponentiel est appliquée pour éviter de surcharger le système. Sinon, les réessayages se font immédiatement.

La stratégie de backoff utilise des paramètres comme le délai initial, un multiplicateur, un facteur de gigue aléatoire, et un délai maximal. Voici un exemple de logique de réessayage avec backoff :

function tenterConnexionAvecBackoff() {
    let delai_actuel = DELAI_INITIAL;
    let echeance = Date.now() + DELAI_initial;
    while (essayerConnexion(Math.max(echeance, Date.now() + DELAI_MIN_CONNEXION)) !== SUCCES) {
        attendreJusquA(echeance);
        delai_actuel = Math.min(delai_actuel * MULTIPLICATEUR, DELAI_MAX);
        let variation = Math.random() * 2 * JITTER * delai_actuel - JITTER * delai_actuel;
        echeance = Date.now() delai_actuel variation;
    }
}

Pour les messages transactionnels, seul un réessayage transparent est effectué ; les erreurs réseau ou timeouts ne déclenchent pas de réessayage.

Considérations sur les réessayages

Les réessayages peuvent entraîner des messages en double et augmenter la charge du serveur. Il est recommandé de mettre en place une gestion idempotente côté consommateur, par exemple en utilisant des identifiants métier uniques pour éviter les traitements répétés.

Bonnes pratiques pour les producteurs

Configurez judicieusement les délais d'expiration et le nombre maximal de tentatives en fonction des besoins de votre application. Pour assurer la non-perte de messages, capturez les exceptions et implémentez une solution de secours, comme le stockage des messages échoués dans une base de données pour un réessayage ultérieur. En cas de contrôle de flux persistant, envisagez une mise à l'échelle du serveur ou une redirection temporaire du trafic.

Réessayage des consommateurs

Lorsqu'un consommateur échoue à traiter un message, RocketMQ le redélivre selon une politique de réessayage. Les messgaes non traités après un certain nombre de tentatives sont envoyés à une file d'attente des messages morts (dead-letter queue).

Conditions de réessayage

Un échec de consommation est détecté si le consommateur renvoie un code d'échec, lève une exception, ou dépasse le délai de traitement. Le réessayage garantit l'intégrité du traitement des messages et la récupération après des pannes système.

Scénarios d'utilisation appropriés

Le réessayage est conçu pour les échecs transitoires, comme un verrou optimiste qui réussit après une nouvelle tentatvie. Il ne doit pas être utilisé pour le routage conditionnel ou le limitage de débit. Pour le limitage de débit, utilisez des mécanismes dédiés, tels que des limitateurs de débit locaux pour contrôler le flux de messages.

Voici un exemple d'utilisation d'un limitateur de débit avec Guava dans un consommateur :

LimateurDebit limiter = LimateurDebit.creer(50);
ConsommateurPush consommateur = fournisseur.nouveauConstructeurConsommateurPush()
    .setGroupeConsommation(groupeConsommation)
    .setConfigurationClient(configurationClient)
    .setExpressionsAbonnement(Collections.singletonMap(sujet, expressionFiltre))
    .setEcouteurMessages(vueMessage -> {
        limiter.acquerir();
        traiterMessage(vueMessage);
        return ResultatConsommation.SUCCES;
    })
    .construire();

Politiques de réessayage

Pour les ConsommateursPush, le nombre maximal de tentatives est défini à la création. Les intervalles de réessayage sont progressifs pour les messages non ordonnés, et fixes pour les messages ordonnés (par défaut 3 secondes).

Les ConsommateursSimples utilisent un paramètre de durée invisible pour contrôler le réessayage. Ce délai peut être ajusté via une API si le traitement dépasse le temps prévu. Par exemple :

consommateurSimple.changerDureeInvisible(vueMessage, nouvelleDuree, rappel);

L'intervalle de réessayage est calculé comme la différence entre la durée invisible et le temps de traitement réel. Si le traitement dépasse la durée invisible, le message est immédiatement réessayé.

Directives générales

Retournez toujours explicitement le succès ou l'échec de la consommation ; évitez les timeouts implicites. Ne remplacez pas le limitage de débit par des réessayages. Assurez une conception idempotente pour gérer les messages en double provenant des réessayages des producteurs ou consommateurs.

Étiquettes: RocketMQ réessai producteurs consommateurs files d'attente de messages

Publié le 10 juin à 07h57