Todos os produtos
Search
Central de documentação

ApsaraMQ for RocketMQ:Enviar e receber mensagens ordenadas

Última atualização: Sep 22, 2026

O ApsaraMQ for RocketMQ entrega mensagens ordenadas em sequência rigorosa de primeiro a entrar, primeiro a sair (FIFO). Utilize mensagens ordenadas quando sua aplicação exigir processamento na ordem exata de envio, como no processamento de ordens de negociação por horário de submissão ou na sincronização sequencial de alterações em banco de dados.

Este tópico fornece código de exemplo para enviar e receber mensagens ordenadas por meio do SDK de cliente HTTP para Java.

Como funciona

As mensagens ordenadas se dividem em duas categorias:

  • Mensagens com ordenação global: todas as mensagens de um tópico seguem uma única sequência FIFO. Essa abordagem oferece a garantia de ordenação mais forte, porém limita o throughput a uma única partição.

  • Mensagens com ordenação por partição: as mensagens são distribuídas entre partições por meio de uma chave de particionamento (sharding key). Dentro de cada partição, a ordem FIFO é mantida. Partições diferentes são consumidas de forma independente, o que possibilita maior throughput e preserva a ordem onde ela é necessária.

A sharding key identifica a qual partição uma mensagem pertence. Mensagens com a mesma sharding key são sempre entregues na mesma partição e consumidas em ordem. Uma sharding key é diferente de uma chave de mensagem (message key).

Importante

O broker do ApsaraMQ for RocketMQ determina a ordem de geração das mensagens com base na ordem em que o remetente utiliza um único produtor ou thread para enviá-las. Se o remetente usar múltiplos produtores ou threads para envio simultâneo, a ordem das mensagens será determinada pela ordem de chegada ao broker do ApsaraMQ for RocketMQ. Essa ordem pode divergir da ordem de envio no lado do negócio.

Ordem de produção e ordem de consumo

A entrega FIFO de ponta a ponta depende de dois fatores independentes:

Ordem de produção

Ordem de consumo

Resultado

Produtor único com envios seriais

Consumo ordenado (consumeMessageOrderly)

FIFO garantido por sharding key

Múltiplos produtores ou threads

Consumo ordenado

Ordem determinada pela chegada ao broker, não pela intenção de negócio

Produtor único com envios seriais

Consumo concorrente

Ordem de consumo não garantida

Para ordenação rigorosa, ambas as condições devem ser atendidas: envie a partir de um único thread de produtor e consuma com consumeMessageOrderly.

Pré-requisitos

Antes de começar, certifique-se de que as seguintes etapas foram concluídas:

  • Instale o SDK para Java. Para mais informações, consulte Defina seu ambiente Java.

  • Crie os recursos que deseja especifique no código por meio do console do ApsaraMQ for RocketMQ. Os recursos incluem instâncias, tópicos e grupos de consumidores. Para mais informações, consulte Crie recursos.

  • Obtenha o par de AccessKey da sua conta Alibaba Cloud. Para mais informações, consulte Crie um AccessKey.

Enviar mensagens ordenadas

O exemplo a seguir envia oito mensagens ordenadas em duas partições usando sharding keys.

Substitua os espaços reservados pelos seus valores reais:

Espaço reservado

Descrição

Exemplo

<your-http-endpoint>

Endpoint HTTP obtido na página Instance Details do console do ApsaraMQ for RocketMQ

http://xxx.mqrest.cn-hangzhou.aliyuncs.com

<your-topic>

Nome do tópico

OrderTopic

<your-instance-id>

ID da instância. Defina como null ou "" se a instância não possuir 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();
    }
}
Nota

Cada tópico suporta apenas um tipo de mensagem. Um tópico criado para mensagens ordenadas não pode enviar nem receber mensagens normais.

Receber mensagens ordenadas

O exemplo a seguir consome mensagens ordenadas com consumeMessageOrderly, que emprega long polling e garante a ordem FIFO no nível da partição.

Substitua os espaços reservados pelos seus valores reais:

Espaço reservado

Descrição

Exemplo

<your-http-endpoint>

Endpoint HTTP obtido na página Instance Details

http://xxx.mqrest.cn-hangzhou.aliyuncs.com

<your-topic>

Nome do tópico

OrderTopic

<your-group-id>

ID do grupo de consumidores

GID_Order

<your-instance-id>

ID da instância. Defina como null ou "" se a instância não possuir 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);
    }
}

Como funciona o consumo ordenado

Ao chamar consumeMessageOrderly, o broker aplica os seguintes comportamentos:

  1. Bloqueio no nível da partição: o consumidor pode buscar mensagens de múltiplas partições, mas, dentro de cada partição, as mensagens chegam na ordem de envio.

  2. Controle de confirmação por lote: o consumidor precisa confirmar (ACK) todas as mensagens do lote atual antes de receber o próximo lote da mesma partição. Se o broker não receber o ACK antes do tempo limite de reentrega, ele reenvia a mensagem não confirmada.

  3. Long polling: quando não há mensagens disponíveis, o broker mantém a requisição pelo tempo de polling especificado (até 30 segundos) e responde imediatamente assim que uma mensagem chega.

Observações de uso

  • Tópicos de tipo único: cada tópico suporta apenas um tipo de mensagem. Não misture mensagens ordenadas com mensagens normais no mesmo tópico.

  • Produtor único para ordenação rigorosa: envie todas as mensagens que exigem ordenação relativa a partir de um único produtor em um único thread. O uso de múltiplos produtores ou threads concorrentes invalida a garantia de ordenação.

  • Design da sharding key: escolha uma sharding key que agrupe mensagens relacionadas sem criar partições sobrecarregadas. Por exemplo, use um id de pedido para manter todos os eventos de um mesmo pedido em sequência, em vez de um id de cliente que pode concentrar mensagens demais em uma única partição.

  • Confirmação imediata: a confirmação tardia bloqueia a entrega de mensagens subsequentes para toda a partição. Processe e confirme (ACK) cada lote o mais rápido possível.

  • Expiração do receipt handle: cada receipt handle de mensagem possui um timestamp exclusivo. Se o handle expirar antes do ACK, a confirmação falha e o broker reenvia a mensagem.

Consulte também

  • Ordered messages: garantias de ordenação, tipos de ordenação e conceitos detalhados

  • Crie recursos: configure instâncias, tópicos e grupos de consumidores no console