← 返回博客首页

Cron 表达式深入解析:字段、特殊字符、常见模式与排错指南

什么是 Cron 表达式?

Cron 表达式是一种用于定义定时任务执行时间的字符串,最初源于 Unix 的 cron 守护进程。如今它被广泛应用于各类调度系统——从 Linux crontab 到 Spring 的 @Scheduled、Quartz Scheduler、Kubernetes CronJob 等。

一个 Cron 表达式由 5 到 7 个字段组成,每个字段代表一个时间单位。

字段结构

标准 5 字段 Cron

* * * * *
│ │ │ │ │
│ │ │ │ └── 星期 (0-7,0 和 7 都表示周日)
│ │ │ └──── 月份 (1-12)
│ │ └────── 日 (1-31)
│ └──────── 时 (0-23)
└─────────── 分 (0-59)

Quartz 6/7 字段 Cron

Quartz Scheduler 使用 6 或 7 个字段,增加了秒和可选的年:

* * * * * * *
│ │ │ │ │ │ │
│ │ │ │ │ │ └── 年 (可选,1970-2099)
│ │ │ │ │ └──── 星期 (1-7,1=周日)
│ │ │ │ └────── 月份 (1-12)
│ │ │ └──────── 日 (1-31)
│ │ └────────── 时 (0-23)
│ └──────────── 分 (0-59)
└─────────────── 秒 (0-59)

字段对照表

字段 标准 Cron 范围 Quartz 范围 允许的特殊字符
0-59 , - * /
0-59 0-59 , - * /
0-23 0-23 , - * /
1-31 1-31 , - * ? L W C
1-12 或 JAN-DEC 1-12 或 JAN-DEC , - * /
星期 0-7 或 SUN-SAT 1-7 或 SUN-SAT , - * ? L C #
1970-2099 , - * /

特殊字符详解

* — 任意值

表示该字段的所有合法值。例如 * * * * * 表示每分钟执行。

, — 列举

指定多个离散值:

0 0 9,12,18 * * ?    // 每天 9:00、12:00、18:00 执行

- — 范围

指定一个连续区间:

0 0 9-17 * * ?       // 每天 9:00 到 17:00,每小时执行

/ — 步长

起始值/步长,从起始值开始每隔步长执行:

0 */15 * * * ?       // 每 15 分钟执行一次
0 0/30 * * * ?       // 每小时的 0 分和 30 分执行

? — 不指定(仅 Quartz)

仅用于日和星期字段,表示"不关心"。因为日和星期可能冲突,Quartz 要求两者不能同时使用 *,必须有一个是 ?

0 0 12 ? * MON       // 每周一中午 12:00(日字段用 ? 忽略)
0 0 12 1 * ?         // 每月 1 号中午 12:00(星期字段用 ? 忽略)

L — 最后(Last,仅 Quartz)

  • 在日字段:表示当月最后一天
  • 在星期字段:表示当月最后一个指定的星期几
0 0 23 L * ?         // 每月最后一天 23:00 执行
0 0 10 ? * 6L        // 每月最后一个周五 10:00 执行

W — 最近工作日(仅 Quartz)

在日字段使用,表示最接近指定日期的工作日(周一至周五):

0 0 9 15W * ?        // 每月最接近 15 号的工作日 9:00 执行

如果 15 号是周六,则在 14 号(周五)执行;如果是周日,则在 16 号(周一)执行。

# — 第 N 个星期几(仅 Quartz)

星期几#第几个

0 0 10 ? * 2#1       // 每月第一个周一 10:00 执行
0 0 10 ? * 6#3       // 每月第三个周五 10:00 执行

常见调度模式

高频任务

表达式 说明
* * * * * 每分钟
*/5 * * * * 每 5 分钟
0 * * * * 每小时整点
0 */2 * * * 每 2 小时

日常任务

表达式 说明
0 0 * * * 每天午夜
0 9 * * * 每天上午 9 点
0 9 * * 1-5 工作日早上 9 点
0 22 * * 1-5 工作日晚上 10 点

周期任务

表达式 说明
0 0 1 * * 每月 1 号
0 0 1 1 * 每年 1 月 1 号
0 0 * * 0 每周日
0 0 1 */3 * 每季度第一天

Quartz 专用模式

表达式 说明
0 0 12 ? * MON-FRI 工作日中午(Quartz 格式)
0 0 23 L * ? 每月最后一天 23:00
0 0 9 15W * ? 最接近 15 号的工作日 9:00
0 0 10 ? * 6#3 每月第三个周五 10:00
0 30 10-14 ? * MON,WED,FRI 周一三五 10:30-14:30 每小时

Quartz vs 标准 Cron 核心差异

特性 标准 Cron Quartz
字段数 5 6 或 7
秒级精度
年字段 ✅(可选)
? 字符 ✅(日/星期字段必填其一)
L 字符
W 字符
# 字符
星期编码 0=周日, 7=周日 1=周日, 7=周六
日和星期关系 两者 OR 关系 两者必须其一为 ?

最容易踩的坑

  1. 日和星期同时指定:标准 Cron 中两者是 OR 关系(满足任一即执行),Quartz 中则必须用 ? 明确忽略一个
  2. 星期编号不同:标准 Cron 的 07 都是周日;Quartz 的 1 是周日,7 是周六
  3. 秒字段:从标准 Cron 迁移到 Quartz 时容易忘记加秒字段

排错指南

1. 表达式不触发

检查清单

  • 时区是否正确?Quartz 默认使用 JVM 时区
  • 日和星期字段是否冲突?Quartz 要求其一为 ?
  • 步长语法是否正确?*/15 而非 0-59/15(部分实现不支持后者)
  • 月份天数是否合法?0 0 0 31 2 * 永远不会触发(2 月没有 31 号)

2. 触发频率异常

# 意图:每 5 分钟执行
*/5 * * * *     # ✅ 正确:0, 5, 10, 15...
0/5 * * * *     # ⚠️ 部分系统等价,部分不等价
0-59/5 * * * *  # ✅ 显式范围步长

3. 时区问题

  • Linux cron 使用服务器本地时区
  • Quartz 可以通过 CronTrigger 设置时区
  • Kubernetes CronJob 使用 UTC,需注意换算
// Quartz 指定时区
CronTrigger trigger = TriggerBuilder.newTrigger()
    .withSchedule(CronScheduleBuilder
        .cronSchedule("0 0 9 * * ?")
        .inTimeZone(TimeZone.getTimeZone("Asia/Shanghai")))
    .build();

4. 2 月 29 日问题

0 0 0 29 2 * 只在闰年触发。如果需要年度任务,建议用程序逻辑判断而非依赖 cron。

5. 冬令时/夏令时

在实行夏令时的时区,切换当天某些时间会重复或跳过。建议:

  • 避免在 2:00-3:00 之间调度关键任务
  • 使用 UTC 时间调度

在线验证工具

建议在部署前使用在线 Cron 表达式验证工具检查:

最佳实践

  1. 明确时区:始终在文档和配置中注明 cron 表达式使用的时区
  2. 避免边界时间:不要用 0 0 0 * * *(午夜零点),多服务同时触发会造成负载尖峰,建议加随机偏移
  3. 注释清晰:在配置文件中为每个 cron 表达式添加注释说明触发逻辑
  4. 幂等设计:任务逻辑应幂等,防止重复执行造成数据问题
  5. 设置超时和重试:防止任务卡死阻塞后续调度
  6. 监控告警:对关键定时任务设置执行失败告警
← 返回博客首页