需求背景
在开发场景中,如预约的签到,用户会有不同的预约签到时间,例如:7:00、7:30、8:00。
对于这种场景,使用定点轮询查表的方式,一方面时间颗粒度需要严格的控制,另一方面在数据库的记录一多就很浪费性能。所以可以考虑使用 MQ 的过期队列来实现功能。
核心思路
基于 RabbitMQ 的死信队列:
- 将消息的存活时间设置为”从当前时间到目标判断截止时间”的时间差
- 消息过期后自动转入死信队列
- 在项目中编写方法监听死信队列,触发业务逻辑
这样就实现了动态的过期时间设置,无需轮询数据库。
配置实现
1. 定义队列与死信队列
配置一个正常队列,设置一个极大的默认过期时间(如 24h),并绑定死信交换机:
1 2 3 4 5 6 7 8 9
| @Bean public Queue queueLog() { Map<String, Object> args = new HashMap<>(); args.put("x-message-ttl", TTL_MAX); args.put("x-dead-letter-exchange", EXCHANGE_DLX_LOG); args.put("x-dead-letter-routing-key", ROUTING_KEY_LOG);
return QueueBuilder.durable(QUEUE_LOG).withArguments(args).build(); }
|
TTL_MAX 设置为 24h(注意单位是毫秒,24 * 60 * 60 * 1000)。
配置流程:
- 创建一个正常的交换机(Exchange)
- 创建一个正常的队列(Queue),绑定到正常交换机
- 定义一个死信交换机(DLX)和死信队列(DLQ)
- 将死信队列绑定到死信交换机
- 将正常队列通过参数绑定到死信交换机(第 2 步的
args)
2. 发送消息时动态设置过期时间
计算当前时间到目标时间的差值,作为消息的过期时间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Autowired private AmqpTemplate rabbitTemplate;
public void sendLogMessage(String message) { QueryWrapper<ReservationsDTO> qw = new QueryWrapper<>(); qw.eq("reservation_id", message); ReservationsDTO dto = reservationsMapper.selectOne(qw);
LocalDateTime startTime = dto.getStartTime(); Duration duration = Duration.between(LocalDateTime.now(), startTime);
if (duration.isNegative()) { log.error("签到时间已过: {}", message); duration = Duration.ZERO; }
long expirationTimeMillis = duration.toMillis();
rabbitTemplate.convertAndSend(RabbitMQ.EXCHANGE, message, msg -> { msg.getMessageProperties().setExpiration(String.valueOf(expirationTimeMillis)); return msg; }); }
|
如果需要路由键:
1 2 3 4
| rabbitTemplate.convertAndSend(RabbitMQ.EXCHANGE, "路由键", message, msg -> { msg.getMessageProperties().setExpiration(String.valueOf(expirationTimeMillis)); return msg; });
|
3. 消费死信队列
在消费者中监听对应的死信队列,正常编写业务逻辑即可。
注意事项
- 设置的单个消息过期时间**不要超过队列的
x-message-ttl**(即 24h),所以在设计时要考虑极端情况的最大间隔
- 时间差为负数时兜底处理为
Duration.ZERO,防止消息永远不过期
setExpiration 接收的是字符串类型的毫秒数
小结
- 利用死信队列 + 动态 TTL,可以优雅地实现定时触发,避免轮询数据库
- 核心是三个配置:正常队列绑定死信交换机、设置队列最大 TTL、发送时动态计算过期时间
- 适用于预约签到、订单超时取消等场景
参考:CSDN - JAVA-MQ设置动态的过期队列