我们在移动APP 下单,假如没有立即支付,进入订单详情会显示倒计时,如果超过支付时间,订单就会被自动取消。
网上有很多八股文,很多方案其实并不太适合真实的业务场景。
1 定时任务方案
我们自然的想到定时任务的方案。
方案流程:
- 每隔 30 秒查询数据库,取出最近的 N 条未支付的订单。
- 遍历查询出来的订单列表,判断当前时间减去订单的创建时间是否超过了支付超时时间,如果超时则对该订单执行取消操作。
这种方案会间隔对数据库造成一定的 IO 压力,特别是当订单量数据量非常高时,高频次的查询对数据库的性能是个不小的考验。
定时任务方案从功能模块角度来讲,包含调度层和业务逻辑层两部分。
网上有很多的定时任务实现策略,我们可以简单划分为单机版和集群版。
2 定时任务方案:单机版
我们可以使用 Timer 、ScheduledEexcutorService、Quartz 非常容易的实现定时任务。
但笔者并不推荐使用单机版的方案,举个简单的例子:
假设我们应用 A 通过 Quartz 调度三个定时任务 A、B、C ,当集群部署时,可能出现多台不同机器实例同时执行任务的风险。
此时,我们可以通过加锁的方式适当规避,见下图:
但这种方式并不优雅,同时定时任务应用内调度层会经常空跑,我们预期是希望三个定时任务 A、B、C 能均匀分布应用 A的不同实例内。
好,接下来,笔者会介绍亲身经历的三种集群定时任务。
3 定时任务方案:集群版
3.1 Quartz + JDBCJobStore
Quartz 可以支持集群模式,集群模式需要在数据库中添加11张表,对业务系统有一定的侵入性。
笔者曾经服务的一家彩票公司,订单调度中心就是使用 Quartz 的集群模式,实现日均百万订单的调度处理。
需要特别注意的是:
基于底层数据库悲观锁的机制,Quartz 的集群模式性能并不高,假如执行频率高的任务数超过达到一定数量,存在性能问题。
3.1 Elastic-Job
3.3 任务调度平台
笔者非常认可任务调度平台这种模式。XXL-JOB 是一个使用最广泛的分布式任务调度平台。
4 延时消息方案
4.1 消息队列 RocketMQ
RocketMQ 4.X 生产者发送延迟消息代码如下:
Message msg = new Message();
msg.setTopic("TopicA");
msg.setTags("Tag");
msg.setBody("this is a delay message".getBytes());
//设置延迟level为5,对应延迟1分钟
msg.setDelayTimeLevel(5);
producer.send(msg);
RocketMQ 4.X 版本默认支持 18 个 level 的延迟消息, 通过 broker 端的 messageDelayLevel 配置项确定的。
RocketMQ 5.X 版本支持任意时刻延迟消息,客户端在构造消息时提供了 3 个 API 来指定延迟时间或定时时间。
假如技术团队基础架构能力很强,笔者非常推荐使用 RocketMQ 5.X 的延迟消息功能。
4.2 自研延迟服务
基于 RocketMQ 4 内置的延迟消息只能支持几个固定的延迟级别,快手、滴滴开发了单独的 Delay Server 来调度延迟消息。
4.3 Redis 延迟队列
Redis 延迟队列是一个轻量级的解决方案,开源成熟的实现是 Redission 。
图中,我们定义两个集合:
1、zset 集合
生产者将任务信息发送到 zset 集合,value 是任务编号,score 是任务执行时间戳。
2、list 集合
5 最佳实践
5.1 并发口诀:一锁二判三更新
伪代码
5.2 兜底意识 + 配置监控
- 系统监控
在条件允许的情况下,建议关注性能监控,方法可用性监控,方法调用次数监控这三大类。
性能监控
上图是性能监控的示例图,性能监控不同时间段性能分布,实时统计 TP99、TP999 、AVG 、MAX 等维度指标,这也是性能调优的重点关注对象。
- 业务监控
6 总结
这篇文章,总结了订单超时自动取消方案的两种流派:定时任务和延迟消息。