0Pricing
RabbitMQ Messaging & Async Systems · レッスン

Dead Letter Exchange(DLX)

配信や処理に失敗したメッセージを扱う Dead Letter Exchange を構成します。堅牢なエラーハンドリングとメッセージ再処理戦略を実装します。

「Dead Letter Exchange(DLX)」はCoddyKit上の無料RabbitMQ Messaging & Async Systemsレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはRabbitMQ Messaging & Async Systems学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 RabbitMQ Messaging & Async Systemsコースには全4レッスンが含まれています。

このレッスンの一部はまだ翻訳されておらず、英語で表示されています。

What is a Dead Letter Exchange?

In message queuing, sometimes messages can't be processed successfully. They might be invalid, or the consumer might fail. What happens to these 'problem' messages?

A Dead Letter Exchange (DLX) in RabbitMQ is a mechanism for handling messages that cannot be delivered or processed. It's like a special mailbox for 'undeliverable' mail.

When Messages Go Astray

Messages are 'dead-lettered' (sent to a DLX) under specific conditions:

  • Rejected by Consumer: A consumer explicitly rejects a message (using basic.reject or basic.nack) and sets requeue to false.
  • Message TTL Expiration: A message's Time-To-Live (TTL) expires while it's in a queue.
  • Queue Length Limit: The queue reaches its maximum length, and new messages cause older ones to be dropped.
  • Message Not Routable: If a message is published to an exchange with a mandatory flag, but it cannot be routed to any queue, it can also be dead-lettered. (Less common, but possible)

Basic DLX Configuration

To use a DLX, you need to configure your main queue to point to it. This is done when you declare the main queue:

  • x-dead-letter-exchange: The name of the exchange to which dead-lettered messages will be sent.
  • x-dead-letter-routing-key: An optional routing key to use when publishing to the DLX. If not set, the original routing key of the dead-lettered message is used.

You also need to declare the DLX itself and a 'dead-letter queue' (DLQ) that's bound to the DLX.

DLX Setup & Producer (Nack Example)

Let's set up a main queue that sends rejected messages to a DLX. This producer declares a DLX, a dead-letter queue (DLQ), and binds them. Then, it declares our main_queue, configuring it to use the DLX.

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

public class DLXProducerNack {
    private static final String MAIN_QUEUE_NAME = "main_nack_queue";
    private static final String DLX_EXCHANGE_NAME = "dlx_exchange";
    private static final String DLQ_QUEUE_NAME = "dlq_nack_queue";
    private static final String DLQ_ROUTING_KEY = "dlq_nack_key";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");

        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {

            // 1. Declare DLX and DLQ
            channel.exchangeDeclare(DLX_EXCHANGE_NAME, "topic", true); 
            channel.queueDeclare(DLQ_QUEUE_NAME, true, false, false, null);
            channel.queueBind(DLQ_QUEUE_NAME, DLX_EXCHANGE_NAME, DLQ_ROUTING_KEY);

            // 2. Declare main queue with DLX arguments
            Map<String, Object> args = new HashMap<>();
            args.put("x-dead-letter-exchange", DLX_EXCHANGE_NAME);
            args.put("x-dead-letter-routing-key", DLQ_ROUTING_KEY);

            channel.queueDeclare(MAIN_QUEUE_NAME, true, false, false, args);

            String message = "Message to be rejected!";
            channel.basicPublish("", MAIN_QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
            System.out.println(" [x] Sent '" + message + "' to " + MAIN_QUEUE_NAME);
        }
    }
}

Consumer with Basic Reject

This consumer listens to the main_nack_queue. When it receives a message, it uses basicReject(deliveryTag, false) to reject it without requeueing. This sends the message to our configured DLX. The consumer also listens to the DLQ to show the dead-lettered message.

Run the producer from the previous scene, then run this consumer.

import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class DLXConsumerNack {
    private static final String MAIN_QUEUE_NAME = "main_nack_queue";
    private static final String DLQ_QUEUE_NAME = "dlq_nack_queue";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");

        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        // Consumer for the main queue, will reject messages
        System.out.println(" [*] Waiting for messages in " + MAIN_QUEUE_NAME + ".");

        DeliverCallback mainQueueDeliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received from " + MAIN_QUEUE_NAME + ": '" + message + "'");
            System.out.println(" [x] Rejecting message. It should go to DLQ.");
            channel.basicReject(delivery.getEnvelope().getDeliveryTag(), false); // Don't requeue
        };
        channel.basicConsume(MAIN_QUEUE_NAME, false, mainQueueDeliverCallback, consumerTag -> {});

        // Consumer for the dead-letter queue
        System.out.println(" [*] Waiting for messages in " + DLQ_QUEUE_NAME + " (DLQ).");

        DeliverCallback dlqDeliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [DLQ] Received dead-lettered message: '" + message + "'");
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        };
        channel.basicConsume(DLQ_QUEUE_NAME, false, dlqDeliverCallback, consumerTag -> {});
    }
}

DLX with Message TTL (Producer)

Messages can also be dead-lettered if they expire. This producer sends a message with a short Time-To-Live (TTL) of 5 seconds to a main queue. If no consumer processes it within that time, it will be dead-lettered.

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.AMQP;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

public class DLXTTLProducer {
    private static final String MAIN_QUEUE_NAME = "main_ttl_queue";
    private static final String DLX_EXCHANGE_NAME = "dlx_exchange"; // Reuse DLX
    private static final String DLQ_QUEUE_NAME = "dlq_ttl_queue"; // Separate DLQ for TTL
    private static final String DLQ_ROUTING_KEY = "dlq_ttl_key";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");

        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {

            // Declare DLX and DLQ (if not already done)
            channel.exchangeDeclare(DLX_EXCHANGE_NAME, "topic", true);
            channel.queueDeclare(DLQ_QUEUE_NAME, true, false, false, null);
            channel.queueBind(DLQ_QUEUE_NAME, DLX_EXCHANGE_NAME, DLQ_ROUTING_KEY);

            // Declare main queue with DLX arguments and message TTL
            Map<String, Object> args = new HashMap<>();
            args.put("x-dead-letter-exchange", DLX_EXCHANGE_NAME);
            args.put("x-dead-letter-routing-key", DLQ_ROUTING_KEY);
            args.put("x-message-ttl", 5000); // Message TTL of 5 seconds

            channel.queueDeclare(MAIN_QUEUE_NAME, true, false, false, args);

            String message = "This message will expire!";
            channel.basicPublish("", MAIN_QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
            System.out.println(" [x] Sent '" + message + "' to " + MAIN_QUEUE_NAME);
            System.out.println(" [x] Message has a TTL of 5 seconds. It will dead-letter if not consumed.");
        }
    }
}

Observing TTL Dead-Lettering (Consumer)

This consumer only listens to the dead-letter queue (dlq_ttl_queue). Run the producer from the previous scene, then run this consumer. You will see the message appear in the DLQ after its 5-second TTL expires, even if no consumer rejects it.

import com.rabbitmq.client.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class DLXTTLConsumer {
    private static final String DLQ_QUEUE_NAME = "dlq_ttl_queue";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");

        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        // Consumer for the dead-letter queue for TTL messages
        channel.queueDeclare(DLQ_QUEUE_NAME, true, false, false, null);
        System.out.println(" [*] Waiting for dead-lettered messages in " + DLQ_QUEUE_NAME + ".");
        System.out.println("     Run DLXTTLProducer first, then wait 5 seconds.");

        DeliverCallback dlqDeliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [DLQ] Received expired dead-lettered message: '" + message + "'");
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        };
        channel.basicConsume(DLQ_QUEUE_NAME, false, dlqDeliverCallback, consumerTag -> {});
    }
}

DLX Routing Keys Deep Dive

When a message is dead-lettered, it's published to the DLX. The routing key used for this publication is important:

  • By default, the original routing key of the message is used.
  • If you specify x-dead-letter-routing-key when declaring the main queue, this new key will override the original one.

This allows you to route dead-lettered messages to different dead-letter queues based on their original context or specific error types.

Reprocessing Dead-Lettered Messages

The purpose of a DLX is not just to store failed messages, but to provide a pathway for recovery. Common strategies for handling messages in the DLQ include:

  • Manual Intervention: Inspecting messages in the DLQ and manually re-publishing them after fixing the underlying issue.
  • Logging & Alerting: Simply logging the dead-lettered message details and triggering alerts for investigation.
  • Automated Retry Service: A dedicated consumer for the DLQ that attempts to reprocess messages after a delay, possibly with an exponential backoff.
  • Separate Error Service: Routing dead-lettered messages to a specific service designed solely for error handling and reporting.

DLX Knowledge Check

Which of the following conditions can lead to a message being dead-lettered by RabbitMQ?

Recap: Dead Letter Exchanges

In this lesson, we explored Dead Letter Exchanges (DLX) in RabbitMQ. You learned:

  • DLX provides a robust error-handling mechanism for unprocessable messages.
  • Messages are dead-lettered due to consumer rejection, TTL expiration, or queue length limits.
  • You configure a main queue with x-dead-letter-exchange and x-dead-letter-routing-key.
  • Practical examples showed how to set up DLX for rejected and expired messages.
  • We discussed strategies for reprocessing messages from a dead-letter queue.

DLX is a crucial tool for building resilient and fault-tolerant messaging systems.

よくある質問

「Dead Letter Exchange(DLX)」レッスンは無料ですか?

はい。「Dead Letter Exchange(DLX)」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、RabbitMQ Messaging & Async Systemsコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 RabbitMQ Messaging & Async Systemsコースには全4レッスンが含まれています。

「Dead Letter Exchange(DLX)」で何を学びますか?

配信や処理に失敗したメッセージを扱う Dead Letter Exchange を構成します。堅牢なエラーハンドリングとメッセージ再処理戦略を実装します。 ブラウザで直接実行するハンズオンコードでRabbitMQ Messaging & Async Systemsを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

RabbitMQ Messaging & Async Systemsを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのRabbitMQ Messaging & Async Systemsは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「Dead Letter Exchange(DLX)」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このRabbitMQ Messaging & Async Systemsレッスンでコードを書いて実行できますか?

はい。すべてのRabbitMQ Messaging & Async Systemsレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Headers Exchange の詳細
  2. Exchange 間の Binding
  3. Dead Letter Exchange(DLX)
  4. ルーティング不能メッセージのためのAlternate Exchange
← RabbitMQ Messaging & Async Systemsに戻る