Tous les produits
Search
Centre de documentation

ApsaraMQ for RocketMQ:Consumption idempotence

Dernière mise à jour :Aug 09, 2026

ApsaraMQ for RocketMQ garantit la livraison des messages au moins une fois. Un consommateur peut donc recevoir le même message plusieurs fois. Si votre logique métier est sensible aux doublons (débits, ajustements de stock, création de commandes), implémentez une consommation idempotente. Ce mécanisme garantit que le traitement répété d'un même message produit le même résultat que son traitement unique.

Prenons l'exemple d'un consommateur traitant un débit de 100 USD pour une commande. En raison d'un problème réseau, le message est livré deux fois. Avec une consommation idempotente, le débit n'est effectué qu'une seule fois et un seul enregistrement de 100 USD est généré pour la commande.

Causes des messages en double

Les messages en double surviennent dans trois scénarios.

Nouvelle tentative du producteur

Le producteur envoie un message et le courtier ApsaraMQ for RocketMQ le persiste. Toutefois, le courtier peut ne pas parvenir à envoyer l'accusé de réception au producteur en raison d'un problème réseau temporaire ou d'une panne de ce dernier. Le producteur considère alors l'envoi comme échoué et réessaie. Le consommateur reçoit deux messages contenant le même contenu mais des ID de message différents.

Redistribution par le courtier

Le consommateur reçoit et traite un message, mais l'accusé de réception envoyé au courtier échoue en raison d'un problème réseau temporaire. Ne pouvant confirmer la consommation, le courtier redistribue le message après le rétablissement du réseau afin de respecter la garantie de livraison « au moins une fois ». Le consommateur reçoit alors deux messages avec le même contenu et le même ID de message.

Équilibrage de charge

Des instabilités réseau, des redémarrages de courtiers ou d'applications consommatrices déclenchent un équilibrage de charge. Durant le rééquilibrage, un consommateur peut recevoir des messages déjà livrés.

Utilisez des clés métier, non des ID de message

Il est tentant de dédupliquer selon l'ID de message, mais cette méthode n'est pas fiable. Comme indiqué dans le scénario de nouvelle tentative du producteur, le même message logique peut arriver avec deux ID de message différents lors d'une nouvelle tentative d'envoi par le producteur. La déduplication basée sur l'ID de message laisserait passer ces doublons.

Attribuez plutôt un identifiant métier unique comme clé de message. Utilisez par exemple un ID de commande, un ID de transaction de paiement ou toute valeur identifiant uniquement l'opération métier. Cette clé reste cohérente quel que soit le nombre d'envois ou de redistributions. Elle découle de votre logique métier, et non du système de messagerie.

Cette approche offre une répétabilité prévisible. En cas d'échec et de renvoi du message, le même contexte métier génère toujours la même clé, rendant la déduplication fiable.

Implémenter la consommation idempotente

Étape 1 : Définissez la clé de message côté producteur

Joignez un identifiant métier unique comme clé de message lors de l'envoi.

Message message = new Message();
message.setKey("ORDERID_100");
SendResult sendResult = producer.send(message);

Remplacez ORDERID_100 par l'identifiant métier unique réel, tel qu'un ID de commande ou de transaction.

Étape 2 : Récupérez la clé de message côté consommateur

Récupérez la clé de message dans le rappel du consommateur et utilisez-la pour garantir l'idempotence du traitement.

consumer.subscribe("ons_test", "*", new MessageListener() {
    public Action consume(Message message, ConsumeContext context) {
        String key = message.getKey()
        // Perform idempotent processing based on the message key that uniquely identifies your business.
    }
});

Étape 3 : Garantissez l'idempotence via un magasin de déduplication

La clé de message seule n'empêche pas le traitement en double : votre application doit vérifier si la clé a déjà été traitée. Un modèle courant utilise une base de données relationnelle avec une contrainte d'unicité :

  1. Avant le traitement, insérez la clé de message dans une table de déduplication dotée d'une contrainte d'unicité sur la colonne de clé.

  2. Si l'insertion réussit, traitez le message.

  3. Si l'insertion échoue en raison d'une violation de clé primaire ou de contrainte d'unicité, le message a déjà été traité. Ignorez-le.

Privilégiez le modèle « insertion puis vérification » au modèle « vérification puis insertion ». Avec ce dernier, deux threads pourraient valider la vérification simultanément avant toute insertion, entraînant un traitement en double. S'appuyer sur la violation de contrainte d'unicité de la base de données assure l'atomicité et évite les conditions de concurrence.

Exemple de table de déduplication (MySQL) :

CREATE TABLE message_dedup (
    message_key VARCHAR(255) NOT NULL,
    created_at  TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (message_key)
);

Exemple de logique de consommateur idempotent :

consumer.subscribe("ons_test", "*", new MessageListener() {
    public Action consume(Message message, ConsumeContext context) {
        String key = message.getKey();

        try {
            // Attempt to insert the message key. Fails if already processed.
            insertMessageKey(key);
        } catch (DuplicateKeyException e) {
            // Already processed. Skip.
            return Action.CommitMessage;
        }

        // Process the business logic.
        processOrder(key);

        return Action.CommitMessage;
    }
});

Remplacez insertMessageKey et processOrder par vos méthodes réelles d'accès à la base de données et de logique métier.

Pour les scénarios à haut débit où une base de données relationnelle deviendrait un goulot d'étranglement, privilégiez une approche basée sur Redis avec SETNX (SET if Not eXists).

Bonnes pratiques

Pratique Détails
Englobez la déduplication et la logique métier dans une seule transaction Si la logique métier échoue après l'insertion de l'enregistrement de déduplication, cet enregistrement est annulé, permettant au message d'être réessayé lors de la prochaine livraison. Sans transaction, un échec métier laisse l'enregistrement de déduplication en place et empêche tout retraitement du message.
Nettoyez périodiquement le magasin de déduplication La table de déduplication grossit avec le temps. Définissez une période de rétention (par exemple, 7 jours) et supprimez les anciens enregistrements par lots pour éviter une croissance illimitée du stockage.
Choisissez le bon magasin de déduplication pour votre débit Utilisez une base de données relationnelle avec des contraintes d'unicité pour un débit modéré. Pour les scénarios à haut débit, utilisez Redis SETNX avec un TTL correspondant à votre période de rétention, ce qui gère également le nettoyage automatique.