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 人工处理
}
相关推荐
云姜.1 小时前
RabbitMQ 核心概念
分布式·rabbitmq
Chan161 小时前
从生产到消费:Kafka 核心原理与实战指南
java·spring boot·分布式·spring·java-ee·kafka·消息队列
鱼骨不是鱼翅2 小时前
个人简历面试复习-----rabbitmq篇(一)
面试·职场和发展·rabbitmq
泯仲2 小时前
RabbitMQ的延迟消息在项目中的运用及实现剖析
开发语言·后端·rabbitmq
yzp-2 小时前
Kafka 原子更新,精确一次消费 Exactly-Once --------- 学习笔记
分布式·学习·kafka
Coder_Boy_2 小时前
从Java虚拟机到分布式中间件:高并发体系全解析(含电商实践细节)
java·jvm·分布式·spring·中间件
瓦中空花2 小时前
如何基于现有平台处理hadoop挂了的情况?
大数据·hadoop·分布式
Thomas.Sir2 小时前
Hadoop YARN:大数据集群的“操作系统”
大数据·hadoop·分布式·yarn
heartbeat..2 小时前
Java操作ZooKeeper 从入门到实战:分布式协调框架核心教程
java·分布式·spring cloud·微服务·java-zookeeper