目录
[1. 配置](#1. 配置)
[2. 自动确认](#2. 自动确认)
[3. 手动确认](#3. 手动确认)
[1. 设置消息的 TTL](#1. 设置消息的 TTL)
[2. 设置队列的 TTL](#2. 设置队列的 TTL)
[3. 两种设置 TTL 方式的区别](#3. 两种设置 TTL 方式的区别)
[1. 消息过期](#1. 消息过期)
[2. 消息数量超过队列限制](#2. 消息数量超过队列限制)
[3. 消费者否定确认,且 requeue 为 false](#3. 消费者否定确认,且 requeue 为 false)
[4. 死信队列的应用](#4. 死信队列的应用)
[1. TTL + 死信队列](#1. TTL + 死信队列)
[2. 延迟队列插件](#2. 延迟队列插件)
[3. 区别](#3. 区别)
[1. 限流](#1. 限流)
[2. 负载均衡](#2. 负载均衡)
前言
RabbitMQ 除了要保证消息的可靠传递,还需要保证消息在异常的情况下避免消息死循环,队列堵塞以及单点故障等问题。因此还需要引入一些高级特性,如重试机制,死信队列,TTL等。本文就来介绍 RabbitMQ 的高级特性。
一、重试机制
在消息的传递过程中,可能会遇到各种异常情况(如:网络故障,服务不可用,资源不足等问题),导致消息处理失败。
RabbitMQ 提供了消息重试机制,允许消息在处理失败后重新发送;
异常情况分为两种:
- 第一种:临时性异常(比如网络波动,数据库短暂不可用等):
- RabbitMQ 可以通过重试机制解决,保证系统可靠;
- 第二种:非临时性异常(比如程序逻辑错误):
- RabbitMQ 多次重试也不能解决问题,但可以设置重试次数,避免持续重试,导致消息积压;
1. 配置
spring:
rabbitmq:
host: 49.233.162.74
port: 5672
username: study
password: 123456
virtual-host: ackhost
listener:
simple:
acknowledge-mode: auto # 消息接收确认
retry:
enabled: true # 开启消费者失败重试
initial-interval: 5000ms # 初始失败等待时长为 5 秒
max-attempts: 5 # 最大重试次数 ( 包括自身消费的⼀次 )
2. 自动确认
配置交换机和队列
java
public static final String RETRY_QUEUE = "retry.queue";
public static final String RETRY_EXCHANGE = "retry.exchange";
java
/**
* 重试机制
*/
@Bean("retryQueue")
public Queue retryQueue(){
return QueueBuilder.durable(Constants.RETRY_QUEUE).build();
}
@Bean("retryExchange")
public DirectExchange retryExchange(){
return ExchangeBuilder.directExchange(Constants.RETRY_EXCHANGE).build();
}
@Bean("retryBinding")
public Binding retryBinding(@Qualifier("retryQueue") Queue queue,
@Qualifier("retryExchange") DirectExchange directExchange){
return BindingBuilder.bind(queue).to(directExchange).with("retry");
}
发送消息
java
@RequestMapping("/retry")
public String retry(){
String message = "hello, this is a message for retry...";
rabbitTemplate.convertAndSend(Constants.RETRY_EXCHANGE, "retry", message);
return "消息发送成功!";
}
消费消息
java
@RabbitListener(queues = Constants.RETRY_QUEUE)
public void retry(Message message) throws UnsupportedEncodingException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.printf("[" + Constants.RETRY_QUEUE + "]接收到消息: %s, deliveryTag: %d\n",
new String(message.getBody(), "utf8"), deliveryTag);
int n = 3 / 0;
System.out.println("业务逻辑处理完毕!");
}
运行结果

可以看到,消息重试 5 次后,抛出异常。
消费者代码中,并没有对异常进行捕获。如果异常被捕获,则不会进行重试;
生产者代码:
java
@RabbitListener(queues = Constants.RETRY_QUEUE)
public void retry(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try{
System.out.printf("[" + Constants.RETRY_QUEUE + "]接收到消息: %s, deliveryTag: %d\n",
new String(message.getBody(), "utf8"), deliveryTag);
int n = 3 / 0;
System.out.println("业务逻辑处理完毕!");
}catch (Exception e){
System.out.println("发生异常!");
}
}
运行结果:

3. 手动确认
**重试机制只在自动确认模式下生效,**重试次数和重试间隔可在配置文件中配置;
消费者采用手动确认的方式,重试机制将不会生效,是否重试取决于消费者的逻辑;
消费时出现异常:
- 消费者否定确认,并将消息重新入队:将会持续重试;
- 消费者否定确认,但不将消息重新入队:不会进行重试;
消费者代码改成手动确认,发生异常时,消息重新入队:
java
@RabbitListener(queues = Constants.RETRY_QUEUE)
public void retry(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try{
System.out.printf("[" + Constants.RETRY_QUEUE + "]接收到消息: %s, deliveryTag: %d\n",
new String(message.getBody(), "utf8"), deliveryTag);
int n = 3 / 0;
System.out.println("业务逻辑处理完毕!");
channel.basicAck(deliveryTag, false);
}catch (Exception e){
System.out.println("发生异常!");
channel.basicNack(deliveryTag, false, true);
}
}
运行结果:

注意事项:
- 1. 自动确认模式下,重试后,消息消费失败,消息会被自动确认,消息就丢失了;
- 2. 手动确认模式下,程序逻辑异常,消息失败后重新入队,消息将无法被确认,导致消息积压;
二、TTL
TTL 是 Time to Live,表示过期时间;RabbitMQ 可以对消息和队列设置 TTL;
当消息到达存活时间后还没有被消费,就会被自动清除;
1. 设置消息的 TTL
设置消息的 TTL 有两种方式:
- 一是针对所有消息,单独设置消息的 TTL;
- 二是设置队列的 TTL;
如果两种方式一起用,则以两者之间 TTL 小的为准;
配置交换机和队列:
java
public static final String TTL_QUEUE = "ttl.queue";
public static final String TTL_QUEUE2 = "ttl2.queue";
public static final String TTL_EXCHANGE = "ttl.exchange";
java
/**
* TTL
*/
@Bean("ttlQueue")
public Queue ttlQueue(){
return QueueBuilder.durable(Constants.TTL_QUEUE).build();
}
@Bean("ttl2Queue")
public Queue ttl2Queue(){
return QueueBuilder.durable(Constants.TTL_QUEUE2).ttl(20000).build();
}
@Bean("ttl3Queue")
public Queue ttl3Queue(){
Map<String, Object> map = new HashMap<>();
map.put("x-message-ttl", 20000);
return QueueBuilder.durable(Constants.TTL_QUEUE2).withArguments(map).build();
}
@Bean("ttlExchange")
public DirectExchange ttlExchange(){
return ExchangeBuilder.directExchange(Constants.TTL_EXCHANGE).build();
}
@Bean("ttlBinding")
public Binding ttlBinding(@Qualifier("ttlQueue") Queue queue,
@Qualifier("ttlExchange") Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("ttl").noargs();
}
发送消息:
java
@RequestMapping("ttl")
public String ttl(){
rabbitTemplate.convertAndSend(Constants.TTL_EXCHANGE, "ttl", "hello, this is ttl test...",
new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("10000");
return message;
}
});
return "发送消息成功!";
}
发送消息后,可以看到 ttl.queue 中 Ready 消息为 1;

过 10s 钟后,消息自动被删除:

不设置消息的 TTL,消息不会过期;
2. 设置队列的 TTL
设置队列的 TTL 是在创建队列时,加入 x-message-ttl 参数实现的,单位是毫秒;
发送消息:
java
@RequestMapping("ttl2")
public String ttl2(){
rabbitTemplate.convertAndSend(Constants.TTL_EXCHANGE, "ttl", "hello, this is ttl test...");
return "发送消息成功!";
}
发送消息后,可以看到 ttl2.queue 中 Ready 消息为 1;

过 10s 钟后,消息自动被删除:

3. 两种设置 TTL 方式的区别
区别:
设置队列的 TTL,一旦消息过期,就会从队列中删除;
设置消息的 TTL,即使消息过期,也不会马上从队列中删除,而是在即将投递到消费者时进行判定;
原因分析:
设置队列的 TTL,队列中先过期的消息在队列的头部,RabbitMQ 只要定期扫描队头即可;
设置消息的 TTL,每条消息的过期时间不同,删除过期消息需要扫描整个队列。RabbitMQ 没有直接扫描队列,而是等到消息被消费时,判断消息是否过期,如果过期再进行删除;
三、死信队列

死信(dead message)是因为各种原因,无法被消费的消息;
死信队列:当消息变成死信后,被重新发送到另一个交换机(死信交换机 - Dead Letter Exchange),绑定死信交换机的队列,称为死信队列(Dead Letter Queue);
死信的原因:
-
- 消息被拒绝或被否定确认,并设置 requeue 参数为 false;
-
- 消息过期;
-
- 队列达到最大长度;
1. 消息过期
声明队列和交换机:
java
public static final String NORMAL_QUEUE = "normal.queue";
public static final String NORMAL_EXCHANGE = "normal.exchange";
public static final String DL_QUEUE = "dl.queue";
public static final String DL_EXCHANGE = "dl.exchange";
java
@Bean("normalQueue")
public Queue normalQueue(){
return QueueBuilder.durable(Constants.NORMAL_QUEUE)
.deadLetterExchange(Constants.DL_EXCHANGE)
.deadLetterRoutingKey("dlx")
.build();
}
@Bean("normalExchange")
public DirectExchange normalExchange(){
return ExchangeBuilder.directExchange(Constants.NORMAL_EXCHANGE).build();
}
@Bean("normalBinding")
public Binding normalBinding(@Qualifier("normalQueue") Queue queue,
@Qualifier("normalExchange") DirectExchange directExchange){
return BindingBuilder.bind(queue).to(directExchange).with("normal");
}
@Bean("dlQueue")
public Queue dlQueue(){
return QueueBuilder.durable(Constants.DL_QUEUE).build();
}
@Bean("dlExchange")
public DirectExchange dlExchange(){
return ExchangeBuilder.directExchange(Constants.DL_EXCHANGE).build();
}
@Bean("dlBinding")
public Binding dlBinding(@Qualifier("dlQueue") Queue queue,
@Qualifier("dlExchange") DirectExchange directExchange){
return BindingBuilder.bind(queue).to(directExchange).with("dlx");
}
设置产生死信的条件:
java
@Bean("normalQueue")
public Queue normalQueue(){
return QueueBuilder.durable(Constants.NORMAL_QUEUE)
.deadLetterExchange(Constants.DL_EXCHANGE)
.deadLetterRoutingKey("dlx")
.ttl(10000)
.maxLength(10)
.build();
}
生产者:
java
@RequestMapping("/dlx")
public String dlx(){
rabbitTemplate.convertAndSend(Constants.NORMAL_EXCHANGE, "normal", "hello, this is a dlx test...");
System.out.printf("%tc 消息发送成功!\n", new Date());
return "消息发送成功!";
}
发送之后,normal.queue 中有一条消息 ready:

等待 10s,消息进入死信队列:

2. 消息数量超过队列限制
生产者:
java
@RequestMapping("/dlx")
public String dlx(){
System.out.printf("%tc 消息发送成功!\n", new Date());
for (int i = 0; i < 20; i++){
rabbitTemplate.convertAndSend(Constants.NORMAL_EXCHANGE, "normal",
i + " hello, this is a dlx test...");
}
return "消息发送成功!";
}
连续发送 20 条消息, 可以看到超出队列大小限制的消息,被放到死信队列里了:

3. 消费者否定确认,且 requeue 为 false
生产者代码:
java
@RequestMapping("/dlx")
public String dlx(){
rabbitTemplate.convertAndSend(Constants.NORMAL_EXCHANGE, "normal",
"hello, this is a dlx test...");
System.out.printf("%tc 消息发送成功!\n", new Date());
return "消息发送成功!";
}
消费者代码:
java
@RabbitListener(queues = Constants.NORMAL_QUEUE)
public void normalListener(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try{
System.out.printf("[" + Constants.NORMAL_QUEUE + "]接收到消息: %s, deliveryTag: %d\n", new String(message.getBody(), "utf8"), deliveryTag);
int n = 3 / 0;
System.out.println("业务逻辑处理完毕!");
channel.basicAck(deliveryTag, false);
}catch (Exception e){
System.out.println("发生异常!");
channel.basicNack(deliveryTag, false, false);
}
}
消息被否定确认后,直接进入死信队列:

4. 死信队列的应用
死信队列通常用于处理异常情况下 ,消息不能被消费者正确消费 ,为了保证消息不丢失,将消息置于死信队列;
应用场景:
- 消息重试:将消息重新发回原队列或者另一个队列重新消费;
- 消息丢弃:丢弃无法处理的消息,避免占用系统资源;
- 日志收集:将死信消息作为日志收集起来,用于后续分析和问题定位;
四、延迟队列
延迟队列:消息被发送以后,并不想让消费者立刻拿到消息,而是等待特定时间后,消费者才能拿到这个消息进行消费;
RabbitMQ 本身并没有直接支持延迟队列,但可以通过 TTL + 死信队列的方式组合模拟实现延迟队列;
原理:将消息设置过期时间存入正常的工作队列中,而消费者通过订阅死信队列的方式,等到到达过期时间,消息进入死信队列,消费者就能拿到消息进行消费,就实现了延迟队列的功能;
1. TTL + 死信队列
可继续沿用上面死信队列的代码;
正常队列:
java
@Bean("normalQueue")
public Queue normalQueue(){
return QueueBuilder.durable(Constants.NORMAL_QUEUE)
.deadLetterExchange(Constants.DL_EXCHANGE)
.deadLetterRoutingKey("dlx")
.build();
}
生产者:
java
@RequestMapping("/delay")
public String delay(){
rabbitTemplate.convertAndSend(Constants.NORMAL_EXCHANGE, "normal", "hello, this is a delay test 10s ...", new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("10000");
return message;
}
});
rabbitTemplate.convertAndSend(Constants.NORMAL_EXCHANGE, "normal", "hello, this is a delay test 30s ...", new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("30000");
return message;
}
});
System.out.printf("%tc 消息发送成功!\n", new Date());
return "消息发送成功!";
}
消费者:
java
@RabbitListener(queues = Constants.DL_QUEUE)
public void dlListener(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.printf("%tc [" + Constants.DL_QUEUE + "]接收到消息: %s, deliveryTag: %d\n", new Date(), new String(message.getBody(), "utf8"), deliveryTag);
}
运行结果:

存在的问题:
将生产者发送消息的顺序调整一下,先发送过期时间 30s 的消息,再发送过期时间 10s 的消息;
生产者代码:
java
@RequestMapping("/delay")
public String delay(){
rabbitTemplate.convertAndSend(Constants.NORMAL_EXCHANGE, "normal", "hello, this is a delay test 10s ...", new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("30000");
return message;
}
});
rabbitTemplate.convertAndSend(Constants.NORMAL_EXCHANGE, "normal", "hello, this is a delay test 30s ...", new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("10000");
return message;
}
});
System.out.printf("%tc 消息发送成功!\n", new Date());
return "消息发送成功!";
}
运行结果:

可以看到,10s 延迟和 30s 延迟的消息同时延迟了 30s 才到达消费者被消费;
原因:
RabbitMQ 只会检查队列头部消息是否过期,后面的消息即使已经过期,也会被队头消息阻塞,必须等待队头消息过期后才能进入死信队列。
因此,使用 TTL + 死信队列:只适合固定的延迟时间;
2. 延迟队列插件
延迟队列插件下载地址:
Releases · rabbitmq/rabbitmq-delayed-message-exchange
上传插件 :installing Additional Plugins | RabbitMQ
将插件上传到指定目录:
- /usr/lib/rabbitmq/plugins 是⼀个附加目录,RabbitMQ 包本身不会在此安装任何内容,如果没有这个路径,可以自己进行创建;
启动插件:
查看插件列表
rabbitmq-plugins list
启动插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
重启服务
service rabbitmq-server restart
验证插件:新建交换机会有延迟消息的选项,表示插件已经安装成功

基于延迟插件实现延迟队列:
申明交换机,队列,绑定关系:
java
public static final String DELAY_QUEUE = "delay.queue";
public static final String DELAY_EXCHANGE = "delay.exchange";
java
@Configuration
public class DelayConfig {
@Bean("delayQueue")
public Queue delayQueue(){
return QueueBuilder.durable(Constants.DELAY_QUEUE).build();
}
@Bean("delayExchange")
public DirectExchange delayExchange(){
return ExchangeBuilder.directExchange(Constants.DELAY_EXCHANGE).durable(true).delayed().build();
}
@Bean("delayBinding")
public Binding delayBinding(@Qualifier("delayQueue") Queue queue,
@Qualifier("delayExchange") DirectExchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("delay");
}
}
生产者:
java
@RequestMapping("/delay2")
public String delay2(){
rabbitTemplate.convertAndSend(Constants.DELAY_EXCHANGE, "delay",
"hello, this is a delay test 30s ...", message -> {
message.getMessageProperties().setDelayLong(30000L);
return message;
});
rabbitTemplate.convertAndSend(Constants.DELAY_EXCHANGE, "delay",
"hello, this is a delay test 10s ...", message -> {
message.getMessageProperties().setDelayLong(10000L);
return message;
});
System.out.printf("%tc 消息发送成功!\n", new Date());
return "消息发送成功!";
}
消费者:
java
@Component
public class DelayListener {
@RabbitListener(queues = Constants.DELAY_QUEUE)
public void dlListener(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.printf("%tc [" + Constants.DELAY_QUEUE + "]接收到消息: %s, deliveryTag: %d\n", new Date(), new String(message.getBody(), "utf8"), deliveryTag);
}
}
运行结果:


消息能够按照设置的延迟时间,正确到达消费者;
3. 区别
延迟队列的应用场景:
- 24 小时未处理,自动退款;
- 注册 3 天后,发送调查问卷;
- 订单 10 分钟未支付,自动取消;
- ......
但 RabbitMQ 并未直接提供延迟队列的实现,实现延迟队列通常有两种方式:
-
- 使用 TTL + 死信队列;
-
- 使用延迟插件;
**TTL + 死信队列:**不需要额外插件的支持,但会存在队头消息阻塞的问题,还需要额外的逻辑处理死信队列的消息;
**延迟插件:**可以直接创建延迟队列,简化延迟消息的实现,但是只支持特定的版本,有运维工作;
五、事务
RabbitMQ 是基于 AMQP 协议实现的,该协议实现了事务机制,因此 RabbitMQ 也支持事务机制。
Spring AMQP 也提供了对事务相关的操作。RabbitMQ 事务允许开发者确保消息的发送和接收是原子性的,要么全部成功,要么全部失败。
配置事务管理器:
java
@Bean("transRabbitTemplate")
public RabbitTemplate transRabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setChannelTransacted(true);
return rabbitTemplate;
}
@Bean
public RabbitTransactionManager rabbitTransactionManager(ConnectionFactory connectionFactory) {
return new RabbitTransactionManager(connectionFactory);
}
声明交换机,队列以及绑定关系:
java
public static final String TRANS_QUEUE = "trans.queue";
public static final String TRANS_EXCHANGE = "trans.exchange";
java
@Configuration
public class TransConfig {
@Bean("transQueue")
public Queue transQueue(){
return QueueBuilder.durable(Constants.TRANS_QUEUE).build();
}
@Bean("transExchange")
public DirectExchange transExchange(){
return ExchangeBuilder.directExchange(Constants.TRANS_EXCHANGE).build();
}
@Bean("transBinding")
public Binding transBinding(@Qualifier("transQueue") Queue queue,
@Qualifier("transExchange") DirectExchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("trans");
}
}
生产者:
java
@Transactional
@RequestMapping("/trans")
public String trans(){
transRabbitTemplate.convertAndSend(Constants.TRANS_EXCHANGE, "trans",
"hello, this is trans test1...");
int a = 10 / 0;
transRabbitTemplate.convertAndSend(Constants.TRANS_EXCHANGE, "trans",
"hello, this is trans test2...");
return "消息发送成功!";
}
带 @Transactional 注解测试:

不带 @Transactional 注解测试:

六、消息分发
RabbitMQ 有多个消费者的时候,会把消息派发给不同的消费者,每条消息派发一次。
当队列中消息数量较多时,可以增加消费者的数量,处理消息;
问题:
默认情况下,RabbitMQ 按照轮询的方式进行消息的分发,但消费者的处理速度有可能是不一致的,轮询分发有可能造成有的消费者空闲,有的消费者信道消息积压;
解决方法:
使用 channel.basicQos(int prefetchCount) 方法,限制消费者信道上能保持的最大未确认消息的数量;
1. 限流
RabbitMQ 提供了限流机制,可以控制消费端一次只拉取 N 个请求;
通过设置 prefetchCount 参数,同时设置消息应答方式为手动应答;
prefetchCount:消费者从队列中预取消息的数量,从而实现流控制和负载均衡;
配置:
listener: simple: acknowledge-mode: manual prefetch: 5
配置交换机,队列和绑定关系:
java
public static final String QOS_QUEUE = "qos.queue";
public static final String QOS_EXCHANGE = "qos.exchange";
java
@Configuration
public class QosConfig {
@Bean("qosQueue")
public Queue qosQueue(){
return QueueBuilder.durable(Constants.QOS_QUEUE).build();
}
@Bean("qosExchange")
public DirectExchange qosExchange(){
return ExchangeBuilder.directExchange(Constants.QOS_EXCHANGE).build();
}
@Bean("qosBinding")
public Binding qosBinding(@Qualifier("qosQueue") Queue queue, @Qualifier("qosExchange") DirectExchange exchange){
return BindingBuilder.bind(queue).to(exchange).with("qos");
}
}
生产者:
java
@RequestMapping("/qos")
public String qos(){
String message = "hello, this is qos test...";
for (int i = 0; i < 20; i++){
rabbitTemplate.convertAndSend(Constants.QOS_EXCHANGE, "qos", i + " - " + message);
}
return "消息发送成功!";
}
消费者:
java
@Component
public class QosListener {
@RabbitListener(queues = Constants.QOS_QUEUE)
public void qos1(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
System.out.printf("qos1 [" + Constants.QOS_QUEUE + "]接收到消息: %s, deliveryTag = %d\n", new String(message.getBody(), "utf8"), deliveryTag);
Thread.sleep(2000);
}catch (Exception e){
channel.basicNack(deliveryTag, false, true);
}
}
}
运行结果:

生产者一次性发送 20 条消息,消费者每次拉取 5 条,可以看到队列中还剩 15条;
通过上述限流机制,可以保证系统在请求骤增的情况下,仍然可以正常工作,避免系统被打垮;
2. 负载均衡
当队列存在多个消费者,不同的消费者处理任务的速度不同,如果轮询分派消息,有可能有的消费者消息积压,有的消费者空闲;
可以设置 prefetch 为 1 ,给每个消费者都分派一个消息,消息确认后,再继续分派消息,保证所有消费者都按照自己的节奏工作,达到负载均衡的目的;
消费者:
java
@Component
public class QosListener {
@RabbitListener(queues = Constants.QOS_QUEUE)
public void qos1(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
System.out.printf("qos1 [" + Constants.QOS_QUEUE + "]接收到消息: %s, deliveryTag = %d\n", new String(message.getBody(), "utf8"), deliveryTag);
Thread.sleep(2000);
channel.basicAck(deliveryTag, false);
}catch (Exception e){
channel.basicNack(deliveryTag, false, true);
}
}
@RabbitListener(queues = Constants.QOS_QUEUE)
public void qos2(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
System.out.printf("qos2 [" + Constants.QOS_QUEUE + "]接收到消息: %s, deliveryTag = %d\n", new String(message.getBody(), "utf8"), deliveryTag);
Thread.sleep(1000);
channel.basicAck(deliveryTag, false);
}catch (Exception e){
channel.basicNack(deliveryTag, false, true);
}
}
}
运行结果:

可以看到,消费者 1 处理慢,消费者 2 处理快,但两者都按照自己的能力处理消息,达到了负载均衡的效果;
总结
本文介绍了 RabbitMQ 的高级特性,包括:重试机制,TTL,死信队列,延迟队列,事务以及消息分发。