一、背景

系统中用户下单,对于系统下单一般是分布式事务的操作,想要实现订单超时自动取消,我们可以基于MQ的延迟队列和死信队列实现。整体的实现思路分三种情况要考虑,第一种是订单的创建和投递到MQ,第二种是正常订单消息的消费,另外则是超时后消息的消费。

二、实现思路

对于订单的创建,只要生产者将消息成功投递到MQ,则认为订单创建成功。MQ返回ack表明消息投递成功,此时向延迟队列发送一条消息,而延迟队列挂载死信队列。这样做目的是:如果延迟队列中的消息达到阈值还没消费,则会进入死信队列,此时死信队列的监听器则会获取到过期的订单信息,可以做取消操作,反之,则走正常订单消费的流程。

整体实现思路大体如下:

三、具体代码

本文基于RabbitMQ实现,借助于RabbitMQ的延迟队列TTL和死信队列。
配置文件:

server.port=8080# 设计rabbitmq连接
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=root
spring.rabbitmq.password=123456
# 设置虚拟主机
spring.rabbitmq.virtual-host=keduw-order# 设置发布者确认机制
# correlated发布消息成功到交换器后会触发回调方法,默认是none
spring.rabbitmq.publisher-confirm-type=correlated
spring.rabbitmq.publisher-returns=true# 消息为手动确认
spring.rabbitmq.listener.direct.acknowledge-mode=manual

增加RabbitMQ的配置类,创建对应的队列、转换器、监听器以及队列信息绑定,备注很详细,这里就不太赘述。

*** RabbitMQ配置类*/
@Configuration
public class RabbitMqConfig {/*** 使用DirectMessageListenerContainer,您需要确保ConnectionFactory配置了一个任务执行器,* 该执行器在使用该ConnectionFactory的所有侦听器容器中具有足够的线程来支持所需的并发性。* 默认连接池大小仅为5。** 并发性基于配置的队列和consumersPerQueue。每个队列的每个使用者使用一个单独的通道,* 并发性由rabbit客户端库控制;默认情况下,它使用5个线程池;* 可以配置taskExecutor来提供所需的最大并发性。** @param connectionFactory* @return*/@Bean(name = "rabbitMessageListenerContainer")public DirectMessageListenerContainer listenerContainer(ConnectionFactory connectionFactory){// 写的时候,默认使用DEFAULT_NUM_THREADS = Runtime.getRuntime().availableProcessors() * 2个线程DirectMessageListenerContainer container = new DirectMessageListenerContainer(connectionFactory);// 设置确认消息的模式container.setAcknowledgeMode(AcknowledgeMode.MANUAL);container.setPrefetchCount(5);container.setConsumersPerQueue(5);container.setMessagesPerAck(1);ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();taskExecutor.setCorePoolSize(10);taskExecutor.setMaxPoolSize(20);//设置该属性,灵活设置并发 ,多线程运行。container.setTaskExecutor(taskExecutor);return container;}/*** 设置消息转换器,用于将对象转换成JSON数据* 可以通过converterAndSend将对象发送消息队列* 监听器也可以通过该工具将接受对象反序列化成java对象** @return Jackson转换器*/@Beanpublic MessageConverter messageConverter(){return new Jackson2JsonMessageConverter();}@Beanpublic RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {return new RabbitAdmin(connectionFactory);}/*** 订单消息队列* @return*/@Beanpublic Queue orderQueue(){return QueueBuilder.durable("q.order").build();}/*** 延迟消息队列* @return*/@Beanpublic Queue ttlQueue(){Map<String,Object> args = new HashMap<>();// 该队列的消息10s到期args.put("x-message-ttl", 10000);// 设置死信队列交换器,(当队列消息TTL到期后依然没有消费,则加入死信队列)args.put("x-dead-letter-exchange","x.dlx");// 设置私信队列路由键,设置该队列所关联的死信交换器的routingKey,如果没有特殊指定,使用原队列的routingKeyargs.put("x-dead-letter-routing-key","k.dlx");Queue queue = new Queue("q.ttl",true,false,false, args);return queue;}/*** 死信队列,用于取消用户订单* 当10s还没有付款的订单则进入死信队列,消费死信队列,取消用户订单** @return*/@Beanpublic Queue dlxQueue(){Map<String,Object> args = new HashMap<>();Queue dlq = new Queue("q.dlx",true,false,false, args);return dlq;}/*** 订单交换器* @return*/@Beanpublic Exchange orderExchange(){Map<String, Object> args = new HashMap<>();DirectExchange exchange = new DirectExchange("x.order", true, false, args);return exchange;}/*** 延迟队列交换器* @return*/@Beanpublic Exchange ttlExchange(){Map<String, Object> args = new HashMap<>();return new DirectExchange("x.ttl", true, false, args);}/*** 死信队列交换器* @return*/@Beanpublic Exchange dlxExchange(){Map<String, Object> args = new HashMap<>();DirectExchange exchange = new DirectExchange("x.dlx", true, false, args);return exchange;}/*** 用于发送下单,做分布式事务的MQ* @return*/@Beanpublic Binding orderBinding(){return BindingBuilder.bind(orderQueue()).to(orderExchange()).with("k.order").noargs();}/*** 用于等待用户支付的延迟队列绑定* @return*/@Beanpublic Binding ttlBinding(){return BindingBuilder.bind(ttlQueue()).to(ttlExchange()).with("k.ttl").noargs();}/*** 用于支付超时取消用户订单的死信队列绑定* @return*/@Beanpublic Binding dlxBinding(){return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with("k.dlx").noargs();}}

创建订单监听器,用于监听订单正常的支付提交和超时取消。

/*** 订单正常支付流程监听*/
@Component
public class OrderNormalListener {@RabbitListener(queues = "q.order",ackMode = "MANUAL")public void onMessage(Order order , Channel channel , Message message) throws IOException {System.out.println("写入数据库");System.out.println(order);for (OrderDetail detail : order.getDetails()){System.out.println(detail);}channel.basicAck(message.getMessageProperties().getDeliveryTag() , false);}}

创建订单超时自动取消监听器,监听的是死信队列。

/*** 订单超时自动取消监听*/
@Component
public class OrderCancelListener implements ChannelAwareMessageListener {@Override@RabbitListener(queues = "q.dlx" , ackMode = "MANUAL")public void onMessage(Message message, Channel channel) throws Exception {String orderId = new String(message.getBody());System.out.println("取消订单:" + orderId);channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);}
}

对于订单提交,正常提交后同时投递多一份到延迟队列里面去,用作延迟取消。

// 构建订单信息
Order order = new Order();
order.setUserId(IdUtils.generateUserId());
order.setOrderId(IdUtils.generateOrderId());
// 设置状态为待支付
order.setStatus(OrderStatus.TO_BE_PAYED.toString());
order.setDetails(details);// 投递消息
CorrelationData correlationData = new CorrelationData();
rabbitTemplate.convertAndSend("x.order","k.order", order, correlationData);
// 同步等待,可以设置为异步回调
CorrelationData.Confirm confirm = correlationData.getFuture().get();
// 判断发送的消息是否得到broker的确认
boolean confirmAck = confirm.isAck();
if (confirmAck){// 发送延迟等待消息rabbitTemplate.convertAndSend("x.ttl","k.ttl" , order.getOrderId());
}

四、总结

到这里,基本就实现了整个订单延迟自动取消的思路,但事实上还有问题。

投递订单消息到MQ后要投递多一份到延迟队列,可能存在第一次投递成功但投递到延迟队列失败的情况,这里则需要依赖分布式锁或者增加补偿机制;还有编码上的问题,MQ队列名称这些最好抽离出来,当然这里只是demo,就没有那么规范,如果是产品开发,这些都需要最好规定,方便后期维护。

电商系统如何实现订单超时自动取消?相关推荐

  1. HH SaaS电商系统的销售订单毛利润模块设计

    HH SaaS电商系统的销售订单利润分析,仅是简单分析供运营人员参考而已,所以订单生成后就自动根据商品成本价和实际交易价生成利润表,而不需要等到订单归档后才生成. 销售子单的毛利润=最终交易总额-成本 ...

  2. 电商系统搭建(商品订单模块)

    借助直播的东风,电商系统正在飞速发展,那么如何从0开始搭建电商系统. 这篇文章就介绍一下怎么简单的搭建一个电商系统,首先从电商系统的核心(订单)来开讲. 数据结构设计 商品表,商品细节表,订单表,订单 ...

  3. 订单超时自动取消3种方案——我们用这种!

    大家好,大家对电商购物应该都比较熟悉了,我们应该注意到,在下单之后,通常会有一个倒计时,如果超过支付时间,订单就会被自动取消. 下单 今天,我们来聊聊订单超时未支付自动取消的几种方案. 1.定时任务 ...

  4. delayQueue实现订单超时自动取消

    目录 说明 实现 1.编写Delayed实现类 2.编写DelayQueue业务类 3.编写订单业务逻辑 总结说明 说明 商城系统的订单模块都应该有:订单未支付超时后自动取消订单的操作.我们在开发过程 ...

  5. 使用DelayQueue模拟订单超时自动取消

    1.创建能在DelayQueue中存放的Order对象 package com.example.javastudy.delay_queue;import java.time.Duration; imp ...

  6. HH SaaS电商系统的销售订单归档服务

    归档状态 销售主单的状态转变成"交易成功"时,同步归档状态转变成"待归档" 归档服务(定时任务) 开发一个专门用于修改销售订单归档状态的服务 获取归档状态为&q ...

  7. HH SaaS电商系统的销售订单毛利润设计

    销售订单毛利润表

  8. 前端学习(2025)vue之电商管理系统电商系统之渲染订单列表数据

    目录结构 router.js import Vue from 'vue' import Router from 'vue-router' import Login from './components ...

  9. HH SaaS电商系统的销售订单付款后生成采购单和出库单的时序图

  10. 广告电商系统开发功能只订单处理

    广告电商系统之订单处理模块 订单处理功能块:订单下单:订单组合付款:订单列表,订单状态,订单物流信息,订单确认,订单售后,订单评论  1.订单下单 通过购物车的筛选,确定出下单的产品,系统自动计算出订 ...

最新文章

  1. LeetCode 电子书!
  2. IOS Push 证书的重新生成
  3. 每日一题(51)—— 大小端判断
  4. 怎么去除idea中代码的波浪线(黄色警告线)
  5. 考考你,能看出这一段代码是用哪个编程语言写的吗?
  6. 2017.10.5 最短母串 思考记录
  7. cookie 保存导航菜单的展开状态
  8. Windows 7 BitLocker 体验
  9. Java集合详解(超详细)
  10. 介绍几个图论和复杂网络的程序库 —— BGL,QuickGraph,igraph和NetworkX
  11. web安全工具 御剑后台扫描layer子域名挖掘机
  12. SpringBoot学习感悟
  13. 电脑故障维修判断指导大全(联想内部文件)[网络转载]
  14. uniapp app端根据支付宝qr链接 直接打开支付宝app支付页面
  15. oracle查表的更新时间,Oracle 查询倒叙查询所有表更新时间
  16. 看似“冰山美人”的弦图竟如此平易近人!
  17. EEG- gan:用于脑电图(EEG)大脑信号的生成对抗网络2018
  18. JavaPoet开源项目的使用
  19. 不越狱无插件实现 sparrow 推送通知
  20. php 没有jstl,jstl,java-ee_JSTL test 为什么没起作用,jstl,java-ee - phpStudy

热门文章

  1. 近世代数——Part2 群:基础与子群
  2. Qt: json对象转格式化字符串
  3. matlab qpsk调制程序,MATLAB QPSK调制及解调.doc
  4. 推荐一款神仙颜值的 Redis 客户端工具
  5. 通过设置关联菜单建立excel记账本
  6. adb 重启应用程序
  7. IntelliJ IDEA导入Spring源码
  8. 游戏植入广告获取收益
  9. Nginx(1)— Nginx工作原理
  10. Nginx 架构原理