Tous les produits
Search
Centre de documentation

ApsaraMQ for RocketMQ:Send and receive ordered messages

Dernière mise à jour :Aug 09, 2026

ApsaraMQ for RocketMQ assure la livraison des messages dans un ordre strict de type premier entré, premier sorti (FIFO). Cette rubrique fournit des exemples de code PHP pour l'envoi et la réception de messages ordonnés via le SDK client HTTP.

Types d'ordonnancement

ApsaraMQ for RocketMQ prend en charge deux portées d'ordonnancement :

Type Portée de l'ordonnancement Fonctionnement
Ordonnancement global Rubrique entière Tous les messages de la rubrique sont envoyés et consommés selon l'ordre FIFO
Ordonnancement par partition Par partition Les messages sont répartis entre les partitions à l'aide d'une clé de hachage. Les messages au sein de chaque partition sont consommés selon l'ordre FIFO

Une clé de hachage identifie la partition à laquelle appartient un message. Elle se distingue de la clé du message.

Pour plus d'informations, consultez la documentation Messages ordonnés.

Garanties d'ordonnancement

La livraison ordonnée dépend du comportement correct du producteur et du consommateur.

Important

Le courtier détermine l'ordre des messages en fonction de la séquence d'envoi effectuée par un seul producteur ou thread. Si plusieurs producteurs ou threads envoient des messages simultanément, l'ordre est défini par la séquence d'arrivée des messages sur le courtier, qui peut différer de l'ordre métier souhaité.

Côté consommateur :

  • Le consommateur peut extraire des messages ordonnés provenant de plusieurs partitions en un seul lot. Les messages de chaque partition conservent leur ordre d'envoi.

  • Le consommateur doit accuser réception (ACK) de tous les messages d'une partition avant de recevoir le lot suivant provenant de cette même partition.

  • Si le courtier ne reçoit pas d'accusé de réception avant l'expiration du délai spécifié par getNextConsumeTime(), il renvoie le message.

Prérequis

Avant de commencer, assurez-vous d'avoir :

Envoyer des messages ordonnés

Le code suivant envoie quatre messages ordonnés et les répartit sur deux partitions à l'aide de setShardingKey(). Les messages partageant la même clé de hachage sont livrés dans l'ordre FIFO au sein de cette partition.

<?php

require "vendor/autoload.php";

use MQ\Model\TopicMessage;
use MQ\MQClient;

class ProducerTest
{
    private $client;
    private $producer;

    public function __construct()
    {
        $this->client = new MQClient(
            // HTTP endpoint. Find this in the HTTP Endpoint section on the Instance Details
            // page in the ApsaraMQ for RocketMQ console.
            "${HTTP_ENDPOINT}",
            // Get AccessKey credentials from environment variables.
            getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"),
            getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET')
        );

        // Topic created in the ApsaraMQ for RocketMQ console.
        $topic = "${TOPIC}";
        // Instance ID. If the instance has no namespace, set this to null or "".
        // Check the Instance Details page for namespace information.
        $instanceId = "${INSTANCE_ID}";

        $this->producer = $this->client->getProducer($instanceId, $topic);
    }

    public function run()
    {
        try
        {
            for ($i = 1; $i <= 4; $i++)
            {
                $publishMessage = new TopicMessage(
                    "hello mq! " // Message body
                );
                // Set a custom message property.
                $publishMessage->putProperty("a", $i);
                // Set the sharding key to distribute messages across partitions.
                // Messages with the same sharding key are delivered in FIFO order.
                $publishMessage->setShardingKey($i % 2);

                $result = $this->producer->publishMessage($publishMessage);

                print "Send mq message success. msgId is:" . $result->getMessageId() . ", bodyMD5 is:" . $result->getMessageBodyMD5() . "\n";
            }
        } catch (\Exception $e) {
            print_r($e->getMessage() . "\n");
        }
    }
}

$instance = new ProducerTest();
$instance->run();

?>

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

Espace réservé Description
${HTTP_ENDPOINT} Endpoint HTTP disponible sur la page Instance Details
${TOPIC} Nom de la rubrique
${INSTANCE_ID} ID de l'instance (null ou "" si aucun namespace n'est configuré)

Recevoir des messages ordonnés

Le code suivant consomme des messages ordonnés à l'aide de consumeMessageOrderly(), ce qui maintient l'ordre FIFO au sein de chaque partition. Le consommateur utilise le sondage long (long polling) : si aucun message n'est disponible, la requête reste en attente sur le courtier jusqu'à l'arrivée d'un message ou l'expiration du délai de sondage.

<?php

require "vendor/autoload.php";

use MQ\MQClient;

class ConsumerTest
{
    private $client;
    private $consumer;

    public function __construct()
    {
        $this->client = new MQClient(
            // HTTP endpoint. Find this in the HTTP Endpoint section on the Instance Details
            // page in the ApsaraMQ for RocketMQ console.
            "${HTTP_ENDPOINT}",
            // Get AccessKey credentials from environment variables.
            getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"),
            getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET')
        );

        // Topic created in the ApsaraMQ for RocketMQ console.
        $topic = "${TOPIC}";
        // Consumer group ID created in the ApsaraMQ for RocketMQ console.
        $groupId = "${GROUP_ID}";
        // Instance ID. If the instance has no namespace, set this to null or "".
        // Check the Instance Details page for namespace information.
        $instanceId = "${INSTANCE_ID}";

        $this->consumer = $this->client->getConsumer($instanceId, $topic, $groupId);
    }

    public function ackMessages($receiptHandles)
    {
        try {
            $this->consumer->ackMessage($receiptHandles);
        } catch (\Exception $e) {
            if ($e instanceof MQ\Exception\AckMessageException) {
                // Handle ACK failures. This occurs when a receipt handle has expired.
                printf("Ack Error, RequestId:%s\n", $e->getRequestId());
                foreach ($e->getAckMessageErrorItems() as $errorItem) {
                    printf("\tReceiptHandle:%s, ErrorCode:%s, ErrorMsg:%s\n", $errorItem->getReceiptHandle(), $errorItem->getErrorCode(), $errorItem->getErrorCode());
                }
            }
        }
    }

    public function run()
    {
        // Poll for messages continuously. For production use, run multiple threads
        // to consume messages concurrently across partitions.
        while (True) {
            try {
                // consumeMessageOrderly() ensures FIFO order within each partition.
                // If the broker does not receive an ACK for a message, it redelivers
                // that message before sending subsequent messages from the same partition.
                $messages = $this->consumer->consumeMessageOrderly(
                    3, // Max messages per batch (up to 16)
                    3  // Long polling timeout in seconds (up to 30)
                );
            } catch (\MQ\Exception\MessageResolveException $e) {
                // Handle messages that cannot be parsed due to invalid characters.
                $messages = $e->getPartialResult()->getMessages();
                $failMessages = $e->getPartialResult()->getFailResolveMessages();

                $receiptHandles = array();
                foreach ($messages as $message) {
                    $receiptHandles[] = $message->getReceiptHandle();
                    printf("MsgID %s\n", $message->getMessageId());
                }
                foreach ($failMessages as $failMessage) {
                    $receiptHandles[] = $failMessage->getReceiptHandle();
                    printf("Fail To Resolve Message. MsgID %s\n", $failMessage->getMessageId());
                }
                $this->ackMessages($receiptHandles);
                continue;
            } catch (\Exception $e) {
                if ($e instanceof MQ\Exception\MessageNotExistException) {
                    // No messages available. Long polling continues automatically.
                    printf("No message, contine long polling!RequestId:%s\n", $e->getRequestId());
                    continue;
                }

                print_r($e->getMessage() . "\n");

                sleep(3);
                continue;
            }

            print "======>consume finish, messages:\n";

            $receiptHandles = array();
            foreach ($messages as $message) {
                $receiptHandles[] = $message->getReceiptHandle();
                printf("MessageID:%s TAG:%s BODY:%s \nPublishTime:%d, FirstConsumeTime:%d, \nConsumedTimes:%d, NextConsumeTime:%d,ShardingKey:%s\n",
                    $message->getMessageId(), $message->getMessageTag(), $message->getMessageBody(),
                    $message->getPublishTime(), $message->getFirstConsumeTime(), $message->getConsumedTimes(), $message->getNextConsumeTime(),
                    $message->getShardingKey());
                print_r($message->getProperties());
            }

            // ACK all messages. If the broker does not receive an ACK before
            // getNextConsumeTime(), it redelivers the message. Each redelivery
            // assigns a new receipt handle with an updated timestamp.
            print_r($receiptHandles);
            $this->ackMessages($receiptHandles);
            print "=======>ack finish\n";

        }

    }
}

$instance = new ConsumerTest();
$instance->run();

?>

En plus des espaces réservés mentionnés dans la section Envoyer des messages ordonnés, remplacez les éléments suivants :

Espace réservé Description
${GROUP_ID} ID du groupe de consommateurs

Paramètres de consumeMessageOrderly()

Paramètre Description Plage
numOfMessages Nombre maximal de messages à consommer par lot Jusqu'à 16
waitSeconds Durée du sondage long en secondes. Si aucun message n'est disponible, la requête reste en attente sur le courtier jusqu'à l'arrivée d'un message ou l'expiration du délai Jusqu'à 30

Gestion des erreurs

Le consommateur gère trois scénarios d'erreur :

Exception Cause Action
MessageResolveException Des caractères non valides dans le corps du message empêchent l'analyse Accusez réception des messages analysables et non analysables
MessageNotExistException Aucun message disponible dans la rubrique Poursuivez automatiquement le sondage long
AckMessageException Le handle de réception a expiré avant l'envoi de l'accusé de réception Enregistrez l'erreur et réessayez lors de la prochaine livraison