RabbitMQ高级特性

消息可靠性

消息丢失可能发生在每个交互端,需要保证消息的可靠性需要从生产者、mq服务端、生产者三个方面考虑。

生产者

1、连接重试

网络异常会导致消息发送失败,消费者端可以开启重试机制,保证消息不会因网络波动导致发送失败,但是重试是阻塞式的,会阻塞用户线程,生产需谨慎开启。

java 复制代码
spring:
  rabbitmq:
    # 连接超时
    connection-timeout: 1000
    template:
      retry:
        enabled: true # ✅开启发送重试
        initial-interval: 1000 # 第一次重试等待1s
        multiplier: 1.5 # 退避倍数:下一次等待时间=上一次*1.5
        max-interval: 5000 # 最大等待上限
        max-retries: 3 #最大重试次数

2、开启生产者确认

生产者可以开启消息确认机制和退回机制,确认和回退函数都是设置在rabbitTemplate上,若是只有一个rabbitTemplate默认所有消息放送都会生效。需要指定部分生效可以在容器中注入多个rabbitTemlate。

java 复制代码
spring:
  rabbitmq:
    publisher-confirm-type: correlated  # 消息确认
    publisher-returns: true #消息回退

1、开启 Publisher Confirm

确认机制是确认消息是否到达交换机,有三种模式可选:SIMPLE-同步、CORRELATED-异步、NONE-关闭(默认)。正常情况消息都能到达交换机,即使达到内存阈值,rabbitmq默认会阻塞不接受新消息投递,也不会触发confirm回调。

2、开启publisher-returns

开机消息回退机制后,当消息到达了交换机,但是没有成功路由到队列,消息将被回退。通过回调函数处理回退消息,一般由路由key配置错误导致。回调函数是设置在rabbitTemplate中,一个应用只会设置一个回调函数,它处理所有回退消息。

通过ApplicationContextAware接口完成回调函数设置

发送一个路由key不存在的消息,回调接口将被执行。

消息队列端

1、消息持久化

当生产者发送消息到消息队列后,若没有开启消息持久化,消息还是存储在内存中。若消费者还没消费,mq服务发生宕机就会导致消息丢失。所以要保证消息的可靠性mq服务端需开启持久化。除了消息,mq服务中还有交换机和队列也都需要开启持久化,保证服务端宕机后数据不丢失。

2、Lazy queue

惰性队列是RabbitMQ3.6.0新增的特性,惰性队列默认将消息直接存储到磁盘,内存中只保留少量消息。内存占用量低,性能更好。普通队列默认内存优先,内存满了再刷盘,刷盘是有性能开销。惰性队列更适合高并发场景和大量消息堆积场景。

消费端

消息队列默认将消息投递到消费者后就删除对应消息,若是消费端在处理消失时异常,该条消息就会丢失。要保证消息在消费端不丢失,需要消费者自行开启自行确认ack。告诉消息队列对应消息是否消费成功。springAMQP中已经实现了消息确认机制,通过添加配置就实现不同方式的消息确认。

java 复制代码
spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: auto # manual 手动ACK;auto自动;none不确认

自动模式是通过spring 的aop 对消息处理逻辑做环绕增强,当消息处理正常时自动返回basicAck,若消息处理抛出异常则返回basicNack (requeue=true)进行消息重试。none不确认,消息到达消费端后消息就会从队列中删除存在消息丢失风险。manual手动方式需要消费者自行返回确认状态,例如:

java 复制代码
@RabbitListener(queues = "my.direct.queue")
public void handleMessage(Message message, Channel channel) throws IOException {
    long deliveryTag = message.getMessageProperties().getDeliveryTag();
    try {
        // 1.业务处理逻辑
        log.info("处理消息:{}", new String(message.getBody()));
        // 2.业务成功,手动ACK,告诉MQ可以删除消息
        channel.basicAck(deliveryTag, false);
    } catch (Exception e) {
        log.error("消息处理异常", e);
        // 3.业务失败:basicNack,消息重回队列,重新投递
        // multiple=false:只拒绝当前这条消息
        channel.basicNack(deliveryTag, false, true);
    }
}

异常类型及是否重试如下:

场景 底层调用 requeue 消息行为
消费方法正常返回 basicAck --- 删除消息
普通业务异常 basicNack true(默认) 重回队列重试,无限循环
AmqpRejectAndDontRequeueException basicNack false 拒绝,不回队列,进死信
ImmediateAcknowledgeAmqpException basicAck --- 确认删除,不重试
ImmediateRequeueAmqpException basicNack true 强制重回队列重试
消息转换 / 参数校验异常(还没进消费方法) basicNack false 拒绝,进死信

这三个异常默认都需要业务手动抛出,只有搭配 RetryTemplate 重试恢复器的时候,框架才会自动抛出。

  1. AmqpRejectAndDontRequeueException:业务遇到不可修复毒消息手动抛出;本地重试耗尽,RejectAndDontRequeueRecoverer 也会自动抛出。底层 nack、requeue=false,消息进死信队列。
  2. ImmediateAcknowledgeAmqpException:只能业务手动抛出,底层直接 basicAck,消息直接删除,不重试也不进死信。
  3. ImmediateRequeueAmqpException:业务手动抛出,或者 ImmediateRequeueMessageRecoverer 自动抛出;强制 nack requeue=true,消息重回队列重试,不受 default-requeue-rejected=false 全局配置影响。

消费端本地重试

通过添加配置,可以实现消费端的本地重试

java 复制代码
spring:
  rabbitmq:
    listener:
      simple:
        retry:
          enabled: true # 开启重试
          initial-interval: 1000ms # 初始重试间隔
          multiplier: 2 # 重试间隔乘数
          max-retries: 3 # 最大重试次数
          max-interval: 10000ms # 最大重试间隔

重试次数耗尽后,默认会抛出AmqpRejectAndDontRequeueException 异常,消息不会重新回队列,若是有绑定实现交换机,则交给死信交换机否则丢弃。也可以自己创建一个MessageRecoverer,处理重试超次数消息。

例如,创建一个处理错误消息的交换机error.direct和队列error.queue,并创建绑定关系,通过路由key"error"路由,消息重试超次数时将消息投递到error.direct交换机。

java 复制代码
/**
 * 处理时重试超次数的消息
 */
@Configuration
@ConditionalOnProperty(prefix = "spring.rabbitmq.listener.simple.retry", name = "enabled", havingValue = "true")
public class errorExchangeConfig {

    @Bean
    public DirectExchange errorExchange() {
        // 创建报错交换机
        return new DirectExchange("error.direct");
    }

    @Bean
    public Queue errorQueue() {
        // 创建报错队列
        return new Queue("error.queue");
    }

    @Bean
    public Binding errorBinding() {
        // 绑定报错队列和交换机,路由key为error
        return BindingBuilder.bind(errorQueue()).to(errorExchange()).with("error");
    }

    @Bean
    public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate){
        // 处理重试超次数的消息
        return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");
    }
}

消息幂等

消息幂等的本质:同一条消息多次投递,业务只执行一次,不会产生重复数据 / 重复扣款

重复消息来源:消费者处理成功,ACK 前网络断开 / 进程宕机 → MQ 重投消息。

1、数据库唯一索引

每条消息携带唯一业务编号(消息 ID / 业务单号) ,业务表对该字段建立唯一索引。

消费时直接插入;重复投递时,数据库抛出唯一索引冲突异常,捕获异常直接返回,不重复执行业务。

适用:新增订单、流水记录等写入场景。

2、数据库状态表

单独一张幂等记录表,存储消息唯一 ID + 消息处理状态(未处理 / 处理成功 / 处理失败)

消费逻辑:

  1. 开启事务,先查询幂等表是否存在该 msgId
  2. 不存在:执行业务 SQL,插入幂等记录;提交事务
  3. 已存在:直接跳过,返回成功

注意:必须给 msgId 加唯一索引,防止并发情况下两个线程同时查询都查不到,导致重复执行业务(竞态问题)。

3、Redis 幂等

消费前,用 SETNX(带过期时间)存入消息唯一 ID。

  • set 成功:代表首次消费,执行业务逻辑;
  • set 失败:消息已处理,直接返回。
    设置过期时间,清理历史消息,防止 Redis 内存溢出。

适用:高并发、允许兜底补偿的场景,不能单独作为资金类强一致性场景的唯一幂等方案

4、业务自身天然幂等

理:改造业务逻辑,接口本身执行多少次结果不变。

例如:更新语句 update order set status=1 where id=xxx and status=0

条件更新,重复执行不会改变数据。第一次执行影响行数 = 1;再次执行影响行数 = 0。

5、消息 ID + 本地缓存

Caffeine/Guava 本地缓存记录已处理 msgId,设置过期时间。

✅ 优点:速度极快。

❌ 缺点:单机有效,集群环境失效;重启丢失缓存,只能做一层前置过滤,不能作为唯一方案。一般搭配 Redis / 数据库兜底。

延时消息

消息发送到mq后不立即处理,而是等待一定时间后再处理,使用于抢票订单超时等场景。

1、死信交换机

死信:消息无法被正常消费,被转发到死信交换机 DLX ,再路由到死信队列 DLQ。

前提:队列必须预先配置 x-dead-letter-exchange(DLX),否则消息满足下面条件只会直接丢弃,不会变成死信。

死信产生条件

  1. 消息被拒绝,且 requeue=false
    调用 basicNack / basicRejectrequeue=false
  • Spring AUTO 模式抛出 AmqpRejectAndDontRequeueException 就属于这种场景
  • 消息转换异常 MessageConversionException,框架自动 nack、requeue=false
  1. 消息过期(TTL 超时)
    两种 TTL 设置方式:
  • 消息级别 TTL :发送消息时设置 expiration 属性,单条消息过期时间
  • 队列级别 TTL :队列参数 x-message-ttl,队列内所有消息统一过期时间

队列 TTL:消息一旦到时间立刻判定过期;

消息 TTL:只有消息到达队列头部才会判断是否过期(坑!)

消息过期后,变为死信。

  1. 队列达到最大长度(消息数超限)
    队列声明参数 x-max-length:队列最多容纳 N 条消息。
    当队列消息数量已满,新消息入队会让队头旧消息变成死信

注意:是丢弃队头,不是丢弃新来的消息。

还有 x-max-length-bytes:队列占用总字节数上限,达到上限,队头消息死信。

设置死信交换机

队列若想设置死信交换机可以通过设置队列参数:x-dead-letter-exchange,当队列中出现死信时,就会被投递到该交换机。

关键参数

  1. x-dead-letter-exchange:死信交换机名称(必填)
  2. x-dead-letter-routing-key:死信转发时使用的 routingKey(可选;不填默认沿用原消息 routingKey)

注意:死信交换机可以是 direct /topic/fanout,和普通交换机无区别。

2、延时交换机插件

rabbitmq-delayed-message-exchange 延时交换机插件,安装插件后,新增一种交换机类型:x-delayed-message

生产者发送消息,带上 x-delay 头(毫秒延时);

交换机在 Broker 内部暂存消息,等到延时时间到达,才路由消息到目标队列。

安装插件

java 复制代码
# 下载对应rabbitmq版本插件,放到plugins目录
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
# 重启rabbitmq生效

Spring 代码声明延时交换机

java 复制代码
@Bean
public CustomExchange delayedExchange() {
    Map<String, Object> args = new HashMap<>();
    args.put("x-delayed-type", "direct"); // 底层路由类型
    return new CustomExchange("delayed.exchange", "x-delayed-message", true, false, args);
}

@Bean
public Queue targetQueue(){
    return QueueBuilder.durable("target.queue").build();
}

@Bean
public Binding binding(Queue targetQueue, CustomExchange delayedExchange){
    return BindingBuilder.bind(targetQueue).to(delayedExchange).with("delay.key").noargs();
}

发送延时消息

java 复制代码
MessageProperties props = MessagePropertiesBuilder.newInstance()
        .setHeader("x-delay", 5000) // 延时5秒
        .build();
rabbitTemplate.send("delayed.exchange", "delay.key", new Message("msg".getBytes(), props));
相关推荐
吃饱了得干活19 小时前
RabbitMQ 原理解析(下):存储、集群与可靠投递
后端·rabbitmq
辰辉创聚2 天前
禽流感病毒分子基础:表面抗原与内部转录复合体的功能解析
ide·leetcode·rabbitmq·重组血凝素蛋白·甲型流感病毒核蛋白·禽流感单抗
她说..2 天前
RabbitMQ 使用场景详解
java·spring boot·后端·spring·rabbitmq·java-rabbitmq
weixin_440730502 天前
rabbitmq的使用记录-架构图、生产端、消费端、rabbitmqctl命令、重启MQ服务
rabbitmq
xbgRS2 天前
RabbitMQ使用
rabbitmq
橙子圆1234 天前
RabbitMQ知识1
分布式·rabbitmq
無a伟6 天前
RabbitMq高级特性:TTL,死信队列,延迟队列
java·分布式·rabbitmq
小楼昨夜又东风1266 天前
kafka、rocketmq、rabbitmq,有什么区别
kafka·rabbitmq·rocketmq
無a伟6 天前
RabbitMq高级特性:消息确认,持久化,重试机制
java·分布式·rabbitmq