超时自动取消订单和订单支付的并发问题分析
业务背景:生单完成或者预支付完成,但是用户消费者,实际上一直没有真正发起支付,需要自动取消订单(业界基本上来说都是三十分钟);
超时自动取消订单技术方案:RocketMQ延迟消息 + Redisson分布式锁 + XXl-Job分布式作业调度;
业务场景分析,超时订单自动取消和支付完成回调通知的并发问题会带来数据不一致的问题;
引入分布式锁,解决并发问题;
开启定时调度任务,兜底处理RocketMQ延迟消息机制;
- 支付完成回调和订单超时取消分布式锁实现互斥;
- 支付完成回调和订单预支付分布式锁实现互斥;
多重分布式锁加锁机制
private void payCallbackMultiLock(List<String> redisKeyList, String orderId) {
// 加支付分布式锁避免支付系统并发回调
String orderPayKey = RedisLockKeyConstants.ORDER_PAY_KEY + orderId;
// 加取消订单分布式锁避免支付和取消订单同时操作同一笔订单
String cancelOrderKey = RedisLockKeyConstants.CANCEL_KEY + orderId;
redisKeyList.add(orderPayKey);
redisKeyList.add(cancelOrderKey);
boolean lock = redisLock.multiLock(redisKeyList);
if (!lock) {
throw new OrderBizException(OrderErrorCodeEnum.ORDER_PAY_CALLBACK_ERROR);
}
}
延迟消息机制原理剖析
- 投递延迟消息,顺序写入磁盘,CommitLog;
- 不会将消息投递到目标Topic,先转发到内部的Topic(重试ConsumerQueue);
- 基于RocketMQ内部的延迟调度组件,重新将消息写入目标Topic(目标ConsumerQueue);
- 正常消费消息即可;
- todo,订单定时任务优化改造,分布式调度任务;
取消订单链路分析
取消订单链路业务整体流程分析
- 订单服务检查订单状态,是否支付,没有支付,更新订单状态为“已取消”
- 订单服务检查订单状态,是否支付,已经支付,订单系统同步通知履约系统,拦截履约,更新订单状态为“已取消”
- 拦截履约,履约服务处理,通知仓储服务,拦截发货;通知物流系统,拦截物流;同时履约服务,也会更新本地履约数据库;
- 订单服务发送“释放资产”消息,处理释放库存、释放优惠券、发起退款;
目前存在的技术问题:取消订单、拦截履约与发送“释放资产(消息)”的强一致性事务保证;
首先,取消订单、拦截履约,使用Seata AT模式,刚性事务,保证数据一致;
发送“释放资产(消息)”又怎么和取消订单、拦截履约实现事务绑定,一起成功,一起失败???
释放资产,多路MQ设计优化
订单服务,内部专门维护释放资产消息消费者,消费消息,发送多路MQ
- 发送消息(释放库存);
- 发送消息(释放优惠券);
- 发送消息(发起退款);
值得注意的是,释放资产的处理思想,订单服务内部维护’释放资产‘消息的消费者,处理消息,完成发送多路MQ消息出去,实现高扩展性;
另外,关于订单取消退款的处理,采用双异步支付退款的设计思路,数据一致性实现基于RocketMQ的事务消息机制,订单售后数据和支付退款数据一致;
当然,整体取消订单链路,释放资产(消息投递),基于RocketMQ事务消息机制,二阶段提交,同时包裹取消订单、拦截履约刚性事务,最终数据一致;
事务消息原理分析具体可以参考生单链路,另外,关注一下,释放库存,数据库和缓存双写机制,具体可以参考生单链路的锁定库存业务处理;
技术亮点
- 正向流程,生单流程,推送履约,异步处理,库存锁定,优惠券锁定,同步处理,提升用户体验
- 逆向过程,取消订单,拦截履约,同步处理,库存释放、资产释放,异步处理,提升用户体验