Skip to content

定时任务总览

定时任务解决的是“到某个时间点或按某个周期自动执行一段业务逻辑”的问题。商业系统里非常常见,比如订单超时关闭、每天生成报表、T+1 对账、优惠券过期、会员等级刷新、ES 数据补偿、库存同步、账单结算、消息失败补偿。

零基础先记住一句话:

定时任务不是简单写一个 cron 就完事,它真正要解决的是触发、执行、记录、重试、告警、幂等、并发、分片和故障恢复。

本目录内容

如果你想检查自己是否真的从零基础学懂到能面试、能落地、能排查,按 定时任务从零到精通验收清单 逐项验收。它会把 Cron、@Scheduled、Quartz、XXL-JOB、延迟消息、幂等、分片、阻塞策略、补偿、告警和生产排查串起来。

为什么需要定时任务

很多业务不是用户点击按钮立即完成,而是需要系统在后台自动推进。

商业场景任务例子如果没有定时任务会怎样
订单系统30 分钟未支付自动关单未支付订单长期占库存
支付系统每天凌晨对账资金差异不能及时发现
会员系统每晚刷新会员等级等级权益不准确
营销系统优惠券到期失效过期券仍可使用
搜索系统定时补偿同步 ES商品改价后搜索页长期不一致
报表系统每小时汇总经营指标页面实时查库压力大
风控系统定时扫描异常交易风险事件发现滞后

定时任务的价值不是“省人工”,而是把可重复、可批量、可补偿的后台动作系统化。

定时任务的基本流程

mermaid
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单体应用、低频本地任务上手快、代码少多实例会重复执行,监控弱
QuartzJava 应用内调度、需要持久化成熟、支持集群配置和表结构相对重
XXL-JOB微服务分布式任务调度控制台、日志、路由、分片、重试、告警需要部署调度中心
MQ 延迟消息到点触发某条业务数据适合订单关单、失败重试不适合复杂批量扫描和运营任务
K8s CronJob容器化批处理和 K8s 集成好业务治理能力弱,Java 方法级任务不方便

商业后端最常见组合:

  1. 小型单体:@Scheduled + 分布式锁。
  2. 微服务系统:XXL-JOB 统一调度。
  3. 订单超时这类单条数据到期:MQ 延迟消息 + 定时补偿。
  4. 容器运维脚本:K8s CronJob。

单机任务和分布式任务区别

mermaid
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 用来描述执行时间。常见字段含义:

text
秒 分 时 日 月 周

常见例子:

表达式含义
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 常见有“秒”。复制表达式前要确认框架规则。

订单超时关闭示例

最朴素的思路是定时扫描超时订单:

sql
select id
from t_order
where status = 'WAIT_PAY'
  and created_at < now() - interval 30 minute
order by id
limit 500;

更新时一定要带状态条件:

sql
update t_order
set status = 'CLOSED',
    close_reason = 'PAY_TIMEOUT',
    updated_at = now()
where id = ?
  and status = 'WAIT_PAY';

为什么要带 status = 'WAIT_PAY':因为任务查出来之后,用户可能刚好支付成功。如果更新时不校验状态,就可能把已支付订单关掉。

定时任务和延迟消息怎么选

对比定时扫描延迟消息
触发方式周期性扫一批数据每条数据创建时发送一条延迟消息
适合对账、报表、补偿、批量同步订单超时、退款超时、验证码过期
优点容易补偿,不怕单条消息丢失时间更精确,压力更均匀
风险扫描范围大时压数据库消息丢失或积压要兜底

商业项目里经常两者都用:订单创建时发延迟消息,到期关单;再加一个低频定时任务扫描漏网订单,作为补偿。

学习路线

mermaid
flowchart TD
    A["先理解本地 @Scheduled"] --> B["理解多实例重复执行问题"]
    B --> C["学习幂等、锁、分页、批处理"]
    C --> D["学习 XXL-JOB 架构"]
    D --> E["学习执行器、路由、分片、阻塞策略"]
    E --> F["学习失败重试、告警、日志"]
    F --> G["落地订单、对账、报表、补偿场景"]
    G --> H["排查不触发、重复执行、执行慢、失败堆积"]

本章小结

定时任务不是简单的“到点跑方法”。真正可上线的定时任务至少要想清楚:

  1. 任务由谁触发。
  2. 在哪台机器执行。
  3. 重复执行会不会出错。
  4. 执行失败怎么重试。
  5. 执行慢怎么限流。
  6. 执行结果在哪里看。
  7. 长期失败谁会收到告警。

这些问题想清楚后,再选择 @Scheduled、XXL-JOB、MQ 延迟消息或 K8s CronJob,才不会上线后靠运气跑任务。