前言
延迟消费在 RabbitMq 没有属性可以设置,只能通过 死信交换器(DLX)和设置过期时间(TTL)结合起来达到延迟的效果,所以我要介绍DLX和TTL以及实现延迟队列。
正文
使用所有框架和中间件的版本
框架 | 版本 |
Spring Boot | 2.1.5.RELEASE |
RabbitMq | 3.7.15 |
JDK |
1.8.0_144 |
Erlang | 22.0.2 |
过期时间(TTL)
TTL是Time To Live的缩写, 也就是生存时间。RabbitMq支持对消息和队列设置TTL,对消息这设置是在发送的时候指定,对队列设置是从消息入队列开始计算, 只要超过了队列的超时时间配置, 那么消息会自动清除。
如果两种方式一起使用消息对TTL和队列的TTL之间较小的为准,也就是消息5s过期,队列是10s,那么5s的生效。
默认是没有过期时间的,表示消息没有过期时间;如果设置为0,表示消息在投递到消费者的时候直接被消息,否则丢弃。
设置消息的过期时间用 x-message-ttl 参数实现,单位毫秒。
设置队列的过期时间用 x-expires 参数,单位毫秒,注意,不能设置为0。
死信交换器(DLX)
DLX是Dead-Letter-Exchange的缩写,全称死信交换器。成为死信队列后,可以被重新发送到另外一个交换器中,这个交换器就是DLX,绑定DLX到队列称为死信队列。注意,死信队列和死信交换器不一样哦。
成为死信一般由以下几种情况:
- 消息被拒绝 (basic.reject or basic.nack) 且带 requeue=false 参数
- 消息的TTL-存活时间已经过期
- 队列长度限制被超越(队列满)
DLX和一般的交换器没有区别,可以声明在任何的队列上,理解为队列的一个属性吧。当这个队列有死信时会根据设置自动的将死信重新发布到设置的DLX上进行消费。这个消费了死信的队列称之为死信队列,并不是绑定了DLX的队列是死信队列,大家要区分清楚。
通过在队列里设置 x-dead-letter-exchange 参数来声明DLX,如果当前DLX是 direct 类型还要 声明 x-dead-letter-routing-key 参数来指定路由键,如果没有指定,则使用原队列的路由键。
延迟队列
通过DLX和TTL模拟出延迟队列的功能,即,消息发送以后,不让消费者拿到,而是等待过期时间,变成死信后,发送给死信队列进行消费。
延迟队列流程图
maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
RabbitMq配置
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=yanlin
spring.rabbitmq.password=yanlin
生产者
@RestController
public class DelayController {
@Autowired
private AmqpTemplate amqpTemplate;
@GetMapping("/delay/{id}")
public String delayTest(@PathVariable Integer id) {
Order order = new Order(id, "我等了10s");
amqpTemplate.convertAndSend("normal_exchange", "normal_key", order);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
String dateString = sdf.format(new Date());
return "请求时间" + dateString;
}
}
配置监听
@Configuration
public class DelayListener {
//死信队列
private static final String DELAY_QUEUE = "delay_queue";
//正常队列
private static final String NORMAL_QUEUE = "normal_queue";
//死信交换器
private static final String DELAY_EXCHANGE = "delay_exchange";
//死信路由键
private static final String DELAY_KEY = "delay_key";
/**
* 正常队列
*
* @return
*/
@Bean
public Queue normalQueue() {
Map<String, Object> map = new HashMap<>();
map.put("x-message-ttl", 10000);//设置10s过期时间
//x-dead-letter-exchange参数是设置该队列的死信交换器(DLX)
map.put("x-dead-letter-exchange", DELAY_EXCHANGE);
//x-dead-letter-routing-key参数是给这个DLX指定路由键
map.put("x-dead-letter-routing-key", DELAY_KEY);
return new Queue(NORMAL_QUEUE, true, false, false, map);
}
/**
* 正常交换器
*
* @return
*/
@Bean
public DirectExchange normalExchange() {
return new DirectExchange("normal_exchange", true, false);
}
/**
* 正常队列绑定
*
* @param normalQueue
* @param normalExchange
* @return
*/
@Bean
public Binding normalBinding(Queue normalQueue, DirectExchange normalExchange) {
return BindingBuilder.bind(normalQueue).to(normalExchange).with("normal_key");
}
/**
* 死信队列
*
* @return
*/
@Bean
public Queue delayQueue() {
return new Queue(DELAY_QUEUE);
}
/**
* 死信交换器
*
* @return
*/
@Bean
public DirectExchange delayExchange() {
return new DirectExchange(DELAY_EXCHANGE, true, false);
}
/**
* 死信队列绑定交换器
*
* @param delayQueue
* @param delayExchange
* @return
*/
@Bean
Binding delayBinding(Queue delayQueue, DirectExchange delayExchange) {
return BindingBuilder.bind(delayQueue).to(delayExchange).with(DELAY_KEY);
}
消费者
@Service
public class DelayConsumer {
/**
* 延迟消费方法
*
* @param order
*/
@RabbitListener(queues = "delay_queue")
@RabbitHandler
public void delay(Order order) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
System.out.println("------" + sdf.format(new Date()) + "------");
System.out.println("请观察页面返回的时间与上面打印时间对比");
System.out.println("这是延迟10s消费的消息:" + order.getName());
}
}
启动项目,生产者发送消息后返回的时间与消费者控制台打印的时间对比,差了10s,延迟队列完成。
项目如图
github地址 https://github.com/362460453/rabbitMQ-demo