Tous les produits
Search
Centre de documentation

ApsaraMQ for RocketMQ:Envoyer et recevoir des messages ordonnés

Dernière mise à jour :Sep 21, 2026

ApsaraMQ for RocketMQ distribue les messages ordonnés selon une séquence stricte de type premier entré, premier sorti (FIFO). Utilisez les messages ordonnés lorsque votre application exige un traitement des messages exactement dans l'ordre d'envoi, par exemple pour traiter les commandes commerciales selon leur heure de soumission ou synchroniser les modifications de base de données de manière séquentielle.

Cette rubrique fournit des exemples de code pour envoyer et recevoir des messages ordonnés à l'aide du SDK client HTTP pour Java.

Fonctionnement des messages ordonnés

Il existe deux catégories de messages ordonnés :

  • Messages globalement ordonnés : Tous les messages d'un topic suivent une seule séquence FIFO. Cette approche offre la garantie d'ordre la plus forte, mais limite le débit à une seule partition.

  • Messages ordonnés par partition : Les messages sont répartis entre les partitions via une clé de sharding. Au sein de chaque partition, les messages respectent l'ordre FIFO. Les différentes partitions sont consommées indépendamment, ce qui permet d'obtenir un débit plus élevé tout en préservant l'ordre là où cela est nécessaire.

Une clé de sharding identifie la partition à laquelle appartient un message. Les messages partageant la même clé de sharding sont toujours acheminés vers la même partition et consommés dans l'ordre. Une clé de sharding est distincte d'une clé de message.

Important

Un courtier ApsaraMQ for RocketMQ détermine l'ordre de génération des messages en fonction de l'ordre dans lequel l'expéditeur utilise un seul producteur ou thread pour envoyer les messages. Si l'expéditeur utilise plusieurs producteurs ou threads pour envoyer des messages simultanément, l'ordre des messages est déterminé par l'ordre de réception par le courtier ApsaraMQ for RocketMQ. Cet ordre peut différer de l'ordre d'envoi côté métier.

Ordre de production et ordre de consommation

La livraison FIFO de bout en bout dépend de deux facteurs indépendants :

Ordre de production Ordre de consommation Résultat
Producteur unique, envois sériels Consommation ordonnée (consumeMessageOrderly) Garantie FIFO par clé de sharding
Plusieurs producteurs ou threads Consommation ordonnée L'ordre est déterminé par l'arrivée chez le courtier, et non par l'intention métier
Producteur unique, envois sériels Consommation concurrente L'ordre de consommation n'est pas garanti

Pour garantir un ordre strict, les deux conditions suivantes doivent être remplies : envoyez les messages depuis un thread de producteur unique et consommez-les avec consumeMessageOrderly.

Prérequis

Avant de commencer, effectuez les opérations suivantes :

  • Installez le SDK pour Java. Pour plus d'informations, consultez la rubrique Configurer votre environnement Java.

  • Créez les ressources que vous souhaitez spécifier dans le code via la console ApsaraMQ for RocketMQ. Ces ressources incluent des instances, des topics et des groupes de consommateurs. Pour plus d'informations, consultez la rubrique Créer des ressources.

  • Obtenez la paire AccessKey de votre compte Alibaba Cloud. Pour plus d'informations, consultez la rubrique Créer une AccessKey.

Envoyer des messages ordonnés

L'exemple suivant envoie huit messages ordonnés répartis sur deux partitions à l'aide de clés de sharding.

Remplacez les espaces réservés par vos valeurs réelles :

Espace réservé Description Exemple
<your-http-endpoint> Endpoint HTTP disponible sur la page Instance Details de la console ApsaraMQ for RocketMQ http://xxx.mqrest.cn-hangzhou.aliyuncs.com
<your-topic> Nom du topic OrderTopic
<your-instance-id> ID de l'instance. Définissez cette valeur sur null ou "" si l'instance ne possède pas de namespace MQ_INST_xxx
import com.aliyun.mq.http.MQClient;
import com.aliyun.mq.http.MQProducer;
import com.aliyun.mq.http.model.TopicMessage;

import java.util.Date;

public class OrderProducer {

    public static void main(String[] args) {
        MQClient mqClient = new MQClient(
                "<your-http-endpoint>",
                // Read credentials from environment variables to avoid hardcoding secrets
                System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"),
                System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
        );

        final String topic = "<your-topic>";
        // Set to null or "" if the instance has no namespace
        final String instanceId = "<your-instance-id>";

        // Get the producer for the specified topic
        MQProducer producer;
        if (instanceId != null && instanceId != "") {
            producer = mqClient.getProducer(instanceId, topic);
        } else {
            producer = mqClient.getProducer(topic);
        }

        try {
            // Send 8 messages, alternating between 2 sharding keys (partitions)
            for (int i = 0; i < 8; i++) {
                TopicMessage pubMsg = new TopicMessage(
                        "hello mq!".getBytes(),   // Message body
                        "A"                        // Message tag for filtering
                );
                // Sharding key determines the partition. Messages with the same
                // sharding key are delivered to the same partition in FIFO order.
                pubMsg.setShardingKey(String.valueOf(i % 2));
                pubMsg.getProperties().put("a", String.valueOf(i));

                // Publish synchronously. No exception means the send succeeded.
                TopicMessage pubResultMsg = producer.publishMessage(pubMsg);

                System.out.println(new Date() + " Send mq message success. Topic is:" + topic
                        + ", msgId is: " + pubResultMsg.getMessageId()
                        + ", bodyMD5 is: " + pubResultMsg.getMessageBodyMD5());
            }
        } catch (Throwable e) {
            // Handle send failure: retry or persist the message for later delivery
            System.out.println(new Date() + " Send mq message failed. Topic is:" + topic);
            e.printStackTrace();
        }

        mqClient.close();
    }
}
Remarque

Chaque topic prend en charge un seul type de message. Un topic créé pour les messages ordonnés ne peut ni envoyer ni recevoir de messages normaux.

Recevoir des messages ordonnés

L'exemple suivant consomme des messages ordonnés à l'aide de consumeMessageOrderly, qui utilise le sondage long (long polling) et garantit l'ordre FIFO au niveau de la partition.

Remplacez les espaces réservés par vos valeurs réelles :

Espace réservé Description Exemple
<your-http-endpoint> Endpoint HTTP disponible sur la page Instance Details http://xxx.mqrest.cn-hangzhou.aliyuncs.com
<your-topic> Nom du topic OrderTopic
<your-group-id> ID du groupe de consommateurs GID_Order
<your-instance-id> ID de l'instance. Définissez cette valeur sur null ou "" si l'instance ne possède pas de namespace MQ_INST_xxx
import com.aliyun.mq.http.MQClient;
import com.aliyun.mq.http.MQConsumer;
import com.aliyun.mq.http.common.AckMessageException;
import com.aliyun.mq.http.model.Message;

import java.util.ArrayList;
import java.util.List;

public class OrderConsumer {

    public static void main(String[] args) {
        MQClient mqClient = new MQClient(
                "<your-http-endpoint>",
                System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"),
                System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET")
        );

        final String topic = "<your-topic>";
        final String groupId = "<your-group-id>";
        // Set to null or "" if the instance has no namespace
        final String instanceId = "<your-instance-id>";

        final MQConsumer consumer;
        if (instanceId != null && instanceId != "") {
            consumer = mqClient.getConsumer(instanceId, topic, groupId, null);
        } else {
            consumer = mqClient.getConsumer(topic, groupId);
        }

        // Continuously poll for messages
        do {
            List<Message> messages = null;

            try {
                // consumeMessageOrderly guarantees partition-level FIFO order.
                // The consumer must ACK all messages in a batch before the broker
                // delivers the next batch from the same partition.
                messages = consumer.consumeMessageOrderly(
                        3,  // Max messages per batch (up to 16)
                        3   // Long polling timeout in seconds (up to 30)
                );
            } catch (Throwable e) {
                e.printStackTrace();
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
            }

            if (messages == null || messages.isEmpty()) {
                System.out.println(Thread.currentThread().getName() + ": no new message, continue!");
                continue;
            }

            // Process messages
            System.out.println("Receive " + messages.size() + " messages:");
            for (Message message : messages) {
                System.out.println(message);
                System.out.println("ShardingKey: " + message.getShardingKey()
                        + ", a:" + message.getProperties().get("a"));
            }

            // ACK consumed messages. If the broker does not receive an ACK before
            // the retry timeout, it redelivers the message.
            {
                List<String> handles = new ArrayList<String>();
                for (Message message : messages) {
                    handles.add(message.getReceiptHandle());
                }

                try {
                    consumer.ackMessage(handles);
                } catch (Throwable e) {
                    if (e instanceof AckMessageException) {
                        AckMessageException errors = (AckMessageException) e;
                        System.out.println("Ack message fail, requestId is:"
                                + errors.getRequestId() + ", fail handles:");
                        if (errors.getErrorMessages() != null) {
                            for (String errorHandle : errors.getErrorMessages().keySet()) {
                                System.out.println("Handle:" + errorHandle
                                        + ", ErrorCode:" + errors.getErrorMessages().get(errorHandle).getErrorCode()
                                        + ", ErrorMsg:" + errors.getErrorMessages().get(errorHandle).getErrorMessage());
                            }
                        }
                        continue;
                    }
                    e.printStackTrace();
                }
            }
        } while (true);
    }
}

Fonctionnement de la consommation ordonnée

Lorsque vous appelez consumeMessageOrderly, le courtier applique les comportements suivants :

  1. Verrouillage au niveau de la partition : Le consommateur peut extraire des messages de plusieurs partitions, mais au sein de chaque partition, les messages arrivent dans l'ordre d'envoi.

  2. Validation par lot (ACK) : Le consommateur doit acquitter (ACK) tous les messages du lot actuel avant de recevoir le lot suivant provenant de la même partition. Si le courtier ne reçoit pas d'acquittement avant l'expiration du délai de nouvelle tentative, il redistribue le message non acquitté.

  3. Sondage long (Long polling) : Si aucun message n'est disponible, le courtier maintient la requête pendant la durée de sondage spécifiée (jusqu'à 30 secondes) et répond immédiatement dès qu'un message arrive.

Notes d'utilisation

  • Topics à type unique : Chaque topic prend en charge un seul type de message. Ne mélangez pas les messages ordonnés avec des messages normaux sur le même topic.

  • Producteur unique pour un ordre strict : Envoyez tous les messages nécessitant un ordre relatif depuis un seul producteur sur un thread unique. L'utilisation de plusieurs producteurs ou de threads concurrents rompt la garantie d'ordre.

  • Conception de la clé de sharding : Choisissez une clé de sharding qui regroupe les messages connexes sans créer de partitions chaudes. Par exemple, utilisez un ID de commande pour conserver tous les événements d'une commande dans l'ordre, plutôt qu'un ID client qui pourrait concentrer trop de messages sur une seule partition.

  • Acquittement rapide : Un acquittement tardif bloque la livraison des messages suivants pour toute la partition. Traitez et acquittez chaque lot aussi rapidement que possible.

  • Expiration du handle de réception : Chaque handle de réception de message possède un horodatage unique. Si le handle expire avant que vous ne l'acquittiez, l'acquittement échoue et le courtier redistribue le message.

Voir aussi