面试题:Rabbitmq怎么保证消息的可靠性?

1.消费端消息可靠性保证

消息确认(Acknowledgements):(自动(默认),手动)

消费者在接收到消息后,默认情况下RabbitMQ会自动确认消息(autoAck=true)。为保证消息可靠性,可以设置autoAck=false,使得消费者在处理完消息后手动发送确认(basicAck)。如果消费者在处理过程中发生异常或者未完成处理就终止运行,那么消息在超时时间内将不会被删除,会再次被RabbitMQ投递给其他消费者。缺点:代码冗余多,容易出现死循环。

死信队列(Dead Letter Queue)

当消息不能被正常消费时(比如达到最大重试次数),可以通过设置TTL(Time To Live)或者死信交换器(Dead Letter Exchange)将消息路由至死信队列,从而有机会后续分析和处理这些无法正常消费的消息。(人工干预)

2.生产端消息可靠性保证:

  1. 消息持久化

当生产者发布消息时,可以选择将其标记为持久化(persistent).这意味着即使 RabbitMQ 服务器重启,消息也不会丢失,因为它们会被存储在磁盘上。(默认就是这)

2.确认(Confirm)机制:(发布者确认(Publisher Confirms)

开启confirm回调 模式后,RabbitMQ会在消息成功写入到磁盘并至少被一个交换器接受后,向生产者发送一个确认(acknowledgement)。若消息丢失或无法投递给任何队列,RabbitMQ将会发送一个否定确认(nack). 生产者可以根据这些确认信号判断消息是否成功送达并采取相应的重试策略。

RabbitMQ作为消息中间件并启用publisher confirms(发布者确认) 与 **publisher returns(发布者退回)**机制时,可以确保消息从生产者到交换机的投递过程得到更准确的状态反馈。

发布者确认-Publisher Confirms

作用: Publisher Confirm机制允许RabbitMQ服务器通知生产者一个消息是否已经被交换机正确接收。当publisher-confirm-type设置为CORRELATED时,RabbitMQ会向生产者发送确认或否定响应,确认消息已到达交换机,但不保证消息已被路由到至少一个队列中。

2.1.配置:

复制代码
spring.rabbitmq.publisher-confirm-type = CORRELATED

setConfirmCallback:

(写到交换机,b为true.没写到交换机b为false)

发布者退回-Publisher Returns

作用: Publisher Return机制用于当消息无法按照路由键规则路由到任何队列时,或者由于其他原因(例如队列满、消息过大等)而被交换机拒绝时,RabbitMQ将消息返回给生产者。

交换机到队列的确认(消息是否正常发送到了其中任何一个队列)

通过实现 ReturnCallback 接口,发送消息失败返回,比如交换机路由不到队列时触发回调:

1.只有消息没有路由到队列的时候,才触发该回调 .

2.只要有一个队列接受到消息了,它就认为成功.

配置

复制代码
spring.rabbitmq.publisher-returns = true

returnedMessage:

(这是找到交换机了,但是路由错了,没有找到对列;这在直连交换机能测出来,广播交换机没路由,只要有绑定的队列就能发送成功)

完整代码

复制代码
@Service
@Slf4j
public class ConfirmProvider {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    public void send(OrderingOk msg) {
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean b, String s) {
                //没找到交换机就会false
                if (b){
                    String id = correlationData.getId();
                    log.info("消息发送成功");
                }else {
                    log.info("消息发送失败");
                }
                }
        });
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int i, String s, String s1, String s2) {
                log.info("消息发送失败");
                log.info("消息主体: {}", message);
                log.info("应答码: {}", i);
                log.info("描述:{}", s);
                log.info("消息使用的交换器 exchange : {}", s1);
                log.info("消息使用的路由键 routing : {}", s2);
            }
        });
        CorrelationData correlationData = new CorrelationData("980520");
        rabbitTemplate.convertAndSend("d_ex01", "erew", msg, correlationData);
    }
}

代码模版:

复制代码
public void send(OrderingOk msg)
    {
        // 设置确认回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             * 确认消息是否被交换机接收。
             *
             * @param correlationData 包含消息相关数据的对象,用于识别消息的唯一性。
             * @param ack 表示消息是否被交换机确认接收。
             * @param cause 如果消息未被接收,提供未接收的原因。
             */
            @Override //线程B
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                if(ack){
                    //该订单状态  10 -> 20
                    String id = correlationData.getId();
                    // 通过这个id改订单状态

                } else {
                    log.error("{]",cause);
                }
            }
        });
        // 设置退回回调, 之后投递失败的时候才会触发
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /**
             * 记录被交换机退回的消息信息。
             *
             * @param message 消息对象,包含消息体。
             * @param replyCode 返回的响应代码,用于指示退回的原因。
             * @param replyText 返回的响应文本,提供关于退回的详细信息。
             * @param exchange 退回时涉及的交换机名称。
             * @param routingKey 退回时使用的路由键。
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {

                System.out.println("Message was returned: " + new String(message.getBody()));
                System.out.println("Reply code: " + replyCode);
                System.out.println("Reply text: " + replyText);
                System.out.println("Exchange: " + exchange);
                System.out.println("Routing key: " + routingKey);
            }
        });
        //CorrelationData 创建一个关联数据,用于消息的跟踪,大部分都是业务单据的id
        CorrelationData correlationData = new CorrelationData("201408145676676");
        rabbitTemplate.convertAndSend("ordering_ok","",msg,correlationData);

    }
相关推荐
用户8307196840826 分钟前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
用户8307196840822 天前
RabbitMQ vs RocketMQ 事务大对决:一个在“裸奔”,一个在“开挂”?
后端·rabbitmq·rocketmq
初次攀爬者3 天前
RabbitMQ的消息模式和高级特性
后端·消息队列·rabbitmq
初次攀爬者5 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
让我上个超影吧6 天前
消息队列——RabbitMQ(高级)
java·rabbitmq
塔中妖6 天前
Windows 安装 RabbitMQ 详细教程(含 Erlang 环境配置)
windows·rabbitmq·erlang
断手当码农6 天前
Redis 实现分布式锁的三种方式
数据库·redis·分布式
初次攀爬者6 天前
Redis分布式锁实现的三种方式-基于setnx,lua脚本和Redisson
redis·分布式·后端
业精于勤_荒于稀6 天前
物流订单系统99.99%可用性全链路容灾体系落地操作手册
分布式
Ronin3056 天前
信道管理模块和异步线程模块
开发语言·c++·rabbitmq·异步线程·信道管理