四、Rabbit消息可靠性投递

本文详细介绍了RabbitMQ中确保消息可靠投递的机制,包括生产者到交换机的confirmCallback、交换机到队列的returnCallback以及消费者端的ACK确认。通过设置回调函数和配置参数,实现消息发送失败的记录与补偿,同时讨论了消息确认机制对系统性能的影响。此外,还展示了如何处理异常情况,并提供了相应的测试示例代码。

Rabbit消息可靠性投递

什么是消息的可靠性投递

  • 保证消息百分百发送到消息队列中去
  • 详细
    • 保证mq节点成功接受消息
    • 消息发送端需要接受到mq服务端接受到消息的确认应答
    • 完善的消息补偿机制,发送失败的消息可以再感知并二次处理

RabbitMQ消息投递路径

  • 生产者–>交换机->队列->消费者
  • 通过两个的点控制消息的可靠性投递
    • 生产者到交换机
      • 通过confirmCallback
    • 交换机到队列
      • 通过returnCallback

注意

开启消息确认机制以后,保证了消息的准确送达,但由于频繁的确认交互, rabbitmq 整体效率变低,吞吐量下降严重,不是非常重要的消息真心不建议用消息确认机制

生产者到交换机的可靠性投递confirmCallback

首先要在配置文件中添加 spring.rabbitmq.publisher-confirm-type: correlated 这个配置,开启confirmCallback功能

生产者测试代码

    @Test
    void testConfirmCallback(){
        template.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             *
             * @param correlationData 配置
             * @param ack 交换机是否收到消息,true是成功,false是失败
             * @param cause 失败原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("ConfirmCallback=======");
                System.out.println("correlationData=======>"+correlationData);
                System.out.println("ack=======>"+ack);
                System.out.println("cause=======>"+cause);

                if (ack) {
                    System.out.println("发送成功");
                    // TODO 更新数据库消息状态
                }else {
                    System.out.println("发送失败,记录到日志或者数据库");
                    // TODO 更新数据库消息状态
                }
            }
        });
        // TODO 数据库新增一条记录,状态是发送

        // 发送消息
//        template.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"order.new","新订单" );
        // 模拟异常
        template.convertAndSend(RabbitMQConfig.EXCHANGE_NAME+"exTest","order.new","新订单" );

    }

我做了一次异常测试,结果如下

ConfirmCallback=======
correlationData=======>null
ack=======>false
cause=======>channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'order_exchangeexTest' in vhost 'devTest', class-id=60, method-id=40)
发送失败,记录到日志或者数据库

cause字段给了异常原因,没有 'order_exchangeexTest’这个交换机。

交换机到队列可靠性投递returnCallback

交换机到队列

  • 通过returnCallback
  • 消息从交换器发送到对应队列失败时触发
  • 两种模式
    • 交换机到队列不成功,则丢弃消息(默认)
    • 交换机到队列不成功,返回给消息生产者,触发returnCallback

配置文件设置

# 开启returnCallback
spring.rabbitmq.template.mandatory=true
#为true,则交换机处理消息到路由失败,则会返回给生产者
spring.rabbitmq.template.mandatory=true

测试代码:

    @Test
    void testReturnCallback(){
        template.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
            @Override
            public void returnedMessage(ReturnedMessage returnedMessage) {
                int replyCode = returnedMessage.getReplyCode();
                String replyText = returnedMessage.getReplyText();
                String returnedStr = returnedMessage.toString();
                System.out.println("replyCode:::"+replyCode);
                System.out.println("replyText:::"+replyText);
                System.out.println("returnedStr:::"+returnedStr);
            }
        });

        // 异常测试 将routingKey 改成asp.order.new
        template.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"asp.order.new","新订单ReturnCallback" );
    }

由于我之前在RabbitMQConfig配置类中 BindingBuilder.bind(queue).to(exchange).with("order.#").noargs();

这样把order.头的路由key都能正常接收,所以模拟异常的时候我在路由key的前面加上了asp让他进不去,出现异常

异常测试结果如下:

replyCode:::312
replyText:::NO_ROUTE
returnedStr:::ReturnedMessage [message=(Body:'新订单ReturnCallback' 。。。。。。

这里提示了没有这个路由

消息确认机制ACK

RabbitMQ的ACK介绍

  • 消费者从RabbitMQ收到消息并处理完成后,反馈给RabbitMQ,RabbitMQ收到反馈后才将此消息从队列中删除
  • 消费者在处理消息出现了网络不稳定、服务器异常等现象,那么就不会有ACK反馈,RabbitMQ会认为这个消息没有正常消费,会将消息重新放入队列中
  • 只有当消费者正确发送ACK反馈,RabbitMQ确认收到后,消息才会从RabbitMQ服务器的数据中删除。
  • 消息的ACK确认机制默认是打开的,消息如未被进行ACK的消息确认机制,这条消息被锁定Unacked

确认方式

​ 自动确认(默认)

​ 手动确认manual

添加配置

spring.rabbitmq.listener.simple.acknowledge-mode: manual

消息监听类 OrderMQListener类代码

@Component
@RabbitListener(queues = "order_queue")
public class OrderMQListener {

    @RabbitHandler
    public void messageHandler(String body, Message message, Channel channel) throws IOException {
        long msgTag = message.getMessageProperties().getDeliveryTag();
        System.out.println("msgTag="+msgTag);
        System.out.println("message="+message.toString());
        System.out.println("body="+body);

        // 告诉broker,消息已被确认
        //channel.basicAck(msgTag,false);

        // 告诉broker,消息拒绝确认
        channel.basicNack(msgTag,false,true);

    }

}

我这里做了拒绝测试,消息不断重新放入队列再次进行消费,测试结果如下:

在这里插入图片描述

开启服务的一秒就有上千次重新放回队列重新消费,我这里是因为没对消息做任何正常处理直接拒绝才会这样。

  • 前面的Tag表示消息投递序号,每次消费消息或者消息重新投递后, deliveryTag都会增加

  • basicNack和basicReject介绍

    • basicReject一次只能拒绝接收一个消息,可以设置是否requeue。
    • basicNack方法可以支持一次0个或多个消息的拒收,可以设置是否requeue。
  • 人工审核异常消息

    • 设置重试阈值,超过后确认消费成功,记录消息,人工处理
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值