定时任务总览
定时任务解决的是“到某个时间点或按某个周期自动执行一段业务逻辑”的问题。商业系统里非常常见,比如订单超时关闭、每天生成报表、T+1 对账、优惠券过期、会员等级刷新、ES 数据补偿、库存同步、账单结算、消息失败补偿。
零基础先记住一句话:
定时任务不是简单写一个
cron就完事,它真正要解决的是触发、执行、记录、重试、告警、幂等、并发、分片和故障恢复。
本目录内容
如果你想检查自己是否真的从零基础学懂到能面试、能落地、能排查,按 定时任务从零到精通验收清单 逐项验收。它会把 Cron、@Scheduled、Quartz、XXL-JOB、延迟消息、幂等、分片、阻塞策略、补偿、告警和生产排查串起来。
为什么需要定时任务
很多业务不是用户点击按钮立即完成,而是需要系统在后台自动推进。
| 商业场景 | 任务例子 | 如果没有定时任务会怎样 |
|---|---|---|
| 订单系统 | 30 分钟未支付自动关单 | 未支付订单长期占库存 |
| 支付系统 | 每天凌晨对账 | 资金差异不能及时发现 |
| 会员系统 | 每晚刷新会员等级 | 等级权益不准确 |
| 营销系统 | 优惠券到期失效 | 过期券仍可使用 |
| 搜索系统 | 定时补偿同步 ES | 商品改价后搜索页长期不一致 |
| 报表系统 | 每小时汇总经营指标 | 页面实时查库压力大 |
| 风控系统 | 定时扫描异常交易 | 风险事件发现滞后 |
定时任务的价值不是“省人工”,而是把可重复、可批量、可补偿的后台动作系统化。
定时任务的基本流程
flowchart TD
A["配置任务<br/>cron、参数、负责人"] --> B["调度器计算触发时间"]
B --> C["到点触发任务"]
C --> D["选择执行器或本机线程"]
D --> E["执行业务逻辑"]
E --> F{"执行结果"}
F -- "成功" --> G["记录成功日志"]
F -- "失败" --> H["记录失败日志"]
H --> I["重试或告警"]
G --> J["等待下一次调度"]
I --> J这个流程里,最容易被忽略的是日志、重试和幂等。只要系统上线,就一定会遇到任务失败、重复执行、执行超时、机器重启、数据库慢、网络抖动这些情况。
常见调度方案
| 方案 | 适合场景 | 优点 | 风险 |
|---|---|---|---|
| Linux crontab | 简单脚本、运维任务 | 简单直接 | 缺少业务日志、重试、分布式治理 |
Spring @Scheduled | 单体应用、低频本地任务 | 上手快、代码少 | 多实例会重复执行,监控弱 |
| Quartz | Java 应用内调度、需要持久化 | 成熟、支持集群 | 配置和表结构相对重 |
| XXL-JOB | 微服务分布式任务调度 | 控制台、日志、路由、分片、重试、告警 | 需要部署调度中心 |
| MQ 延迟消息 | 到点触发某条业务数据 | 适合订单关单、失败重试 | 不适合复杂批量扫描和运营任务 |
| K8s CronJob | 容器化批处理 | 和 K8s 集成好 | 业务治理能力弱,Java 方法级任务不方便 |
商业后端最常见组合:
- 小型单体:
@Scheduled+ 分布式锁。 - 微服务系统:XXL-JOB 统一调度。
- 订单超时这类单条数据到期:MQ 延迟消息 + 定时补偿。
- 容器运维脚本:K8s CronJob。
单机任务和分布式任务区别
flowchart TD
A["同一个服务部署 3 个实例"] --> B{"使用 @Scheduled"}
B --> C["实例 A 到点执行"]
B --> D["实例 B 到点执行"]
B --> E["实例 C 到点执行"]
C --> F["可能重复处理同一批数据"]
D --> F
E --> F
A --> G{"使用分布式调度"}
G --> H["调度中心选择一个或多个执行器"]
H --> I["按路由、分片、策略执行"]单机任务的问题不是“不能跑”,而是多实例部署后会一起跑。如果任务是刷新缓存还好,如果任务是关单、发券、结算,就可能重复扣减、重复发放、重复生成数据。
定时任务必须关注的能力
| 能力 | 为什么重要 | 例子 |
|---|---|---|
| 幂等 | 防止重复执行造成脏数据 | 关单时 where status = WAIT_PAY |
| 超时控制 | 防止任务卡死占住线程 | 单次任务最多执行 10 分钟 |
| 失败重试 | 临时网络抖动后可恢复 | 调用下游失败后重试 3 次 |
| 执行日志 | 出问题能知道跑了什么 | 记录任务参数、耗时、结果 |
| 告警 | 失败后及时通知负责人 | 连续失败发送企业微信 |
| 分片 | 大数据量并行处理 | 10 个执行器各处理一部分订单 |
| 阻塞策略 | 上次没跑完时怎么办 | 丢弃、串行、覆盖 |
| 错过触发处理 | 服务停机期间任务错过 | 补偿跑或只跑下一次 |
Cron 表达式
Cron 用来描述执行时间。常见字段含义:
秒 分 时 日 月 周常见例子:
| 表达式 | 含义 |
|---|---|
0 0 2 * * ? | 每天凌晨 2 点 |
0 */5 * * * ? | 每 5 分钟 |
0 0/30 9-18 * * ? | 每天 9 点到 18 点,每 30 分钟 |
0 0 1 ? * MON | 每周一凌晨 1 点 |
注意:不同框架的 Cron 字段数量可能不同。Linux crontab 通常没有“秒”,Spring/XXL-JOB 常见有“秒”。复制表达式前要确认框架规则。
订单超时关闭示例
最朴素的思路是定时扫描超时订单:
select id
from t_order
where status = 'WAIT_PAY'
and created_at < now() - interval 30 minute
order by id
limit 500;更新时一定要带状态条件:
update t_order
set status = 'CLOSED',
close_reason = 'PAY_TIMEOUT',
updated_at = now()
where id = ?
and status = 'WAIT_PAY';为什么要带 status = 'WAIT_PAY':因为任务查出来之后,用户可能刚好支付成功。如果更新时不校验状态,就可能把已支付订单关掉。
定时任务和延迟消息怎么选
| 对比 | 定时扫描 | 延迟消息 |
|---|---|---|
| 触发方式 | 周期性扫一批数据 | 每条数据创建时发送一条延迟消息 |
| 适合 | 对账、报表、补偿、批量同步 | 订单超时、退款超时、验证码过期 |
| 优点 | 容易补偿,不怕单条消息丢失 | 时间更精确,压力更均匀 |
| 风险 | 扫描范围大时压数据库 | 消息丢失或积压要兜底 |
商业项目里经常两者都用:订单创建时发延迟消息,到期关单;再加一个低频定时任务扫描漏网订单,作为补偿。
学习路线
flowchart TD
A["先理解本地 @Scheduled"] --> B["理解多实例重复执行问题"]
B --> C["学习幂等、锁、分页、批处理"]
C --> D["学习 XXL-JOB 架构"]
D --> E["学习执行器、路由、分片、阻塞策略"]
E --> F["学习失败重试、告警、日志"]
F --> G["落地订单、对账、报表、补偿场景"]
G --> H["排查不触发、重复执行、执行慢、失败堆积"]本章小结
定时任务不是简单的“到点跑方法”。真正可上线的定时任务至少要想清楚:
- 任务由谁触发。
- 在哪台机器执行。
- 重复执行会不会出错。
- 执行失败怎么重试。
- 执行慢怎么限流。
- 执行结果在哪里看。
- 长期失败谁会收到告警。
这些问题想清楚后,再选择 @Scheduled、XXL-JOB、MQ 延迟消息或 K8s CronJob,才不会上线后靠运气跑任务。
