How to Implement Delayed Queues in RabbitMQ Using Dead‑Letter Exchanges
This guide explains common use cases for delayed tasks, describes two RabbitMQ delayed‑queue implementations—including TTL with dead‑letter exchanges and the rabbitmq‑delayed‑message‑exchange plugin—provides detailed configuration steps, code examples in PHP, and demonstrates how to run and verify the solution.
Use Cases for Delayed Tasks
IoT systems need to timeout commands that receive no response.
Orders are automatically cancelled if payment is not received within 30 minutes.
Send registration emails to new members after a 1‑minute delay.
Two Ways to Implement RabbitMQ Delayed Queues
Use message expiration (TTL) combined with a dead‑letter exchange (DLX) to forward expired messages to a delay queue.
Install the rabbitmq-delayed-message-exchange plugin (available from RabbitMQ 3.5.7) to provide native delayed‑message support.
Dead‑Letter Mechanism for Delayed Queues
RabbitMQ does not have a built‑in delayed‑queue feature; it must be achieved by combining message TTL and a dead‑letter exchange. When a message’s TTL expires, it becomes a dead letter and is routed to the DLX.
Message TTL (Time To Live)
TTL defines how long a message lives. It can be set per‑queue or per‑message using the expiration field (milliseconds) or the queue argument x-message-ttl. The smaller of queue TTL and message TTL applies.
$body = 'Tinywan expiration!';
$msg = new AMQPMessage($body);
$msg->set("delivery_mode", AMQPMessage::DELIVERY_MODE_PERSISTENT);
// Set timeout (expiration) in milliseconds
$msg->set("expiration", 30000); // 30 secondsWhen the message is published with {expiration: '30000'}, RabbitMQ holds it for 30 seconds. The message is only discarded or turned into a dead letter when it reaches the head of the queue and its expiration has passed.
Dead‑Letter Exchange (DLX)
A dead‑letter exchange receives messages that become dead letters. It is declared like any other exchange. When a message expires, is rejected without requeue, or the queue length limit is reached, the message is sent to the DLX specified by the queue argument x-dead-letter-exchange. The routing key can be set with x-dead-letter-routing-key; if omitted, the original routing key is used.
Configuring a Dead‑Letter Queue
First declare the DLX and its queue, then bind them:
Exchange: dlx.exchange
Queue: dlx.queue
RoutingKey: #
# "#" means any routing key will be routed to this queue.Next, add the dead‑letter settings to the target queue:
arguments.put("x-dead-letter-exchange", "dlx.exchange");Now, when a message expires, is rejected, or the queue overflows, it is automatically routed to the dead‑letter queue.
Using the DLX for Scheduled Tasks
By publishing a message with an expiration and consuming from the DLX‑bound queue, you can implement a scheduled task. When the original queue’s message expires, it becomes a dead letter and is delivered to the DLX queue, where a consumer processes it at the intended time.
Practical Example (PHP)
Sending a Delayed Message
public static function delayQueueSend($param = [])
{
$connection = RabbitMqConnection::getConnection();
$channel = $connection->channel();
// Define a fanout exchange for waiting messages
$channel->exchange_declare('waitSendExchange', 'fanout', false, false, false);
// Define a fanout exchange for expired messages
$channel->exchange_declare('expireExchange', 'fanout', false, false, false);
// Declare the expired queue
$channel->queue_declare('expireQueue', false, false, false, false);
// Declare the waiting queue with DLX pointing to the expired exchange
$channel->queue_declare('waitSendQueue', false, false, false, false, false,
new AMQPTable(["x-dead-letter-exchange" => "expireExchange"]));
$channel->queue_bind('waitSendQueue', 'waitSendExchange');
$channel->queue_bind('expireQueue', 'expireExchange');
$body = 'Tinywan expiration!';
$msg = new AMQPMessage($body);
$msg->set("delivery_mode", AMQPMessage::DELIVERY_MODE_PERSISTENT);
// Set expiration (30 seconds)
$msg->set("expiration", 30000);
$channel->basic_publish($msg, 'waitSendExchange');
echo " [x] Sent " . date('Y-m-d H:i:s') . ': ' . $body . "
";
$channel->close();
$connection->close();
}Receiving the Delayed Message (Blocking Mode)
public static function delayQueueReceive()
{
$connection = RabbitMqConnection::getConnection();
$channel = $connection->channel();
$channel->exchange_declare('waitSendExchange', 'fanout', false, false, false);
$channel->exchange_declare('expireExchange', 'fanout', false, false, false);
$channel->queue_declare('expireQueue', false, false, false, false);
$channel->queue_declare('waitSendQueue', false, false, false, false, false,
new AMQPTable(["x-dead-letter-exchange" => "expireExchange"]));
$channel->queue_bind('waitSendQueue', 'waitSendExchange');
$channel->queue_bind('expireQueue', 'expireExchange');
echo " [*] Waiting for message. To exit press CTRL+C " . PHP_EOL;
$callback = function ($msg) {
echo " [x] Receive " . date('Y-m-d H:i:s') . ':' . $msg->body . "
";
};
// Consume from the expired queue
$channel->basic_consume('expireQueue', "", false, true, false, false, $callback);
while (count($channel->callbacks)) {
$channel->wait();
}
$channel->close();
$connection->close();
}Important Notes
If the expireExchange is of type direct, you must provide a binding key (the dead‑letter routing key) when binding queues; otherwise, declare the exchange as fanout to avoid specifying a binding key.
The fanout exchange broadcasts messages to all bound queues, which is suitable for simple delayed‑task scenarios.
Execution Result
After running the sender, you will see a "Sent" log with a timestamp. The receiver then prints a "Receive" log when the delayed message is delivered from the dead‑letter queue after the configured timeout.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Open Source Tech Hub
Sharing cutting-edge internet technologies and practical AI resources.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
