RabbitMQ——消息发送的双重保障机制

在分布式系统中,确保消息的可靠传输是至关重要的。无论是处理金融交易、用户互动还是后台数据同步,丢失或重复的消息都可能导致严重的问题。为了增强系统的可靠性,我们可以依赖于生产者的重试机制和生产者确认机制。本文将探讨这两种机制如何共同作用来提高发送者的可靠性。

目录

生产者重试机制

生产者确认机制

实现生产者确认

对应的模块添加配置

定义ReturnCallback

定义ConfirmCallback

生产者确认实现效果


生产者重试机制

问题:网络故障,导致与MQ的连接中断。

解决方案:SpringAMQP提供的消息发送时的重试机制。

即:当RabbitTemplate与MQ连接超时后,多次重试。

方案实现如下:

修改**publisher** 模块的**application.yml**文件,添加如下配置:

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

测试:(故意停掉RabbitMQ服务,验证方案可行性)

效果如下:

解决方案验证成功


生产者确认机制

问题:消息发送到MQ之后丢失的现象。

描述:

MQ内部处理消息的进程发生了异常

生产者发送消息到达MQ后未找到**Exchange(** 往往是程序员编码出问题导致**)**

生产者发送消息到达MQ的**Exchange** 后,未找到合适的**Queue** ,因此无法路由**(** 往往是程序员编码出问题导致**)**

解决方案:RabbitMQ提供了生产者消息确认机制。

即:Publisher Confirm和**Publisher Return** 两种。在开启确认机制的情况下,当生产者发送消息给MQ后,MQ会根据消息处理的情况返回不同的回执

如图所示:

图像解读:

publisher --- exchange2:当消息投递到MQ,但是路由失败时,返回异常信息,同时返回ACK(投递成功)

publisher --- exchange:临时消息投递到了MQ,并且入队成功,返回ACK(投递成功)

publisher --- exchange:持久消息投递到了MQ,并且入队完成持久化,返回ACK (投递成功)

其它情况都会返回NACK(投递失败)


实现生产者确认

对应的模块添加配置

在publisher模块的**application.yml**中添加配置:

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

publisher-confirm-type 有三种模式,推荐使用correlated(回调机制)

定义ReturnCallback

每个**RabbitTemplate** 只能配置一个**ReturnCallback。**

我们在publisher模块定义MqConfig配置类,代码如下:

java 复制代码
package com.itheima.publisher.config;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

@Slf4j
@AllArgsConstructor
@Configuration
public class MqConfig {
    private final RabbitTemplate rabbitTemplate;

    @PostConstruct
    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());
            }
        });
    }
}

定义ConfirmCallback

由于每个消息发送时的处理逻辑不一定相同,因此ConfirmCallback需要在每次发消息时定义。具体来说,是在调用RabbitTemplate中的convertAndSend方法时,多传递一个参数,代码如下:

java 复制代码
@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("user.direct", "q", "hello", cd);
}

生产者确认实现效果

idea控制台展示:

效果解读:

可以看到,由于传递的RoutingKey是错误的,路由失败后,触发了return callback,同时也收到了ack。

当我们修改为正确的RoutingKey以后,就不会触发return callback了,只收到ack。

相关推荐
在未来等你5 小时前
Elasticsearch面试精讲 Day 17:查询性能调优实践
大数据·分布式·elasticsearch·搜索引擎·面试
大数据CLUB8 小时前
基于spark的澳洲光伏发电站选址预测
大数据·hadoop·分布式·数据分析·spark·数据开发
ajax_beijing10 小时前
zookeeper是啥
分布式·zookeeper·云原生
虫小宝16 小时前
返利app的消息队列架构:基于RabbitMQ的异步通信与解耦实践
分布式·架构·rabbitmq
We....17 小时前
Java分布式编程:RMI机制
java·开发语言·分布式
在未来等你17 小时前
Elasticsearch面试精讲 Day 18:内存管理与JVM调优
大数据·分布式·elasticsearch·搜索引擎·面试
We....17 小时前
Java 分布式缓存实现:结合 RMI 与本地文件缓存
java·分布式·缓存
Chasing__Dreams17 小时前
kafka--基础知识点--5.3--producer事务
分布式·kafka
小枫编程17 小时前
Spring Boot 调度任务在分布式环境下的坑:任务重复执行与一致性保证
spring boot·分布式·后端
Hello.Reader19 小时前
Kafka 实现从网络层到日志与位点的“全景拆解”
分布式·kafka