RabbitMQ高级特性----生产者确认机制

题记:在Java微服务开发中,对于一个功能需要调用另一个服务下的功能才能实现的情况,我们通常会使用异步调用取代同步调用,进而实现增强业务的可拓展性和实现故障隔离以及流量削峰填谷的目的。而消息队列就是异步调用的解决方案之一。不过在使用消息队列实现异步调用的时候,可能会出现消息无法传递到位进而导致业务信息出现差异的情况,因此消息的传递的可靠性就显得尤为重要。

传递消息的流程:

要保障消息传递的可靠性,我们可以从消息队列的每一步分析,以RabbitMQ为例,其发送消息的流程大致如下图所示:

不难发现消息传递一共会经历三名角色,分别是消息发送者,MQ和消息接收者。因此我们要保证消息成功传递并被正确处理就需要保证这三者的可靠性。

发送者可靠性:

1.1生产者重连

首先第一种情况,就是生产者发送消息时,出现了网络故障,导致与MQ的连接中断。为了解决这个问题,SpringAMQP提供的消息发送时的重试机制。即:当RabbitTemplate与MQ连接超时后,多次重试。

我们可以在消息发送者的yml配置文件中加入如下信息:

复制代码
spring:
  rabbitmq:
    connection-timeout: 1s # 设置MQ的连接超时时间
    template:
      retry:
        enabled: true # 开启超时重试机制,默认时关闭的
        initial-interval: 1000ms # 失败后的初始等待时间
        multiplier: 2 # 失败后下次的等待时长倍数,下次等待时长 = initial-interval * multiplier
        max-attempts: 3 # 最大重试次数

我们可以在消息发送者工程下创建一个Test模拟向rabbitMQ发送消息(在此之前我们需要把rabbiitMQ关闭或是断掉网络)

复制代码
    @Test
    public void TestTimeOutPublish(){
        rabbitTemplate.convertAndSend("simple.queue","Hello,world");
    }

代码执行后会是这样的效果:

通过日志我们可以观察到一共重连了三次,与我们在yml文件中配置的max-attempt属性一致。需要注意的是,这里等待的时间还需要再加上connect-timeout这一判定连接超时的配置。

1.2生产者确认机制

在保证网络畅通并且RabbitMQ服务正确启动了的前提下,我们就可以成功将消息发送到MQ中了。

不过仍有少数情况下会出现消息进入mq后丢失的情况:

  • 无法找到指定的exchange,大部分情况下就是交换机名称出错
  • exchange无法正确路由到queue,可能是routeKey错误导致的

针对上述情况,RabbitMQ提供了生产者消息确认机制,包括Publisher ConfirmPublisher Return两种。在开启确认机制的情况下,当生产者发送消息给MQ后,MQ会根据消息处理的情况返回不同的回执

当我们在yml文件中增加了相应的配置后流程就变得如下图所示:

配置完成之后:

  • 对于所有成功投递如Mq中的消息都会返回Ack,表示投递成功。
  • 对于投递成功但路由失败的消息,会返回Publish Return并返回Ack
  • 除此之外的所有消息都返回NAck,表示消息投递失败

需要注意的是,对于临时消息而言是进入队列Publisher Comfirm就是返回Ack,如果是持久消息则需要写入磁盘后才是返回Ack。

实现方式如下:

publisher消息发送者yml配置文件中加入:

复制代码
spring:
  rabbitmq:
    publisher-confirm-type: correlated # 开启publisher confirm机制,并设置confirm类型
    publisher-returns: true # 开启publisher return机制

pubulisher-confirm-type一共有三种属性提供选择:

  1. none:默认选项,关闭
  2. correlated:异步回调返回回执
  3. simple:同步阻塞等待mq回执

并且我们还需要在配置类中配置confrim Return报文:

复制代码
public class MqConfig {
    private final RabbitTemplate rabbitTemplate;

    @PostConstruct  //在注入rabbitTemplate依赖后执行
    public void init(){
        rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
            @Override
            public void returnedMessage(ReturnedMessage returned) {
                log.error("触发return callback,");
                log.debug("exchange: {}", returned.getExchange());
                log.debug("routingKey: {}", returned.getRoutingKey());
                log.debug("message: {}", returned.getMessage());
                log.debug("replyCode: {}", returned.getReplyCode());
                log.debug("replyText: {}", returned.getReplyText());
            }
        });
    }
}

对于不同的业务我们有不同的处理方案,因此CallbackComfirm需要在每次发送方法前定义,并且由作为convertMessage方法的参数,如下所示:

复制代码
@Test
void testPublisherConfirm() {
    // 1.创建CorrelationData
    CorrelationData cd = new CorrelationData();
    // 2.给Future添加ConfirmCallback
    cd.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {
        @Override
        public void onFailure(Throwable ex) {
            // 2.1.Future发生异常时的处理逻辑,基本不会触发
            log.error("send message fail", ex);
        }
        @Override
        public void onSuccess(CorrelationData.Confirm result) {
            // 2.2.Future接收到回执的处理逻辑,参数中的result就是回执内容
            if(result.isAck()){ // result.isAck(),boolean类型,true代表ack回执,false 代表 nack回执
                log.debug("发送消息成功,收到 ack!");
            }else{ // result.getReason(),String类型,返回nack时的异常描述
                log.error("发送消息失败,收到 nack, reason : {}", result.getReason());
            }
        }
    });
    // 3.发送消息
    rabbitTemplate.convertAndSend("simple.direct", "simple", "hello,RabbiteMQ", cd);
}

我们可以在onSuccess和onFailure中编写我们对这一消息发送成功以及失败情况的对应处理。

如果关于上述有其他更好的建议以及疑问,欢迎留言,我将尽快回复。

相关推荐
ALex_zry14 小时前
Redis Cluster 分布式缓存架构设计与实践
redis·分布式·缓存
为什么不问问神奇的海螺呢丶16 小时前
n9e categraf rabbitmq监控配置
分布式·rabbitmq·ruby
TTBIGDATA20 小时前
【Atlas】Atlas Hook 消费 Kafka 报错:GroupAuthorizationException
hadoop·分布式·kafka·ambari·hdp·linq·ranger
m0_687399841 天前
telnet localhost 15672 RabbitMQ “Connection refused“ 错误表示目标主机拒绝了连接请求。
分布式·rabbitmq
陌上丨1 天前
生产环境分布式锁的常见问题和解决方案有哪些?
分布式
新新学长搞科研1 天前
【智慧城市专题IEEE会议】第六届物联网与智慧城市国际学术会议(IoTSC 2026)
人工智能·分布式·科技·物联网·云计算·智慧城市·学术会议
Ronin3051 天前
日志打印和实用 Helper 工具
数据库·sqlite·rabbitmq·文件操作·uuid生成
泡泡以安1 天前
Scrapy分布式爬虫调度器架构设计说明
分布式·爬虫·scrapy·调度器
没有bug.的程序员1 天前
RocketMQ 与 Kafka 深度对垒:分布式消息引擎内核、事务金融级实战与高可用演进指南
java·分布式·kafka·rocketmq·分布式消息·引擎内核·事务金融
上海锟联科技1 天前
250MSPS DAS 在地铁监测中够用吗?——来自上海锟联科技的工程实践
分布式·科技·分布式光纤传感·das解调卡·光频域反射·das