Cron 调度表达式:从入门到精通
一、什么是 Cron?
Cron 是 Unix/Linux 系统中的一个经典任务调度工具,用于在指定的时间点定期执行脚本或命令。它的名字来源于希腊语 Chronos(χρόνος),意为"时间"。
Cron 最早出现在 Unix V7(1979 年),由 AT&T 贝尔实验室开发。近半个世纪以来,它已成为任务调度的工业标准——几乎所有操作系统(Linux、macOS、BSD)和云服务(AWS Lambda、Google Cloud Scheduler、Azure Functions、Vercel Cron Jobs)都支持或兼容 Cron 语法。
二、Cron 表达式格式
2.1 标准格式(5 位)
* * * * *
┬ ┬ ┬ ┬ ┬
│ │ │ │ └── 星期 (0-7, 0和7都表示周日)
│ │ │ └──── 月份 (1-12)
│ │ └────── 日期 (1-31)
│ └──────── 小时 (0-23)
└────────── 分钟 (0-59)
每个字段支持以下特殊字符:
| 字符 | 含义 | 示例 |
|---|---|---|
* |
任意值(每个单位都匹配) | * 每分钟、每小时... |
, |
枚举多个值 | 1,15 第 1 和第 15 |
- |
范围 | 1-5 1 到 5 |
/ |
步长 | */5 每 5 个单位 |
⚠️ 注意:Cron 的时间都是基于系统时区的,通常默认 UTC。
2.2 带秒的格式(6 位)
某些实现(如 Quartz、Spring、阿里云 OSS)支持 6 位表达式,在标准格式前加上秒:
0 * * * * *
┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ └── 星期 (0-7)
│ │ │ │ └──── 月份 (1-12)
│ │ │ └────── 日期 (1-31)
│ │ └──────── 小时 (0-23)
│ └────────── 分钟 (0-59)
└──────────── 秒 (0-59)
三、常见示例详解
3.1 基础示例
| 表达式 | 含义 |
|---|---|
0 0 * * * |
每小时整点执行 |
*/5 * * * * |
每 5 分钟执行一次 |
0 */2 * * * |
每 2 小时执行一次 |
0 8 * * 1-5 |
工作日每天早上 8 点 |
30 18 * * 5 |
每周五下午 6:30 |
0 0 1 * * |
每月 1 号凌晨执行 |
0 0 1 1 * |
每年 1 月 1 日执行 |
0 */30 9-18 * * |
每天 9:00~18:00 每 30 分钟执行 |
3.2 进阶示例
| 表达式 | 含义 |
|---|---|
0 0,12 * * * |
每天 0 点和 12 点执行 |
*/10 8-17 * * 1-5 |
工作日上午 8 点到下午 5 点,每 10 分钟执行 |
0 2 * * 0 |
每周日凌晨 2 点 |
0 0 1 */3 * |
每 3 个月的第 1 天执行 |
15 10 * * * |
每天 10:15 执行 |
0 3 * * 6 |
每周六凌晨 3 点 |
0 0 31 * * |
每月 31 号(没有 31 号的月份不会执行) |
四、常见 Cron 实现与差异
不同的 Cron 实现在语法上存在一些差异,值得注意:
| 特性 | Linux Cron | Quartz | Spring @Scheduled |
Node.js node-cron |
|---|---|---|---|---|
| 字段数 | 5 | 6-7 | 5-6 | 6 |
| 秒支持 | ❌ | ✅ | ✅ | ✅ |
L / W / # |
❌ | ✅ | ❌ | ❌ |
| 年字段 | ❌ | ✅ | ❌ | ❌ |
| 别名 | ✅(如 @daily) |
✅ | ✅(@Scheduled(cron="...")) |
❌ |
在 Linux 中还有这些方便的时间别名:
| 别名 | 等价表达式 | 含义 |
|---|---|---|
@reboot |
N/A | 系统启动时 |
@yearly |
0 0 1 1 * |
每年 |
@annually |
0 0 1 1 * |
每年 |
@monthly |
0 0 1 * * |
每月 |
@weekly |
0 0 * * 0 |
每周 |
@daily |
0 0 * * * |
每天 |
@hourly |
0 * * * * |
每小时 |
五、Cron 调度详解
5.1 分钟:0-59
# 每分钟执行
* * * * * command
# 每 5 分钟
*/5 * * * * command
# 在 0、15、30、45 分钟时执行
0,15,30,45 * * * * command
# 在 10~20 分钟之间每分钟执行
10-20 * * * * command
5.2 小时:0-23
# 每小时执行
0 * * * * command
# 每天 0 点
0 0 * * * command
# 每天 9 点到 18 点,每 2 小时执行(即 9、11、13、15、17 点)
0 */2 9-18 * * command
# 每天 0、8、16 点执行
0 0,8,16 * * * command
5.3 日期:1-31
# 每月 1 号和 15 号
0 0 1,15 * * command
# 每月最后一天(需要手动计算或使用工具)
0 0 28,29,30,31 * * [ "$(date +\%m -d tomorrow)" != "$(date +\%m)" ] && command
# 每 10 天执行一次
0 0 */10 * * command
5.4 月份:1-12
# 每季度初(1月、4月、7月、10月)
0 0 1 1,4,7,10 * command
# 每年 6 月到 9 月之间的工作日早上 9 点
0 9 * 6-9 1-5 command
# 半年一次(1 月和 7 月)
0 0 1 1,7 * command
5.5 星期:0-7(0 和 7 都表示周日)
# 工作日(周一到周五)早上 8 点
0 8 * * 1-5 command
# 周末(周六周日)
0 8 * * 0,6 command
# 每周日到周四,每小时的 30 分执行
30 * * * 0-4 command
# 仅在周三执行
0 0 * * 3 command
六、常见陷阱与最佳实践
6.1 五个关键陷阱
| 陷阱 | 错误示例 | 说明 |
|---|---|---|
| 日期与星期同时使用 | 0 0 1 * 1 |
每月 1 号且是周一才执行,很少是你想要的 |
| 步长起始 | */2 * * * * |
在 0、2、4...分钟执行,不是"从第一条记录开始" |
| 时区问题 | 0 8 * * * |
如果服务器 UTC 8 点 = 用户本地可能 16 点 |
| DST 调整 | 0 3 * * * |
夏令时切换时 3 点可能跳过一次或执行两次 |
| 秒级精度 | * * * * * script |
非秒级精度,平均每 60 秒执行,不会精确到秒 |
6.2 最佳实践
1. 日期与星期避免同时使用
# ❌ 容易误解
0 0 15 * 5 # 每月 15 号且是周五才执行
# ✅ 用两个独立任务
0 0 15 * * command # 每月 15 号
0 0 * * 5 command # 每周五
2. 使用随机延迟避免"惊群效应"
# ❌ 整点每分钟执行,太多任务同时触发
0 * * * * curl https://api.example.com/sync
# ✅ 在 0~9 分钟间随机延迟
# 在脚本内加入:sleep $((RANDOM % 600))
0 * * * * bash -c 'sleep $((RANDOM % 600)); curl https://api.example.com/sync'
3. 日志与输出重定向
# ❌ 丢失输出
*/5 * * * * /usr/bin/backup.sh
# ✅ 记录日志
*/5 * * * * /usr/bin/backup.sh >> /var/log/backup.log 2>&1
# ✅ 使用 logger 写入 syslog
*/5 * * * * /usr/bin/backup.sh | logger -t backup
4. 不要在脚本中使用相对路径
# ❌ 可能找不到命令
0 3 * * * mysqldump -u root test > backup.sql
# ✅ 使用绝对路径
0 3 * * * /usr/bin/mysqldump -u root test > /backup/$(date +\%Y\%m\%d).sql
# ✅ 或先 cd 到目标目录
0 3 * * * cd /backup && /usr/bin/mysqldump -u root test > backup.sql
5. 用文本注释说明含义
# ┌────────── 每周日凌晨 3:00 全量备份
# │ 由于全量备份耗时约 30 分钟,安排在非高峰时段
# │ 日志: /var/log/backup-full.log
# ▼
0 3 * * 0 /usr/bin/backup.sh --full
七、在线工具与调试
| 工具 | 地址 | 用途 |
|---|---|---|
| Crontab Guru | https://crontab.guru | 最常用的在线 Cron 解析 |
| Cron Expression Generator | https://www.freeformatter.com/cron-expression-generator-quartz.html | Quartz 格式生成器 |
| 中文 Crontab | https://www.bejson.com/othertools/cron/ | 中文界面,方便快速验证 |
快速调试命令:
# 查看当前用户的 crontab
crontab -l
# 编辑 crontab
crontab -e
# 查看 cron 日志(Ubuntu/Debian)
grep CRON /var/log/syslog
# 查看 cron 日志(RHEL/CentOS)
tail -f /var/log/cron
八、各语言/平台的 Cron 用法
Shell / Linux Crontab
# 编辑当前用户定时任务
crontab -e
# 格式
0 2 * * * /path/to/script.sh
Node.js (node-cron)
const cron = require('node-cron')
cron.schedule('*/5 * * * *', () => {
console.log('每 5 分钟执行')
})
Python (croniter)
import croniter
import datetime
# 解析 cron 表达式
cron = croniter.croniter('*/5 * * * *', datetime.datetime.now())
next_time = cron.get_next(datetime.datetime)
print(f"下次执行: {next_time}")
GitHub Actions
on:
schedule:
# 每天早上 8 点(UTC)
- cron: '0 8 * * *'
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "定时任务执行"
Vercel Cron Jobs
{
"crons": [
{
"path": "/api/sync",
"schedule": "0 */3 * * *"
}
]
}
总结
| 要点 | 说明 |
|---|---|
| Cron 是什么 | 基于时间的任务调度器,5 个字段描述执行周期 |
| 核心字段 | 分钟 小时 日期 月份 星期 |
| 特殊字符 | * 任意、, 枚举、- 范围、/ 步长 |
| 最大陷阱 | 日期+星期同时使用、时区问题、DST 切换 |
| 最常用表达式 | * * * * * / */5 * * * * / 0 0 * * * |
掌握 Cron 表达式的写法,是运维和云原生开发的基本功。"先理解时间逻辑,再写表达式"——用自然语言描述你的需求("每个工作日的上午 9 点到 17 点,每 30 分钟一次"),再翻译成 Cron 表达式,这样基本不会出错。
Comments | 0条评论