TTL
- TTL全称Time To Live (存活时间/过期时间)。
- 当消息到达存活时间后,还没有被消费,会被自动清理。
- RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间
例如下了订单为这个订单设置一个过期时间,当超过这个过期时间这个订单就失效了
设置过期时间方式一:声明队列的时候设置
为队列设置统一过期时间,时间范围内没有消费消息,消息将自动删除(消息从进入队列开始计算)
代码示例:
/**
* Expiration字段以微秒为单位表示TTL值,且与x-message-ttl具有相同的约束条件.因为Expiration字段必须是
* 字符串类型,borker将只会接收以字符串形式表达的数字
* 当同时指定了queue和message中的TTL值,两者中较小的值将会起作用
* @return
*/
@Bean
public Queue TTLQueue() {
Map<String, Object> map = new HashMap<>();
map.put("x-message-ttl", 30000); // 队列中的消息未被消费则30秒后过期
return new Queue("TTL_QUEUE", true, false, false, map);
}
设置过期时间方式二:发送消息的时候设置消息过期时间
代码示例:
/**
* ttl时间设置
* @param message
* @return
*/
public Boolean setMessageTTl(String message) {
String messageId = String.valueOf(UUID.randomUUID());
String messageData = message;
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String, Object> map = new HashMap<>();
map.put("messageId", messageId);
map.put("messageData", messageData);
map.put("createTime", createTime);
/*
Expiration字段以微秒为单位表示TTL值,且与x-message-ttl具有相同的约束条件.因为Expiration字段必须是
字符串类型,borker将只会接收以字符串形式表达的数字
当同时指定了queue和message中的TTL值,两者中较小的值将会起作用
*/
MessagePostProcessor messageProperties = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//设置message的信息
message.getMessageProperties().setExpiration("10000");
//返回该消息
return message;
}
};
//将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange
rabbitTemplate.convertAndSend("TestDirectExchange", "TestDirectRouting2", map,messageProperties);
return true;
}
当项目中同一个队列同时设置了两种过期时间,那个会选择时间较短的那个过期时间执行。
如果一个消息队列中其他的消息正常但是中间的消息突然设置过期时间,则不会清理消息,但是在消息消费的时候会去判断这个消息的过期时间。
测试代码:
/**
* ttl时间设置
* @param message
* @return
*/
public Boolean setMessageTTl(String message) {
String messageId = String.valueOf(UUID.randomUUID());
String messageData = message;
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String, Object> map = new HashMap<>();
map.put("messageId", messageId);
map.put("messageData", messageData);
map.put("createTime", createTime);
/*
Expiration字段以微秒为单位表示TTL值,且与x-message-ttl具有相同的约束条件.因为Expiration字段必须是
字符串类型,borker将只会接收以字符串形式表达的数字
当同时指定了queue和message中的TTL值,两者中较小的值将会起作用
*/
MessagePostProcessor messageProperties = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//设置message的信息
message.getMessageProperties().setExpiration("5000");
//返回该消息
return message;
}
};
for(int i=0;i<10;i++){
if(i==5) {
//将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange
rabbitTemplate.convertAndSend("topicExchange", "queue_ttl", map, messageProperties);
}else {
//将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange
rabbitTemplate.convertAndSend("topicExchange", "queue_ttl", map);
}
}
return true;
}
TTL小结
-
设置队列过期时间使用参数:x-message-ttl,单位:ms(毫秒),会对整个队列消息统一过期。
-
设置单独消息过期时间使用参数:expiration。单位:ms(毫秒),
当该消息到达队列头部时(消费时),会单独判断这一消息是否过期(如果消息在中间生成,暂时没到达头部,就不会将消息从队列中移除。) -
如果两者都进行了设置,以时间短的为准。
死信队列
死信队列,DLX。Dead Letter Exchange(死信交换机) ,当消息成为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX 。
消息成为死信的三种情况:
1.队列消息长度到达限制;
2.消费者拒绝消息,basicNak/basicReject,并且不把消息重新放入原目标队列,requeue= false;
3.原队列存在消息过期设置,消息到达超时时间未被消费;
队列绑定死信交换机:
给队列设置参数: x-dead-letter-exchange 和 x-dead-letter-routing-key
死信队列:
1.声明正常的队列(test_queue_dlx) 和交换机(test_exchange_dlx)
2.声明死信队列(queue_dlx)和死信交换机(exchange_dlx)
3.正常队列绑定死信交换机
设置两个参数:
x-dead-letter-exchange:死信交换机名称
x-dead-letter-routing-key:发送给死信交换机的routingkey
spring boot中设置当时间过期时形成的死信队列
代码示例
1.生成交换机和队列
@Configuration
public class DlxRabbitConfig {
/**
* 正常队列
* @return
*/
@Bean
public Queue dlxQueue(){
Map<String, Object> map = new HashMap<>();
// x-dead-letter-exchange:死信交换机名称
map.put("x-dead-letter-exchange", "exchange_dlx");
// x-dead-letter-routing-key:发送给死信交换机的routingkey
map.put("x-dead-letter-routing-key","dlx_tt");
//成为死信对设置队列的过期时间 ttl
map.put("x-message-ttl",10000);
//设置队列的长度
map.put("x-max-length",10);
return new Queue("queue_dlx",true,false,false,map);
}
/**
* 死信队列
* @return
*/
@Bean
public Queue dlxQueue1(){
return new Queue("test_queue_dlx");
}
/**
* 正常交换机
* @return
*/
@Bean
public DirectExchange dlxExchange(){
return new DirectExchange("test_exchange_dlx");
}
/**
* 死信交换机
*/
@Bean
public DirectExchange dlxExchange1(){
return new DirectExchange("exchange_dlx");
}
/**
* 交换机和队列绑定
* @return
*/
@Bean
public Binding bindingExchange(){
return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with("test_queue");
}
/**
* 死信交换机和队列绑定
* @return
*/
@Bean
public Binding bindingExchange1(){
return BindingBuilder.bind(dlxQueue1()).to(dlxExchange1()).with("dlx_tt");
}
}
2.测试代码
/**
* 死信队列测试
* @param message
* @return
*/
public Boolean deadExchange(String message) {
String messageId = String.valueOf(UUID.randomUUID());
String messageData = message;
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String, Object> map = new HashMap<>();
map.put("messageId", messageId);
map.put("messageData", messageData);
map.put("createTime", createTime);
rabbitTemplate.convertAndSend("test_exchange_dlx", "test_queue", map);
return true;
}
过程如下:当发送请求时会在队列(queue_dlx)存在一条消息,因为没有消费者,而这个队列设置了过期时间,当到达了过期时间,那么这个队列会将消息通过路由键发送到交换机(exchange_dlx),交换机将消息又通过路由键发送到队列(test_queue_dlx)中那么就可以看到这个消息了。
过一段时间后
接下来写一个监听类监听队列(test_queue_dlx)
@Component
public class DlxReceiver {
final private String message="messageData";
@RabbitHandler
@RabbitListener(queues = "test_queue_dlx")
public void process(Map testMessage) {
System.out.println("test_queue_dlx队列 exchange_dlx消费者收到消息 : " + testMessage.toString());
System.out.println("发送的消息:"+testMessage.get(message));
}
}
测试结果:
长度测试的时候队列的消息是先进先出的规则
代码示例
/**
* 死信队列长度测试
* @param message
* @return
*/
public Boolean deadExchangelenght(String message) {
String messageId = String.valueOf(UUID.randomUUID());
String dto = message;
String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Map<String, Object> map = new HashMap<>();
map.put("messageId", messageId);
map.put("createTime", createTime);
for (int i=0;i<20;i++) {
map.put("messageData", dto+i);
rabbitTemplate.convertAndSend("test_exchange_dlx", "test_queue1", map);
}
return true;
}
rabbitMQ延时队列
延时队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。
需求:
1.下单后,30分钟未支付,取消订单,回滚库存。
2.新用户注册成功7天后,发送短信问候。
实现方式:
1.定时器(可以解决第一个问题,但是消耗性能)
2.延时队列
RabbitMQ延时队列实现方式:TTL+死信队列组合实现延迟队列的效果。
延时队列和之前演示死信队列差不多就不代码演示了!