RabbitMQ如何保证消息可靠性

目录

[一、RabbitMQ可靠性其实是 三个阶段的问题:](#一、RabbitMQ可靠性其实是 三个阶段的问题:)

二、生产者如何保证消息可靠投递

1.开启生产者确认机制

2.ConfirmCallback(确认是否到达交换机)

[3 发送失败重试(最多3次)](#3 发送失败重试(最多3次))

4、ReturnCallback(防止消息路由失败)

三、MQ如何保证消息不丢失

[1 队列持久化](#1 队列持久化)

[2 交换机持久化](#2 交换机持久化)

[3 消息持久化](#3 消息持久化)

四、消费者如何保证消息不丢

[1 开启手动ACK](#1 开启手动ACK)

[2 消费者代码](#2 消费者代码)

五、如何防止消息重复消费

[1 Redis去重(最常见)](#1 Redis去重(最常见))

[2 数据库唯一索引](#2 数据库唯一索引)

六.消费消息失败写入死信队列

1.私信配置

1.1死信交换机

1.2死信队列

1.3绑定死信队列

2.业务队列配置

3.消费代码

4.死信队列消费者


RabbitMQ是一个流行的开源消息代理,它提供了可靠的消息传递机制,广泛应用于分布式系统和微服务架构中。在现代应用中,确保消息的可靠性至关重要,以防止消息丢失和重复处理。本文将详细探讨RabbitMQ如何通过多种机制保证消息的可靠性,并提供相关的最佳实践。

一、RabbitMQ可靠性其实是 三个阶段的问题:

生产者 → MQ → 消费者

必须保证:

1 生产者发送消息不丢

2 MQ存储消息不丢

3 消费者消费消息不丢

因此完整方案:

生产者可靠投递

  • MQ持久化

  • 消费者可靠消费

  • 幂等控制

二、生产者如何保证消息可靠投递

生产者主要解决:

复制代码
消息发送失败
消息未到达交换机
消息未路由到队列

需要使用两个机制:

复制代码
Publisher Confirm
Return Callback

1.开启生产者确认机制

复制代码
spring:
  rabbitmq:
    publisher-confirm-type: correlated
    publisher-returns: true

2.ConfirmCallback(确认是否到达交换机)

如果消息 没有到达交换机 会触发。

复制代码
@Component
public class RabbitConfirmCallback implements RabbitTemplate.ConfirmCallback {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {

        if (ack) {

            System.out.println("消息成功到达交换机: " + correlationData.getId());

        } else {

            System.out.println("消息发送失败: " + cause);

            // 重试机制
            retrySend(correlationData);
        }
    }
}

3 发送失败重试(最多3次)

这个阶段是发送到交换机要重试;

复制代码
public void retrySend(CorrelationData correlationData){

    ReliableMessage msg = messageCache.get(correlationData.getId());

    int retry = msg.getRetryCount();

    if(retry < 3){

        msg.setRetryCount(retry + 1);

        rabbitTemplate.convertAndSend(
                msg.getExchange(),
                msg.getRoutingKey(),
                msg.getMessage(),
                new CorrelationData(msg.getId())
        );

    }else{

        // 持久化数据库
        failMessageService.save(msg);
    }
}

4、ReturnCallback(防止消息路由失败)

场景:

交换机存在

但没有队列匹配 routingKey

复制代码
rabbitTemplate.setReturnsCallback(returned -> {

    System.out.println("消息路由失败");

    System.out.println("exchange=" + returned.getExchange());

    System.out.println("routingKey=" + returned.getRoutingKey());

});

三、MQ如何保证消息不丢失

MQ层面要解决:

复制代码
RabbitMQ重启
服务器崩溃

需要 持久化机制。

1 队列持久化

复制代码
@Bean
public Queue orderQueue(){

    return new Queue("order.queue", true);
}

参数:

true = durable

2 交换机持久化

复制代码
@Bean
public DirectExchange orderExchange(){

    return new DirectExchange("order.exchange", true, false);
}

3 消息持久化

发送消息时设置:

复制代码
MessageProperties props = new MessageProperties();

props.setDeliveryMode(MessageDeliveryMode.PERSISTENT);

四、消费者如何保证消息不丢

消费者必须使用:

复制代码
手动ACK

1 开启手动ACK

复制代码
spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: manual

2 消费者代码

复制代码
@RabbitListener(queues = "order.queue")
public void receive(Message message, Channel channel) throws Exception {

    long tag = message.getMessageProperties().getDeliveryTag();

    try {

        String msg = new String(message.getBody());

        System.out.println("收到消息: " + msg);

        // 执行业务
        process(msg);

        // 手动确认
        channel.basicAck(tag,false);

    } catch (Exception e) {

        // 消费失败
        channel.basicNack(tag,false,true);
    }
}

解释:

方法 作用
basicAck 确认消息
basicNack 拒绝消息
requeue=true 重新入队

五、如何防止消息重复消费

RabbitMQ保证:

至少消费一次

但不能保证:

只消费一次

所以必须做:

幂等控制

1 Redis去重(最常见)

复制代码
public void process(String msg){

    String msgId = getMsgId(msg);

    if(redisTemplate.hasKey(msgId)){
        return;
    }

    // 执行业务
    createOrder(msg);

    redisTemplate.opsForValue().set(msgId,"1");
}

2 数据库唯一索引

订单号 unique

ALTER TABLE orders ADD UNIQUE (order_no);

如果重复消费:数据库直接拒绝

六.消费消息失败写入死信队列

消费者消费消息失败有两种解决方式,一种是重试消费,另一种就是写入死信队列,死信队列的监听者来处理消息;

1.私信配置

1.1死信交换机

复制代码
@Bean
public DirectExchange deadLetterExchange() {
    return new DirectExchange("dlx.exchange", true, false);
}

1.2死信队列

复制代码
@Bean
public Queue deadLetterQueue() {
    return new Queue("order.dlq", true);
}

1.3绑定死信队列

复制代码
@Bean
public Binding deadLetterBinding() {
    return BindingBuilder.bind(deadLetterQueue())
            .to(deadLetterExchange())
            .with("dlx.routingKey");
}

2.业务队列配置

关键配置:

复制代码
x-dead-letter-exchange
x-dead-letter-routing-key

代码:

复制代码
@Bean
public Queue orderQueue() {

    Map<String, Object> args = new HashMap<>();

    // 指定死信交换机
    args.put("x-dead-letter-exchange", "dlx.exchange");

    // 指定死信routingKey
    args.put("x-dead-letter-routing-key", "dlx.routingKey");

    return new Queue("order.queue", true, false, false, args);
}

3.消费代码

重点在:

复制代码
basicNack(tag,false,false)

第三个参数 false 表示 不重新入队,MQ就会投递到死信队列。

复制代码
@RabbitListener(queues = "order.queue")
public void receive(Message message, Channel channel) throws Exception {

    long tag = message.getMessageProperties().getDeliveryTag();

    try {

        String msg = new String(message.getBody());

        System.out.println("收到消息: " + msg);

        // 模拟业务异常
        int i = 1 / 0;

        // 成功确认
        channel.basicAck(tag, false);

    } catch (Exception e) {

        System.out.println("消费失败,进入死信队列");

        // false = 不重新入队
        channel.basicNack(tag, false, false);
    }
}

4.死信队列消费者

死信队列可以专门监听:

复制代码
@RabbitListener(queues = "order.dlq")
public void deadLetterConsumer(Message message) {

    String msg = new String(message.getBody());

    System.out.println("死信消息: " + msg);

    // 可以做:
    // 1 记录日志
    // 2 存数据库
    // 3 人工处理
}
相关推荐
银河技术9 小时前
TB级文本去重实战:从单机 OOM 到 Spark / Ray 分布式架构的工程演进
分布式·微服务·重构·架构·spark·llm·rag
deepdata_cn10 小时前
从集中式到分布式:Data Mesh颠覆传统数据架构的底层逻辑
分布式·data mesh
5008411 小时前
React Native for OpenHarmony 实战:三方库 react-native-device-uptime 的鸿蒙化适配指南
javascript·分布式·react native·react.js·harmonyos
灯澜忆梦18 小时前
【RabbitMQ #10】 | MQ可靠性
分布式·rabbitmq
醉颜凉20 小时前
Kafka 与 RabbitMQ/RocketMQ 选型对比:场景匹配、性能基准与迁移成本
kafka·消息队列·rabbitmq·rocketmq·中间件选型
逐流人1 天前
Ceph分布式存储集群配置与池管理:从配置优先级到PG、复本池与纠删码池
运维·分布式·ceph·云原生·云计算·rados
天天喝旺仔1 天前
gRPC 底层原理:HTTP/2 多路复用、Protobuf 序列化与四种流模式
分布式·http·微服务·rpc
灯澜忆梦1 天前
【RabbitMQ #5】 | 三大交换机 Fanout ,Direct ,Topic
分布式·rabbitmq·ruby
.冰块.1 天前
Ceph 分布式存储实战(二):存储池管理与 cephx 认证授权
分布式·ceph·rados·纠删码·复本池·cephx认证与用户权限