过期时间
在我们业务开发中有这样一个场景,如果一个订单30分钟未支付则移除该订单。这个场景就可以使用到消息队列中的过期时间来处理。
过期时间TTL表示可以对消息设置预期的时间,在这个时间内都可以被消费者接受获取;过了之后消息将自动被删除。
RabbitMQ可以对消息和队列设置TTL,目前有两种方式可以设置:
** 第一种方法是通过队列属性设置,队列中所有消息都有相同的过期时间。**
简单使用
- 首先创建工程,然后添加相关依赖即可。
- 编写RabbitMQ配置文件
@Configuration
public class TTLRabbitMQConfiguration {
@Bean
public DirectExchange ttlDirectExchange() {
return new DirectExchange("ttl_direct_exchange", true, false);
}
@Bean
public Queue ttlDirectQueue() {
// 设置过期时间 从web界面 添加队列出获取 ,单位是毫秒
Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl", 5000); //这里一定是int类型
return new Queue("ttl.direct.queue", true, false, false, args);
}
@Bean
public Binding ttlDirectBinding() {
return BindingBuilder.bind(ttlDirectQueue()).to(ttlDirectExchange()).with("ttl");
}
}
- 编写业务逻辑
@Service
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
//给队列设置过期时间
public void makeOrderTtl() {
// 保存订单
String orderId = UUID.randomUUID().toString();
System.out.println("下单成功:" + orderId);
// 通过MQ完成消息的分发
// 参数1:交换机 ;参数2:路由key/队列名;参数3:消息内容
String exchangeName = "ttl_direct_exchange";
String routingKey = "ttl";
rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId);
}
}
- 测试
只需要编写测试类调用业务代码即可。然后可以在web界面查看效果。
** 第二种方法是对消息进行单独设置,每条消息TTL可以不同。**
- 声明新队列
@Configuration
public class TTLRabbitMQConfiguration {
@Bean
public DirectExchange ttlDirectExchange() {
return new DirectExchange("ttl_direct_exchange", true, false);
}
@Bean
public Queue ttlMessageDirectQueue() {
return new Queue("ttl.message.direct.queue", true);
}
@Bean
public Binding ttlMessageDirectBinding() {
return BindingBuilder.bind(ttlMessageDirectQueue()).to(ttlDirectExchange()).with("ttlMessage");
}
}
- 业务代码
@Service
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
//给消息设置过期时间
public void makeOrderTtlMessage() {
String orderId = UUID.randomUUID().toString();
System.out.println("下单成功:" + orderId);
String exchangeName = "ttl_direct_exchange";
String routingKey = "ttlMessage";
// 给消息设置过期时间
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//这里区别在于参数是字符串
message.getMessageProperties().setExpiration("5000");
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
};
rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId, messagePostProcessor);
}
}
两者的差异在于:过期队列中的消息过期后会写入死信队列中,而手动设置消息的过期时间不会将消息写入死信队列中。
如果上述两种方法同时使用,则消息的过期时间以两者之间的TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TTL值,就称为dead message,会被投递到死信队列,消费者将无法收到该消息。
死信队列
DLX,全程为Dead-Letter-Exchange,也可以称之为死信交换机。当消息在一个队列中变成死信之后,他能被重新发送到一个交换机中,这个交换机就是DLX,绑定DLX的队列就是死信队列。
消息变为死信,可能由于以下几个原因:
- 消息被拒绝
- 消息过期
- 队列达到最大长度
DLX也是一个正常的交换机,和一般的交换机没有区别,他能在任何的队列上被指定,实际上就是设置某一个队列的属性。当这个队列中存在死信时,RabbitMQ就会自动的将这个消息重新发布到设置的DLX 上去,进而被路由到另一个队列,即死信队列。
想要使用死信队列,只需要在定义队列的时候设置队列参数*** x-dead-letter-exchange *** 指定交换机即可。
- 声明死信交换机及死信队列
@Configuration
public class DeadRabbitMQConfiguration {
@Bean
public DirectExchange deadDirectExchange() {
return new DirectExchange("dead_direct_exchange", true, false);
}
@Bean
public Queue deadDirectQueue() {
return new Queue("dead.direct.queue", true);
}
@Bean
public Binding deadDirectBinding() {
return BindingBuilder.bind(deadDirectQueue()).to(deadDirectExchange()).with("dead");
}
}
- 绑定死信队列
使用第一步测试过期时间队列的代码即可。添加对应的参数,交换机,路由参数,最大长度等。具体可以从web界面查看。
@Configuration
public class TTLRabbitMQConfiguration {
@Bean
public DirectExchange ttlDirectExchange() {
return new DirectExchange("ttl_direct_exchange", true, false);
}
@Bean
public Queue ttlDirectQueue() {
// 设置过期时间 从web界面 添加队列出获取 ,单位是毫秒
Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl", 5000); //这里一定是int类型
args.put("x-dead-letter-exchange", "dead_direct_exchange");
args.put("x-dead-letter-routing-key", "dead"); //fanout模式不需要配置
return new Queue("ttl.direct.queue", true, false, false, args);
}
@Bean
public Queue ttlMessageDirectQueue() {
return new Queue("ttl.message.direct.queue", true);
}
@Bean
public Binding ttlDirectBinding() {
return BindingBuilder.bind(ttlDirectQueue()).to(ttlDirectExchange()).with("ttl");
}
@Bean
public Binding ttlMessageDirectBinding() {
return BindingBuilder.bind(ttlMessageDirectQueue()).to(ttlDirectExchange()).with("ttlMessage");
}
}
- 测试
直接调用过期时间的测试方法即可,需要注意的是如果队列名称一样,会执行失败。所以可以换一个队列名或者在web界面删除相对应的队列即可。
测试之后,可以看到5秒之后,数据会被移到死信队列中。