使用 RabbitMQ 實現延時佇列

thus發表於2020-07-17

方案一:使用 RabbitMQ 自帶的延遲佇列(不推薦)

Dead Letter Exchange 其實就是一種普通的 exchange 。只是在某一個設定Dead Letter Exchange 的佇列中有訊息過期了會自動觸發訊息的轉發,傳送到 Dead Letter Exchange 中去。

實現原理:

RabbitMQ針對佇列中的訊息過期時間有兩種方法可以設定。

  • 通過設定佇列的 “x-message-ttl” 屬性,佇列中所有訊息都有相同的過期時間。
  • 對訊息進行單獨設定,每條訊息TTL可以不同。
  • 如果同時使用,則訊息的過期時間以兩者之間TTL較小的那個數值為準。

注意事項:

兩種方式是有區別的: 如果設定了佇列的TTL屬性,那麼一旦訊息過期,就會被佇列丟棄,而第二種方式,訊息即使過期,也不一定會被馬上丟棄,因為訊息是否過期是在即將投遞到消費者之前判定的,如果當前佇列有嚴重的訊息積壓情況,則已過期的訊息也許還能存活較長時間,如果使用在訊息屬性上設定 TTL 的方式,訊息可能並不會按時“死亡“,因為RabbitMQ只會檢查第一個訊息是否過期,如果過期則丟到死信佇列,索引如果第一個訊息的延時時長很長,而第二個訊息的延時時長很短,則第二個訊息並不會優先得到執行。

延時佇列的訊息流轉流程
使用 RabbitMQ 實現延時佇列

具體思路就是不同延時時間建立不同佇列

使用 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();

檢視效果

發訊息
使用 RabbitMQ 實現延時佇列

收訊息

使用 RabbitMQ 實現延時佇列
到此延遲佇列已實現。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

如果可以,我要變成光

如果可以,我要變成光

相關文章