MQ的消息可靠性

MQ的消息可靠性

在使用消息队列(MQ)时。如果消息消费失败,且重试后仍然失败,就需要进行手动介入处理,以避免消息丢失或业务逻辑错误。这种情况下,通常会将失败的消息存储到一个“死信队列”(Dead Letter Queue,DLQ)中,方便后续手动处理。

场景描述

假设我们有一个电商平台,用户下单后会将订单消息发送到消息队列中,由订单处理服务消费这些消息并完成订单处理。如果订单处理服务在消费消息时失败(例如,数据库连接失败、库存不足等),并且重试后仍然失败,就需要将这些消息发送到死信队列中,以便人工介入处理。


死信队列(DLQ)的实现

RabbitMQ 提供了死信队列的功能,可以通过设置队列的死信交换器(Dead Letter Exchange,DLX)来实现。当消息消费失败且达到最大重试次数后,消息会被自动发送到死信队列中。

示例代码

1. 定义队列和交换器

定义普通队列和死信队列,并设置死信交换器。

import com.rabbitmq.client.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;

public class DeadLetterQueueExample {
    private static final String EXCHANGE_NAME = "order_exchange";
    private static final String QUEUE_NAME = "order_queue";
    private static final String DLX_NAME = "dlx_exchange";
    private static final String DLQ_NAME = "dlq_queue";

    public static void setupQueues() throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            // 创建普通交换器和队列
            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true);
            channel.queueDeclare(QUEUE_NAME, true, false, false, null);
            channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "order_routing_key");

            // 创建死信交换器和队列
            channel.exchangeDeclare(DLX_NAME, BuiltinExchangeType.DIRECT, true);
            channel.queueDeclare(DLQ_NAME, true, false, false, null);
            channel.queueBind(DLQ_NAME, DLX_NAME, "dlq_routing_key");

            // 设置普通队列的死信交换器
            Map<String, Object> args = new HashMap<>();
            args.put("x-dead-letter-exchange", DLX_NAME);
            args.put("x-dead-letter-routing-key", "dlq_routing_key");
            channel.queueDeclare(QUEUE_NAME, true, false, false, args);
        }
    }
}
2. 生产者代码

发送消息到普通队列。

public class Producer {
    private static final String EXCHANGE_NAME = "order_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            String message = "Order 12345";
            channel.basicPublish(EXCHANGE_NAME, "order_routing_key", MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes(StandardCharsets.UTF_8));
            System.out.println(" [x] Sent '" + message + "'");
        }
    }
}
3. 消费者代码

消费消息,如果失败则触发重试,达到最大重试次数后将消息发送到死信队列。

public class Consumer {
    private static final String QUEUE_NAME = "order_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");

            try {
                // 模拟消息处理逻辑
                if (message.equals("Order 12345")) {
                    System.out.println(" [x] Processing failed for '" + message + "'");
                    throw new RuntimeException("Processing failed");
                } else {
                    System.out.println(" [x] Processing succeeded for '" + message + "'");
                }
            } catch (Exception e) {
                System.out.println(" [x] Re-queueing message...");
                channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, true);
            }
        };

        channel.basicConsume(QUEUE_NAME, false, deliverCallback, consumerTag -> {});
    }
}
4. 死信队列消费者代码

手动介入处理死信队列中的消息。

public class DeadLetterConsumer {
    private static final String DLQ_NAME = "dlq_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received dead letter message: '" + message + "'");
            // 手动介入处理逻辑
            System.out.println(" [x] Manually processing message...");
        };

        channel.basicConsume(DLQ_NAME, true, deliverCallback, consumerTag -> {});
    }
}

运行流程

  1. 启动队列和交换器:运行DeadLetterQueueExample.setupQueues(),初始化普通队列和死信队列。
  2. 发送消息:运行Producer,发送消息到普通队列。
  3. 消费消息:运行Consumer,消费消息。如果消息处理失败,会触发重试。如果重试后仍然失败,消息会被发送到死信队列。
  4. 处理死信队列:运行DeadLetterConsumer,手动介入处理死信队列中的消息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT枫斗者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值