订单超时处理
GPT摘要
在电商场景中,订单超时自动处理(如未付款取消、未发货关闭、自动收货等)是一个核心需求。文章分析了多种技术实现方案及其优缺点: 1. DelayQueue方案 - 通过延迟队列按超时时间排序,单线程轮询处理。启动时需从数据库加载未完成订单。 - 优点:实现简单,无第三方依赖。 - 缺点:内存占用高,无法分布式扩展,仅适合小规模场景。 2. RabbitMQ延迟队列 - 利用死信交换机和TTL实现固定延时等级(如5s、10s)。需预定义多级延时队列。 - 优点:支持海量消息与分布式处理。 - 缺点:延时时间不灵活,配置复杂。 3. 时间轮算法 - 通过循环队列和层级时间轮处理大范围延时(如24小时内),支持任意时刻精度。 - 优点:延时精度高,使用简单。 - 缺点:同一时刻大量消息易导致延迟,MQ存储成本高。 4. Redis过期监听 - 基于键空间通知实现过期回调,需配置
notify-keyspace-events
。 - 问题:Redis的惰性删除策略可能导致延时不准。 5. 定时任务批处理 - 通过分布式调度周期性扫描数据库处理超时订单,需隔离超时库以降低主库压力。 - 优点:稳定性高,适合大规模场景(如阿里超时中心SLA达30秒内)。 - 缺点:精度依赖调度周期,高频扫描可能影响数据库性能。 总结:小规模场景可选DelayQueue;中大规模推荐RabbitMQ固定延时或时间轮算法(需权衡灵活性);超大流量下定时任务批处理(如超时中心)是更优解,但需独立部署资源。
原文链接:订单超时处理的几种方案及分析-阿里云开发者社区 (aliyun.com)
介绍
在电商场景下,买卖双方没有面对面交易,许多情况下需要通过超时处理自动关闭订单,如:
- 买家超时未付款
- 卖家超时未发货
- 买家超时自动收货
JDK自带DelayQueue
处理流程
- 把订单插入DelayQueue中,以超时时间作为排序条件,将订单按照超时时间从小到大排序。
- 起一个线程不停轮询队列的头部,如果订单的超时时间到了,就出队进行超时处理,并更新订单状态到数据库中。
- 为了防止机器重启导致内存中的DelayQueue数据丢失,每次机器启动的时候,需要从数据库中初始化未结束的订单,加入到DelayQueue中。
优点:简单,不需要借助其他第三方组件,成本低。
缺点:
- 所有超时处理订单都要加入到DelayQueue中,占用内存大。
- 没法做到分布式处理,只能在集群中选一台leader专门处理,效率低。
- 不适合订单量比较大的场景。
DelayQueue原理:
- PriorityQueue(按照超时时间排序) + 锁
- 每次取出队头,并且判断是否已经超时,如果没有超时需要等待;为了只有一个消费者等待队头,添加了一个leader
1 |
|
1 |
|
RabbitMQ的延时消息
- RabbitMQ Delayed Message Plugin(非高可用)
- 消息的TTL+死信Exchange
- 定义一个BizQueue,用来接收死信消息,并进行业务消费。
- 定义一个死信交换机(DLXExchange),绑定BizQueue,接收延时队列的消息,并转发给BizQueue。
- 定义一组延时队列DelayQueue_xx,分别配置不同的TTL,用来处理固定延时5s、10s、30s等延时等级,并绑定到DLXExchange。
- 定义DelayExchange,用来接收业务发过来的延时消息,并根据延时时间转发到不同的延时队列中。
优点:可以支持海量延时消息,支持分布式处理。
缺点:
- 不灵活,只能支持固定延时等级。
- 使用复杂,要配置一堆延时队列。
RocketMQ的定时消息
时间轮算法
指针代表当前时刻,slot上有每个时刻上等待的任务,链表串起来
- 时间转一下代表过去1s
- 遍历slot上的链表,if round > 0 round – 否则开始处理消息
- 新消息到达 Slot=(curTime + delay) mod total_slot,round = (curTime + delay) / total_slot
延时最大限制
定时时长最大值24小时,使用循环处理实现大延时
优点
- 精度高,支持任意时刻。
- 使用门槛低,和使用普通消息一样。
缺点
- 成本高:每个订单需要新增一个定时消息,且不会马上消费,给MQ带来很大的存储成本。
- 同一个时刻大量消息会导致消息延迟:定时消息的实现逻辑需要先经过定时存储等待触发,定时时间到达后才会被投递给消费者。因此,如果将大量定时消息的定时时间设置为同一时刻,则到达该时刻后会有大量消息同时需要被处理,会造成系统压力过大,导致消息分发延迟,影响定时精度。
Redis的过期监听
- redis配置文件开启”notify-keyspace-events Ex”
- 配置过期回调
1 |
|
问题:redis的key是惰性删除+定期删除,延时不正确
定时任务分布式批处理
- 稳定性强:没有引入额外中间件
- 效率高:批处理
但是使用定时任务有个天然的缺点:没法做到精度很高。定时任务的延迟时间,由定时任务的调度周期决定。如果把频率设置很小,就会导致数据库的qps比较高,容易造成数据库压力过大,从而影响线上的正常业务。
所以一般需要抽离出超时中心和超时库来单独做订单的超时调度,在阿里内部,几乎所有的业务都使用基于定时任务分布式批处理的超时中心来做订单超时处理,SLA可以做到30秒以内: