Los siguientes ejemplos de código PHP muestran cómo enviar y consumir mensajes programados y diferidos con el SDK cliente HTTP de ApsaraMQ for RocketMQ.
Funcionamiento de los mensajes programados y diferidos
Los mensajes programados y diferidos posponen su entrega en lugar de llegar inmediatamente después de la publicación.
Mensaje diferido: Se entrega después de un periodo de retraso especificado. Por ejemplo, entregar 30 segundos después del envío.
Mensaje programado: Se entrega en un momento específico. Por ejemplo, entregar el 2024-06-15 a las 09:00:00.
Ambos tipos comparten la misma API. Llame a setStartDeliverTime con una marca de tiempo Unix en milisegundos que represente cuándo debe entregar el mensaje el broker.
Para un mensaje diferido, calcule la marca de tiempo como
hora actual + duración del retraso.Para un mensaje programado, convierta la hora de entrega objetivo directamente a una marca de tiempo Unix en milisegundos.
Casos de uso comunes:
Tiempo de espera de pedido: Cancele un pedido no pagado después de 30 minutos al publicar un mensaje diferido cuando se crea el pedido.
Reintento con retroceso: Vuelva a procesar una tarea con errores después de un retraso en lugar de reintentar inmediatamente.
Notificaciones temporizadas: Active un recordatorio en un momento específico, como 15 minutos antes de un evento programado.
Para obtener más información, consulte Scheduled messages and delayed messages.
Requisitos previos
Antes de empezar, asegúrese de contar con lo siguiente:
Created resources en la consola de ApsaraMQ for RocketMQ: una instancia, un topic y un grupo de consumidores
Created an AccessKey pair y haber almacenado el AccessKey ID y el AccessKey secret como variables de entorno
Envío de mensajes programados o diferidos
El siguiente código envía cuatro mensajes diferidos, cada uno entregado 10 segundos después de su publicación. Para enviar un mensaje programado en su lugar, pase la hora de entrega objetivo como una marca de tiempo Unix en milisegundos a setStartDeliverTime.
Reemplace los marcadores de posición antes de ejecutar el código:
|
Marcador de posición |
Descripción |
Ejemplo |
|
|
Endpoint HTTP de la página Instance Details en la consola de ApsaraMQ for RocketMQ |
|
|
|
Nombre del topic creado en la consola |
|
|
|
ID de la instancia. Si la instancia no tiene namespace, establézcalo en |
|
<?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 from the Instance Details page in the ApsaraMQ for RocketMQ console
"<HTTP_ENDPOINT>",
// AccessKey ID for authentication
getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"),
// AccessKey secret for authentication
getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET')
);
$topic = "<TOPIC>";
// Instance ID. If the instance has no namespace, set to null or "".
$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
);
// Custom message property
$publishMessage->putProperty("a", $i);
// Message key for tracing
$publishMessage->setMessageKey("MessageKey");
// Deliver the message 10 seconds from now.
// For a scheduled message, set this to the target delivery time
// as a millisecond-level Unix timestamp.
$publishMessage->setStartDeliverTime(time() * 1000 + 10 * 1000);
$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();
?>
Puntos clave:
setStartDeliverTimeacepta una marca de tiempo Unix en milisegundos. En PHP,time()devuelve segundos, así que multiplique por 1000 para convertir.Para un retraso de 10 segundos:
time() * 1000 + 10 * 1000.Para la entrega en un momento específico, convierta esa hora a una marca de tiempo Unix en milisegundos. Por ejemplo, para entregar a las
2024-06-15 09:00:00 UTC:strtotime('2024-06-15 09:00:00') * 1000.
Consumo de mensajes programados o diferidos
Los mensajes programados y diferidos se consumen de la misma manera que los mensajes normales. Una vez que llega la hora de entrega, los mensajes están disponibles mediante sondeo largo.
Reemplace los marcadores de posición antes de ejecutar el código:
|
Marcador de posición |
Descripción |
Ejemplo |
|
|
Endpoint HTTP de la página Instance Details |
|
|
|
Nombre del topic creado en la consola |
|
|
|
ID del grupo de consumidores creado en la consola |
|
|
|
ID de la instancia. Si la instancia no tiene namespace, establézcalo en |
|
<?php
use MQ\MQClient;
require "vendor/autoload.php";
class ConsumerTest
{
private $client;
private $consumer;
public function __construct()
{
$this->client = new MQClient(
// HTTP endpoint from the Instance Details page in the ApsaraMQ for RocketMQ console
"<HTTP_ENDPOINT>",
// AccessKey ID for authentication
getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"),
// AccessKey secret for authentication
getenv('ALIBABA_CLOUD_ACCESS_KEY_SECRET')
);
$topic = "<TOPIC>";
// Consumer group ID created in the ApsaraMQ for RocketMQ console
$groupId = "<GROUP_ID>";
// Instance ID. If the instance has no namespace, set to null or "".
$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) {
// ACK may fail if the 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->getErrorMessage());
}
}
}
}
public function run()
{
// Consume messages in a loop. For production use, run multiple threads
// to consume messages concurrently.
while (True) {
try {
// Long polling: if no message is available, the request waits
// on the broker until a message arrives or the polling period ends.
$messages = $this->consumer->consumeMessage(
3, // Max messages per request (up to 16)
3 // Long polling timeout in seconds (up to 30)
);
} catch (\MQ\Exception\MessageResolveException $e) {
// Thrown when some messages contain invalid characters and cannot be parsed
$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 on the next iteration.
printf("No message, continue 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, MessageKey:%s\n",
$message->getMessageId(), $message->getMessageTag(), $message->getMessageBody(),
$message->getPublishTime(), $message->getFirstConsumeTime(),
$message->getConsumedTimes(), $message->getNextConsumeTime(),
$message->getMessageKey());
print_r($message->getProperties());
}
// If the broker does not receive an ACK before the time specified
// in getNextConsumeTime(), the message is redelivered.
// Each delivery generates a new receipt handle.
print_r($receiptHandles);
$this->ackMessages($receiptHandles);
print "ack finish\n";
}
}
}
$instance = new ConsumerTest();
$instance->run();
?>
Aspectos importantes sobre el consumo de mensajes
Sondeo largo: Cuando no hay ningún mensaje disponible, el broker retiene la solicitud del consumidor hasta que llega un mensaje o expira el tiempo de espera del sondeo. Esto reduce las solicitudes de red innecesarias.
ACK (confirmación de consumo): Después de procesar un mensaje, el consumidor envía un ACK con el identificador de recepción. Si el broker no recibe ningún ACK antes de
getNextConsumeTime(), vuelve a entregar el mensaje.Caducidad del identificador de recepción: Cada entrega asigna un identificador de recepción único con su propio tiempo de caducidad. Un identificador caducado provoca que el ACK falle y desencadena una nueva entrega.
Errores de análisis sintáctico: Si el cuerpo de un mensaje contiene caracteres no válidos, el SDK lanza
MessageResolveException. Llame agetPartialResult()para recuperar los mensajes analizados correctamente y gestionar los errores por separado.