我们在移动APP 下单,假如没有立即支付,进入订单详情会显示倒计时,如果超过支付时间,订单就会被自动取消。

网上有很多八股文,很多方案其实并不太适合真实的业务场景。

Java中订单到期用什么么时间格式存储_java

1 定时任务方案

我们自然的想到定时任务的方案。

方案流程:

  1. 每隔 30 秒查询数据库,取出最近的 N 条未支付的订单。
  2. 遍历查询出来的订单列表,判断当前时间减去订单的创建时间是否超过了支付超时时间,如果超时则对该订单执行取消操作。

这种方案会间隔对数据库造成一定的 IO 压力,特别是当订单量数据量非常高时,高频次的查询对数据库的性能是个不小的考验。

定时任务方案从功能模块角度来讲,包含调度层业务逻辑层两部分。

Java中订单到期用什么么时间格式存储_java_02

网上有很多的定时任务实现策略,我们可以简单划分为单机版集群版

2 定时任务方案:单机版

我们可以使用 Timer 、ScheduledEexcutorService、Quartz 非常容易的实现定时任务。

Java中订单到期用什么么时间格式存储_Java中订单到期用什么么时间格式存储_03

但笔者并不推荐使用单机版的方案,举个简单的例子:

Java中订单到期用什么么时间格式存储_java_04

假设我们应用 A 通过 Quartz 调度三个定时任务 A、B、C  ,当集群部署时,可能出现多台不同机器实例同时执行任务的风险。

此时,我们可以通过加锁的方式适当规避,见下图:

Java中订单到期用什么么时间格式存储_java_05

但这种方式并不优雅,同时定时任务应用内调度层会经常空跑,我们预期是希望三个定时任务 A、B、C 能均匀分布应用 A的不同实例内。

好,接下来,笔者会介绍亲身经历的三种集群定时任务。

3 定时任务方案:集群版

Java中订单到期用什么么时间格式存储_定时任务_06

3.1 Quartz + JDBCJobStore

Quartz 可以支持集群模式,集群模式需要在数据库中添加11张表,对业务系统有一定的侵入性。

Java中订单到期用什么么时间格式存储_Java中订单到期用什么么时间格式存储_07

笔者曾经服务的一家彩票公司,订单调度中心就是使用 Quartz 的集群模式,实现日均百万订单的调度处理。

需要特别注意的是:

基于底层数据库悲观锁的机制,Quartz 的集群模式性能并不高,假如执行频率高的任务数超过达到一定数量,存在性能问题。

3.1 Elastic-Job

Java中订单到期用什么么时间格式存储_spring boot_08

Java中订单到期用什么么时间格式存储_定时任务_09

Java中订单到期用什么么时间格式存储_spring boot_10

3.3 任务调度平台

笔者非常认可任务调度平台这种模式。XXL-JOB 是一个使用最广泛的分布式任务调度平台

Java中订单到期用什么么时间格式存储_java_11

Java中订单到期用什么么时间格式存储_Java中订单到期用什么么时间格式存储_12

Java中订单到期用什么么时间格式存储_性能监控_13

4 延时消息方案

Java中订单到期用什么么时间格式存储_定时任务_14

Java中订单到期用什么么时间格式存储_Java中订单到期用什么么时间格式存储_15

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 配置项确定的。

Java中订单到期用什么么时间格式存储_定时任务_16

RocketMQ 5.X 版本支持任意时刻延迟消息,客户端在构造消息时提供了 3 个 API 来指定延迟时间或定时时间。

Java中订单到期用什么么时间格式存储_定时任务_17

假如技术团队基础架构能力很强,笔者非常推荐使用 RocketMQ 5.X 的延迟消息功能。

4.2 自研延迟服务

基于 RocketMQ 4 内置的延迟消息只能支持几个固定的延迟级别,快手、滴滴开发了单独的 Delay Server 来调度延迟消息。

Java中订单到期用什么么时间格式存储_spring boot_18

Java中订单到期用什么么时间格式存储_性能监控_19

4.3 Redis 延迟队列

Redis 延迟队列是一个轻量级的解决方案,开源成熟的实现是 Redission 。

Java中订单到期用什么么时间格式存储_Java中订单到期用什么么时间格式存储_20

图中,我们定义两个集合:

1、zset 集合

生产者将任务信息发送到 zset 集合,value 是任务编号,score 是任务执行时间戳。

2、list 集合

Java中订单到期用什么么时间格式存储_java_21

5 最佳实践

5.1 并发口诀:一锁二判三更新

Java中订单到期用什么么时间格式存储_java_22



Java中订单到期用什么么时间格式存储_java_23

Java中订单到期用什么么时间格式存储_定时任务_24

伪代码

5.2 兜底意识 + 配置监控

Java中订单到期用什么么时间格式存储_spring boot_25

  • 系统监控

在条件允许的情况下,建议关注性能监控,方法可用性监控,方法调用次数监控这三大类。

Java中订单到期用什么么时间格式存储_spring boot_26

性能监控

上图是性能监控的示例图,性能监控不同时间段性能分布,实时统计 TP99、TP999 、AVG 、MAX 等维度指标,这也是性能调优的重点关注对象。

  • 业务监控

Java中订单到期用什么么时间格式存储_spring boot_27

6 总结

这篇文章,总结了订单超时自动取消方案的两种流派:定时任务延迟消息

Java中订单到期用什么么时间格式存储_spring boot_28