在Idea上实践RabbitMQ之生产者可靠性实现

生产者可靠性实现



前言

在发送消息时我们或许想过消息可能会被丢弃。从根据消息传输流程从前往后,我们从生产者开始。生产者有可能在发送时把消息弄丢。当消息传到MQ时,MQ本身也有可能把消息弄丢。而消息从队列送到消费者手里时,消费者也有可能把传来的消息弄丢。因此保证消息可靠性是很重要的。RabbitMQ也正是基于这三种可能性依次提出了解决办法。

生产者可靠性

  1. 生产者重连 :连接MQ失败的重试,是线程阻塞式的等待重连。(十分影响性能,程序发布时一定要禁用)
    直接在配置中配置:

    spring:
      rabbitmq:
        host: IP地址
        port: 5672
        virtual-host: 主机名
        username: 用户名
        password: 密码
        connection-timeout: 200ms #设置MQ连接超时的时间
        template:
          retry:
            enabled: true #开启超时重试机制
            initial-interval: 1000ms #失败后的初始等待时间
            multiplier: 1 #失败后下次的等待时长倍数,下次等待时长
            max-attempts: 2 #最大重试次数
    
  2. 生产者确认
    开启生产者确认机制后,MQ在成功收到消息后会返回确认消息给生产者四种结果:

    • 消息投递到了MQ,但是路由失败。此时会通过PublisherReturn返回路由异常原因,然后返回ACK,告知投递成功
    • 临时消息投递到了MQ,并且入队成功,返回ACK,告知投递成功
    • 持久消息投递到了MQ,并且入队完成持久化,返回ACK,告知投递成功
    • 其它情况都会返回NACK,告知投递失败
    spring:
      rabbitmq:
        host: IP地址
        port: 5672
        virtual-host: 主机名
        username: 用户名
        password: 密码
        publisher-confirm-type: correlated #开启PublishConfirm确认机制 设置Confirm类型
        publisher-returns: true #开启PublishReturn机制
    

    2.1 Publish Return

    专门返回路由失败的消息,路由失败的原因基本是由开发者导致的(开发者配置不正确的yml、队列不存在、格式不对等)。既然原因一致,那么失败信息除了ID之外高度一致,因此我们可以统一设置Return回调函数的模版格式以提高性能。
    我们可以在RabbitMQ模版被注册到容器后立即设置Return回调函数格式,这样就能够在程序运行前预先设定好Return回调函数的模版。为什么要设置Return为回调函数?因为回调是异步执行的,不会阻塞当前线程。

    那么应该如何统一处理?
    我们最好在RabbitMQ模版被注册到容器后马上设置Return回调函数格式,这样能够在程序运行前就提前设定好Return回调函数的模版。
    在SpringBoot容器创建完成时的会扫描实现了ApplicationContextAware 接口的类,并把容器传给类中实现接口的方法。因此我们只需要在这个方法里调用即将Return回调函数统一模版设置即可大功告成。
    这样只要消息发送失败就会触发我们重写的Return回调函数

    package com.example.publisher.config;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.amqp.core.ReturnedMessage;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @Slf4j
    public class MqConfirmConfig implements ApplicationContextAware{
    
            @Override
            public void setApplicationContext(ApplicationContext applicationContext)throws BeansException {
                    RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
                    //设置ReturnCallback回调函数
                    rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
                            @Override
                            public void returnedMessage(ReturnedMessage r) {
                                    log.debug("收到消息的return callback,exchange:{],key:f},msg:{},code:{},text:{}",
                                            r.getExchange(),r.getRoutingKey(),r.getMessage(),r.getReplyCode()
                                    ,r.getReplyText());
                            }
                    });
            }
    }
    

    2.2 Publish Confirm

    确认类型publisher-confirm-type说明:
    none:关闭confirm机制
    simple:同步阻塞等待MQ的回执消息
    correlated:MQ异步回调方式返回回执消息

    package com.example.publisher;
    
    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.Test;
    import org.springframework.amqp.rabbit.connection.CorrelationData;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.util.concurrent.ListenableFutureCallback;
    
    import java.util.UUID;
    
    @SpringBootTest
    @Slf4j
    class PublisherApplicationTests {
    
        @Autowired
        private RabbitTemplate rabbitTemplate;
    
        @Test
        void contextLoads() throws InterruptedException {
        	//1、创建CorrelationData
            CorrelationData cd = new CorrelationData(UUID.randomUUID().toString());
            //2.添加ConfirmCallback
            cd.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {
                @Override
                public void onFailure(Throwable ex) {
                    log.error("消息回调失败", ex);
                }
    
                @Override
                public void onSuccess(CorrelationData.Confirm result) {
                    log.debug("收到confirm callback回执");
                    if (result.isAck()) {
                        //消息发送成动
                        log.debug("消息发送成功,收到ack");
                    } else {
                        //消总发送失败
                        log.error("消息发送失败,收到nack,原因:{}", result.getReason());
                    }
                }
            });
            rabbitTemplate.convertAndSend("xxx.direct", "xxx", "hello", cd);
            Thread.sleep(2000);
        }
    
    }
    

    这是我随便给不存在的交换机发的消息,最后能在控制台看到类似这种消息在这里插入图片描述 注意:我设置的日志格式等级为debug 我采用的是Lombok+SpringBoot默认的日志框架

    logging:
      pattern:
        dateformat: MM-dd HH-mm-ss:SSS
        level: DEBUG
    
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值