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.
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 :
Installé le SDK PHP pour ApsaraMQ for RocketMQ. Pour plus de détails, consultez Préparer l'environnement
Créé une instance, une rubrique et un groupe de consommateurs ApsaraMQ for RocketMQ dans la console ApsaraMQ for RocketMQ
Obtenu une paire AccessKey pour votre compte Alibaba Cloud. Pour plus de détails, consultez Créer une paire AccessKey
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 |