一、RabbitMQ消费者消息限流
在一开始介绍MQ的时候,就提到了削峰填谷,本质上就是限流,所以我们需要对限流做一个落地的实现。
限流主要在消费者这一块,基于消费者做代码实现。并且基于手动ack的开启
yml:
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual
prefetch: 2 # 每个消费者最多2条未确认消息
concurrency: 1 # 固定1个消费者
max-concurrency: 1 # 最多也只有1个消费者
我配置了,但是并未生效。后面发现代码中硬编码了,其实那段
RabbitMqConfig类中该方法messageListenerContainer是不需要的,在
RabbitMqClient类中并未使用SimpleMessageListenerContainer
那需要yml配置怎么生效的呢?
在这个包中,

java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.boot.autoconfigure.amqp;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.connection.AbstractConnectionFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.AbstractConnectionFactory.AddressShuffleMode;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory.CacheMode;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@ConfigurationProperties(
prefix = "spring.rabbitmq"
)
public class RabbitProperties {
private static final int DEFAULT_PORT = 5672;
private static final int DEFAULT_PORT_SECURE = 5671;
private static final int DEFAULT_STREAM_PORT = 5552;
private String host = "localhost";
private Integer port;
private String username = "guest";
private String password = "guest";
private final Ssl ssl = new Ssl();
private String virtualHost;
private String addresses;
private AbstractConnectionFactory.AddressShuffleMode addressShuffleMode;
@DurationUnit(ChronoUnit.SECONDS)
private Duration requestedHeartbeat;
private int requestedChannelMax;
private boolean publisherReturns;
private CachingConnectionFactory.ConfirmType publisherConfirmType;
private Duration connectionTimeout;
private Duration channelRpcTimeout;
private final Cache cache;
private final Listener listener;
private final Template template;
private final Stream stream;
private List<Address> parsedAddresses;
public RabbitProperties() {
this.addressShuffleMode = AddressShuffleMode.NONE;
this.requestedChannelMax = 2047;
this.channelRpcTimeout = Duration.ofMinutes(10L);
this.cache = new Cache();
this.listener = new Listener();
this.template = new Template();
this.stream = new Stream();
}
public String getHost() {
return this.host;
}
public String determineHost() {
return CollectionUtils.isEmpty(this.parsedAddresses) ? this.getHost() : ((Address)this.parsedAddresses.get(0)).host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return this.port;
}
public int determinePort() {
if (CollectionUtils.isEmpty(this.parsedAddresses)) {
Integer port = this.getPort();
if (port != null) {
return port;
} else {
return (Boolean)Optional.ofNullable(this.getSsl().getEnabled()).orElse(false) ? 5671 : 5672;
}
} else {
return ((Address)this.parsedAddresses.get(0)).port;
}
}
public void setPort(Integer port) {
this.port = port;
}
public String getAddresses() {
return this.addresses;
}
......
}
java
RabbitProperties.SimpleContainer simple = rabbitProperties.getListener().getSimple();
Integer concurrency = simple.getConcurrency();
Integer prefetch = simple.getPrefetch();
log.info("【我是处理人1】 prefetch : " + prefetch);
log.info("【我是处理人1】 concurrency : " + concurrency);
打印的配置也正是yml配置的,所以按照要求,放心进行配置,会自动载入配置。
二、RabbitMQ ttl特性控制短信队列超时
假设现在短信发送量很多,消息太多太多了,可能处理不过来,那么假设短信验证码本身就是5分钟内失效的,但是5分钟过后还没有发出去,mq消息还是在队列中没有被消费者消费,那么这条消息其实是作废的,没有用的。而且本身用户已经等了那么久了都没收到,所以我们能不能索性设定一个时间为60s,60秒还没有消费消息,那么就不消费了呗,让用户再次发送一个请求完事。

可以使用ttl这个特性。
- TTL:time to live 存活时间,和redis的ttl是一个道理
- 如果消息到到ttl的时候,还没有被消费,则自动清除
- RabbitMQ可以对消息或者整个队列设置ttl
已有的队列,加了ttl是无效的,需要新建的队列。

若依集成,在启动项目时,就会initQueue初始化队列。首先在rabbitMq的管理界面把以前有的队列(需要配置ttl的)先删除掉。
所以配置是需要放在消费者类
java
@RabbitListener(
queuesToDeclare = @Queue(
value = "wj_place_msg",
arguments = {
@Argument(name = "x-message-ttl", value = "10000", type = "java.lang.Integer"),
}
)
)
或者在配置类中增加
java
@Bean("placeOrderQueue")
public Queue placeOrderQueue() {
return QueueBuilder.durable(CloudConstant.MQ_JEECG_PLACE_ORDER)
.ttl(10 * 1000) // 10秒TTL
.build();
}
再重新启动

三、RabbitMQ 死信队列的实现
死信队列是 RabbitMQ 中用于处理无法正常消费的消息的特殊队列。当一个消息变成"死信"时,它会被重新发布到死信交换机,然后路由到死信队列。

- 死信队列:DLX,dead letter exchange。
- 当一个消息
死了以后,就会被发送到死信交换机DLX
队列绑定了DLX死信交换机,那么超时后,消息不会被抛弃,而是会进入到死信交换机,死信交换机绑定了其他队列(称之为死信队列),那么这个时候我们就可以处理那些被抛弃的消息了。
1.消息变成死信的三种情况
- 消息被拒绝(basic.reject或basic.nack消费者手动驳回消息)且 requeue=false
- 消息过期(TTL时间到): 队列设置:x-message-ttl=10000(10秒)
- 队列达到最大长度
java
@RabbitListener(
queuesToDeclare = @Queue(
value = "wj_place_msg",
arguments = {
@Argument(name = "x-message-ttl", value = "10000", type = "java.lang.Integer"),
@Argument(name = "x-dead-letter-exchange", value = DelayExchangeBuilder.DEAD_EXCHANGE),
@Argument(name = "x-dead-letter-routing-key", value = "dlx.routing.key")
}
)
)

其中死信队列可以不止一个。
死信队列本质上也是传统意义的队列,由业务队列绑定死信交换机及路由,然后分发到不同的死信队列其消费者进行死信处理。
2.若依集成设计

1)关于注解:

|--------|---------------------|----------|-----------------------------------------------------------------------------------------------------------|
| 编号 | 注解 | 配置位置 | 作用 |
| 1 | DeadLetterBinding | 业务消费者 | 绑定死信路由,交换因为只有一个,内置默认的。 需要注意的是,消费者配置同一个队列,死信路由也许一样,否则只有一个生效。见3)下说明 |
| 2 | DeadLetterQueueArgs | 死信消费者 | 死信队列的参数配置 |
| 3 | RabbitComponent | 所有消费者 | 配置值为首字母小写类名,用于加载消费者的 |
| 4 | RabbitQueueArgs | 业务消费者 | * 配置各种特殊队列的参数 * 延迟队列: 支持延迟消息功能 * 优先级队列: 支持消息优先级处理 * 懒加载队列: 优化内存使用 * 仲裁队列: 高可用队列类型(需要插件支持) |
2)初始加载:
java
@Bean
public void initQueue() {
// 获取消费者
Map<String, Object> beansWithRqbbitComponentMap = this.applicationContext.getBeansWithAnnotation(RabbitComponent.class);
Class<? extends Object> clazz = null;
for (Map.Entry<String, Object> entry : beansWithRqbbitComponentMap.entrySet()) {
log.info("初始化队列............");
//获取到实例对象的class信息
clazz = entry.getValue().getClass();
Method[] methods = clazz.getMethods();
RabbitListener rabbitListener = clazz.getAnnotation(RabbitListener.class);
if (ObjectUtil.isNotEmpty(rabbitListener)) {
DeadLetterQueueArgs deadLetterQueueArgs = clazz.getAnnotation(DeadLetterQueueArgs.class);
DeadLetterBinding rabbitBindDead = clazz.getAnnotation(DeadLetterBinding.class);
RabbitQueueArgs rabbitQueueArgs = clazz.getAnnotation(RabbitQueueArgs.class);
if (ObjectUtil.isNotEmpty(deadLetterQueueArgs)) {
// 创建死信队列
createDeadQueue(rabbitListener, deadLetterQueueArgs);
} else {
// 创建业务队列
createBusinessQueue(rabbitListener, rabbitQueueArgs, rabbitBindDead);
}
}
for (Method method : methods) {
RabbitListener methodRabbitListener = method.getAnnotation(RabbitListener.class);
if (ObjectUtil.isNotEmpty(methodRabbitListener)) {
DeadLetterQueueArgs methodDeadLetterQueueArgs = method.getAnnotation(DeadLetterQueueArgs.class);
DeadLetterBinding methodRabbitBindDead = method.getAnnotation(DeadLetterBinding.class);
RabbitQueueArgs methodRabbitQueueArgs = method.getAnnotation(RabbitQueueArgs.class);
if (ObjectUtil.isNotEmpty(methodDeadLetterQueueArgs)) {
// 创建死信队列
createDeadQueue(methodRabbitListener, methodDeadLetterQueueArgs);
} else {
// 创建业务队列
createBusinessQueue(methodRabbitListener, methodRabbitQueueArgs, methodRabbitBindDead);
}
}
}
}
}
3)配置需要注意的:
业务消费者
java
@RabbitListener(queues = "wj_place_msg")
@RabbitComponent(value = "sendMsgConsumer1")
@DeadLetterBinding(deadRouter = "wj.dead.msg.send1")
@RabbitQueueArgs(
ttl = 10 * 1000, // 10秒测试TTL
maxLength = 10000, // 最多1万条消息
maxPriority = 5 // 支持5个优先级
)
public class SendMsgConsumer1 extends BaseRabbiMqHandler<BaseMap>
java
@RabbitListener(queues = "wj_place_msg")
@RabbitComponent(value = "sendMsgConsumer")
@DeadLetterBinding(deadRouter = "wj.dead.msg.send")
public class SendMsgConsumer extends BaseRabbiMqHandler<BaseMap>
我在两个消费者且同一队列wj_place_msg的情况,配置绑定死信队列路由用了不同的key。这是不行的,因为
x-dead-letter-routing-key 只允许一个key。而且假如没有这个队列,必然读到其中一个消费者时,就会创建队列。读到下一个消费者时,已经不会再创建队列了。
2026-01-29 10:30:25.489 ERROR 93300 --- [ntContainer#1-1] c.w.c.m.l.MsgDeadLetterQueueListener : === 收到死信消息 ===
2026-01-29 10:30:25.489 ERROR 93300 --- [ntContainer#1-1] c.w.c.m.l.MsgDeadLetterQueueListener : 消息内容: {phone=13524533454}
2026-01-29 10:30:25.489 ERROR 93300 --- [ntContainer#1-1] c.w.c.m.l.MsgDeadLetterQueueListener : 消息头: {spring_listener_return_correlation=9a6244be-fe17-4d1d-a7fc-31b1bb749b02, spring_returned_message_correlation=a131cb88-3642-4436-b0ad-607f18605da9, x-first-death-exchange=wj.direct.exchange, x-last-death-reason=rejected, x-death=[{reason=rejected, count=1, exchange=wj.direct.exchange, time=Thu Jan 29 10:30:19 CST 2026, routing-keys=[wj_place_msg], queue=wj_place_msg}], x-first-death-reason=rejected, x-first-death-queue=wj_place_msg, x-last-death-queue=wj_place_msg, x-last-death-exchange=wj.direct.exchange}
2026-01-29 10:30:25.733 WARN 93300 --- [ntContainer#1-1] c.w.c.m.l.MsgDeadLetterQueueListener : 死信原因: rejected, 来源队列: wj_place_msg, 死亡时间: Thu Jan 29 10:30:19 CST 2026
2026-01-29 10:30:25.733 WARN 93300 --- [ntContainer#1-1] c.w.c.m.l.MsgDeadLetterQueueListener : 消息被消费者明确拒绝
2026-01-29 10:30:26.620 ERROR 93300 --- [ntContainer#1-1] c.w.c.m.l.MsgDeadLetterQueueListener : === 收到死信消息 ===
2026-01-29 10:30:26.620 ERROR 93300 --- [ntContainer#1-1] c.w.c.m.l.MsgDeadLetterQueueListener : 消息内容: {phone=13524533454}
2026-01-29 10:30:26.620 ERROR 93300 --- [ntContainer#1-1] c.w.c.m.l.MsgDeadLetterQueueListener : 消息头: {spring_listener_return_correlation=9a6244be-fe17-4d1d-a7fc-31b1bb749b02, spring_returned_message_correlation=41cb2208-70ea-4cab-9ca7-40db85b8c959, x-first-death-exchange=wj.direct.exchange, x-last-death-reason=rejected, x-death=[{reason=rejected, count=1, exchange=wj.direct.exchange, time=Thu Jan 29 10:30:19 CST 2026, routing-keys=[wj_place_msg], queue=wj_place_msg}], x-first-death-reason=rejected, x-first-death-queue=wj_place_msg, x-last-death-queue=wj_place_msg, x-last-death-exchange=wj.direct.exchange}
2026-01-29 10:30:26.951 WARN 93300 --- [ntContainer#1-1] c.w.c.m.l.MsgDeadLetterQueueListener : 死信原因: rejected, 来源队列: wj_place_msg, 死亡时间: Thu Jan 29 10:30:19 CST 2026
2026-01-29 10:30:26.952 WARN 93300 --- [ntContainer#1-1] c.w.c.m.l.MsgDeadLetterQueueListener : 消息被消费者明确拒绝
4)如果死信消费者也出现问题呢?
死信消费者拒绝的终极处理架构
1. 业务队列 → 普通死信队列
2. 普通死信队列 → 终极死信队列(永不拒绝)
3. 终极死信队列 → 持久化存储
4. 持久化存储 → 人工处理 + 告警
实施步骤
-
配置终极死信队列(lazy模式 + 仲裁队列)
-
实现终极死信消费者(捕获所有异常)
-
建立多级持久化(DB → 文件 → 分布式存储)
-
配置智能告警(多渠道 + 重试机制)
-
实现监控自愈(健康检查 + 自动恢复)
-
提供管理界面(人工处理 + 数据分析)
这个方案适合信息可靠性要求很高的业务下。