方案一:使用 RabbitMQ 自帶的延遲佇列(不推薦)
Dead Letter Exchange 其實就是一種普通的 exchange 。只是在某一個設定Dead Letter Exchange 的佇列中有訊息過期了會自動觸發訊息的轉發,傳送到 Dead Letter Exchange 中去。
實現原理:
RabbitMQ針對佇列中的訊息過期時間有兩種方法可以設定。
- 通過設定佇列的 “x-message-ttl” 屬性,佇列中所有訊息都有相同的過期時間。
- 對訊息進行單獨設定,每條訊息TTL可以不同。
- 如果同時使用,則訊息的過期時間以兩者之間TTL較小的那個數值為準。
注意事項:
兩種方式是有區別的: 如果設定了佇列的TTL屬性,那麼一旦訊息過期,就會被佇列丟棄,而第二種方式,訊息即使過期,也不一定會被馬上丟棄,因為訊息是否過期是在即將投遞到消費者之前判定的,如果當前佇列有嚴重的訊息積壓情況,則已過期的訊息也許還能存活較長時間,如果使用在訊息屬性上設定 TTL 的方式,訊息可能並不會按時“死亡“,因為RabbitMQ只會檢查第一個訊息是否過期,如果過期則丟到死信佇列,索引如果第一個訊息的延時時長很長,而第二個訊息的延時時長很短,則第二個訊息並不會優先得到執行。
延時佇列的訊息流轉流程
具體思路就是不同延時時間建立不同佇列
程式碼
生產者虛擬碼
$channel = $connection->getChannel();
$delayExchange = 'delayed_' . $producerMessage->getExchange();
$delayQueue = 'delayed_queue_' . $producerMessage->getExchange() . $producerMessage->getTtl() . '_' . $delayTime;
$delayRoutingKey = $producerMessage->getRoutingKey() . $delayTime;
//定義延遲交換器
$channel->exchange_declare($delayExchange, 'topic', false, true, false);
//定義延遲佇列
$channel->queue_declare($delayQueue, false, true, false, false, false, new AMQPTable(array(
"x-dead-letter-exchange" => $producerMessage->getExchange(),
"x-dead-letter-routing-key" => $producerMessage->getRoutingKey(),
"x-message-ttl" => $producerMessage->getTtl() * 1000,
)));
//繫結延遲佇列到交換器上
$channel->queue_bind($delayQueue, $delayExchange, $delayRoutingKey);
$channel->basic_publish($message, $delayExchange, $delayRoutingKey);
消費者
略
方案二 使用 rabbitmq-delayed-message-exchange 外掛
安裝
- 第一 下載後解壓,並將其拷貝至 RabbitMQ plugins 目錄。
- 啟用外掛
rabbitmq-plugins enable rabbitmq_delayed_message_exchang
編寫 Demo 程式碼
生產者
<?php
require_once __DIR__ . './vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Wire\AMQPTable;
$connection = new AMQPStreamConnection('*.*.*.*', 5672, 'guest', 'guest');
$channel = $connection->channel();
$args = new AMQPTable(['x-delayed-type' => 'fanout']);
$channel->exchange_declare('delayed_exchange_test', 'x-delayed-message', false, true, false, false, false, $args);
$args = new AMQPTable(['x-dead-letter-exchange' => 'delayed']);
$channel->queue_declare('delayed_queue_test', false, true, false, false, false, $args);
$channel->queue_bind('delayed_queue_test', 'delayed_exchange_test');
$data = 'Hello World at ' . date('Y-m-d H:i:s');
$delay = 10000;
$message = new AMQPMessage($data, ['delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT]);
$headers = new AMQPTable(['x-delay' => $delay]);
$message->set('application_headers', $headers);
$channel->basic_publish($message, 'delayed_exchange_test');
printf(' [x] Message sent: %s %s', $data, PHP_EOL);
$channel->close();
$connection->close();
消費者
require_once __DIR__ . './vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Wire\AMQPTable;
$connection = new AMQPStreamConnection('*.*.*.*', 5672, 'guest', 'guest');
$channel = $connection->channel();
$callback = function (AMQPMessage $message) {
printf(' [x] Message received: %s %s', $message->body, \Carbon\Carbon::now()->toDateTimeString().PHP_EOL);
$message->delivery_info['channel']->basic_ack($message->delivery_info['delivery_tag']);
};
$channel->basic_consume('delayed_queue_test', '', false, false, false, false, $callback);
while (count($channel->callbacks)) {
$channel->wait();
}
$channel->close();
$connection->close();
檢視效果
發訊息
收訊息
到此延遲佇列已實現。
本作品採用《CC 協議》,轉載必須註明作者和本文連結