目录
1. 死信队列
死信:无法被消费方消费掉的消息,称为死信。如果死信一直留在队列中,会导致一直被消费,却从不消费成功。所以 rabbitmq
专门开辟了一个来存放死信的队列,叫死信队列(DLX,dead-letter-exchange
)
1.1. 死信从何而来
- 消息消费方调用了
basicNack()
或basicReject()
,并且参数都是requeue = false
,则消息会路由进死信队列 - 消息过期,过了
TTL
存活时间,就是消费方在TTL
时间之内没有消费,则消息会路由进死信队列 - 队列设置了
x-max-length
最大消息数量且当前队列中的消息已经达到了这个数量,再次投递,消息将被挤掉,被挤掉的消息会路由进死信队列 - 有一种场景需要注意下:消费者设置了自动
ACK
,当重复投递次数达到了设置的最大retry
次数之后,消息也会投递到死信队列,但是内部的原理还是调用了basicNack()
或basicReject()
#开启rabbitmq的生产端重试机制,默认是false,默认重试 3 次
spring.rabbitmq.template.retry.enabled=true
#开启rabbitmq的消费端重试机制,默认是false,默认重试 3 次
spring.rabbitmq.listener.simple.retry.enabled=true
#设置重试的次数
spring.rabbitmq.listener.simple.retry.max-attempts=5
1.2. 死信流程
死信流程图
- 正常业务消息被投递到正常业务的
Exchange
,该Exchange
根据路由键将消息路由到绑定的正常队列 - 正常业务队列中的消息变成了死信消息之后,会被自动投递到该队列绑定的死信交换机上(并带上配置的路由键,如果没有指定死信消息的路由键,则默认继承该消息在正常业务时设定的路由键)
- 死信交换机收到消息后,将消息根据路由规则路由到指定的死信队列
- 消息到达死信队列后,可监听该死信队列,处理死信消息
死信队列并不是什么特殊的队列,只不过是绑定在死信交换机上的队列。死信交换机也不是什么特殊的交换机,只不过是用来接收死信的交换机,所以可以为任何类型(Direct、Fanout、Topic
)。一般来说,会为每个业务队列分配一个独有的路由 key
,并对应的配置一个死信队列进行监听,也就是说,一般会为 每个重要的业务队列配置一个死信队列
2. 死信队列实现
SpringBoot
版本2.0.6.RELEASE
Rabbitmq
版本3.8.3
分别创建项目消息生产方,消息接收方两个项目
2.1. 消息发送方
2.1.1. 配置文件
server.port=8080
#配置rabbitmq服务器
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
#确认消息已发送到交换机
spring.rabbitmq.publisher-confirms=true
#确认消息已发送到队列
spring.rabbitmq.publisher-returns=true
2.1.2. 队列、交换机定义及绑定
@Configuration
public class DeadLetterConfig {
// --------------------------正常业务队列--------------------------
// 业务队列 A
@Bean
public Queue businessQueueA() {
Map<String, Object> args = new HashMap<>();
// x-dead-letter-exchange:这里声明当前业务队列绑定的死信交换机
args.put("x-dead-letter-exchange", Constant.DEAD_LETTER_EXCHANGE);
// x-dead-letter-routing-key:这里声明当前业务队列的死信路由 key
args.put("x-dead-letter-routing-key", Constant.DEAD_LETTER_QUEUE_A_ROUTING_KEY);
return new Queue(Constant.BUSINESS_QUEUE_A, true, false, false, args);
}
// 业务队列 B
@Bean
public Queue businessQueueB() {
Map<String, Object> args = new HashMap<>();
// x-dead-letter-exchange:这里声明当前队列绑定的死信交换机
args.put("x-dead-letter-exchange", Constant.DEAD_LETTER_EXCHANGE);
// x-dead-letter-routing-key:这里声明当前队列的死信路由 key
args.put("x-dead-letter-routing-key", Constant.DEAD_LETTER_QUEUE_B_ROUTING_KEY);
return new Queue(Constant.BUSINESS_QUEUE_B, true, false, false, args);
}
// 业务队列的交换机
@Bean
public TopicExchange businessTopicExchange() {
return new TopicExchange(Constant.BUSINESS_EXCHANGE, true, false);
}
// 业务队列 A 与交换机绑定,并指定 Routing_Key
@Bean
public Binding businessBindingA() {
return BindingBuilder.bind(businessQueueA()).to(businessTopicExchange()).with(Constant.BUSINESS_QUEUE_A_ROUTING_KEY);
}
// 业务队列 B 与交换机绑定,并指定 Routing_Key
@Bean
public Binding businessBindingB() {