用户下了订单之后,还未付款,在规定的期限内如果没有支付,则这个订单应该标记为取消。
如果实现过期自动取消,下面有几种解决方案
- 定时轮询订单,超过期限且未支付
- 创建订单后,开启一个消息队列,等待X时间后执行
- 通过死信队列回调
其实方案二和死信队列的原理差不多,但是MQ已经拥有类似的机制,所以我们直接沿用即可。
死信队列介绍
- 死信队列:DLX,
dead-letter-exchange
- 利用DLX,当消息在一个队列中变成死信
(dead message)
之后,它能被重新publish到另一个Exchange,这个Exchange就是DLX
消息变成死信有以下几种情况
- 消息被拒绝(basic.reject/ basic.nack)并且不再重新投递 requeue=false
- 消息过期 (rabbitmq Time-To-Live -> messageProperties.setExpiration())
- 队列超出最大长度
整体的一个流程思路:
创建一个普通的队列(加入死信队列的一些属性),这个消息永远没有人消费,没人消费则过期,过期则通过之前的设置转发回调给另外一个队列
下面是基于Spring boot 的实际用例
- 引入依赖
-
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
-
- 配置类
-
/** * 死信队列交换机标识符 属性值不能改,写死 */ private static final String DEAD_LETTER_QUEUE_KEY = "x-dead-letter-exchange"; /** * 死信队列交换机绑定键 标识符 属性值不能改,写死 */ private static final String DEAD_LETTER_ROUTING_KEY = "x-dead-letter-routing-key"; /** * deadLetterExchange(direct类型交换机) * * @return */ @Bean("deadLetterExchange") public Exchange deadLetterExchange() { return ExchangeBuilder.directExchange("DEAD_LETTER_EXCHANGE").durable(true).build(); } /** * 声明一个死信队列 * x-dead-letter-exchange 对应 死信交换机 * x-dead-letter-routing-key 对应 死信队列 */ @Bean("deadLetterQueue") public Queue deadLetterQueue() { //应该像个普通队列,里面多设置了两个参数,这个队列没有被消费或者超时 则通过x-dead-letter-exchange 指明重新回到死信交换机 TEST_SIGN_EXCHANGE //交换机 // 参数 Map<String, Object> args = new HashMap<>(2); // 出现dead letter之后将dead letter重新发送到指定exchange args.put(DEAD_LETTER_QUEUE_KEY, "DEAD_LETTER_EXCHANGE"); // 出现dead letter之后将dead letter重新按照指定的routing-key发送 args.put(DEAD_LETTER_ROUTING_KEY, "REDIRECT_KEY"); // name队列名字 durable是否持久化,true保证消息的不丢失, exclusive是否排他队列,如果一个队列被声明为排他队列,该队列仅对首次申明它的连接可见,并在连接断开时自动删除, autoDelete如果该队列没有任何订阅的消费者的话,该队列是否会被自动删除, arguments参数map return new Queue("DEAD_LETTER_QUEUE", true, false, false, args); } /** * 死信路由通过 DEAD_LETTER_KEY 绑定到死信队列上. */ @Bean public Binding deadLetterBinding() { return new Binding("DEAD_LETTER_QUEUE", Binding.DestinationType.QUEUE, "DEAD_LETTER_EXCHANGE", "DEAD_LETTER_KEY", null); } /** * 死信路由通过 REDIRECT_KEY 绑定到转发队列上. 这个队列绑定的是当出现死信消息后 重新转发给的队列 */ @Bean public Binding redirectBinding() { return new Binding("REDIRECT_QUEUE", Binding.DestinationType.QUEUE, "DEAD_LETTER_EXCHANGE", "REDIRECT_KEY", null); } /** * 定义死信队列转发队列. (和普通队列一样,这个队列是为了原有的消息没有被消费重新转发给一个新的队列) */ @Bean("redirectQueue") public Queue redirectQueue() { return new Queue("REDIRECT_QUEUE", true, false, false); }
-
- 监听类
-
@Component public class TestMQConsumer { /** * 监听转发队列 死信队列重新转发回这里 * */ @RabbitListener(queues = {"REDIRECT_QUEUE"}) public void redirect(HashMap<String,Object> dataMap) throws IOException { System.out.println(dataMap.get("msg")); System.out.println("我是转发队列,这里执行逻辑业务"); } }
-
- 执行类
-
@Test public void testMq(){ //声明消息处理器 设置消息的编码以及消息的过期时间 时间毫秒值为字符串 MessagePostProcessor messagePostProcessor = message -> { MessageProperties messageProperties = message.getMessageProperties(); messageProperties.setMessageId(UUID.randomUUID().toString().replaceAll("-", "")); messageProperties.setContentEncoding("utf-8"); //超时时间10秒 messageProperties.setExpiration(String.valueOf(1000*10)); return message; }; Map<String, Object> dataMap = new HashMap<>(); dataMap.put("msg","我是传递的消息"); rabbitTemplate.convertAndSend("DEAD_LETTER_EXCHANGE", "DEAD_LETTER_KEY",dataMap,messagePostProcessor); }
-
执行效果:
DEAD_LETTER_QUEUE 执行后没有被消费,超过10秒钟后自动回调 REDIRECT_QUEUE