配置调度频率:cron 表达式详解

调度频率怎么设?

刚接触 Splunk 定时任务时,我第一反应就是 “设个每隔X分钟跑一次不就行了?” 后来发现在生产环境里根本不够用——错峰执行、业务低谷跑、特定工作日排除…… 这些全得靠 cron 表达式。

说白了,cron 就是一套描述 “什么时候执行” 的 DSL。Splunk 直接用了标准 Unix cron 格式,稍微加了点自己的小脾气。今天咱们把它彻底讲透。

4.1 从标准 cron 说起

标准 cron 表达式长这样:

分 时 日 月 周

五个字段,用空格隔开。每个字段可以是数字、逗号分隔列表、范围、步长或者通配符。

字段 取值范围 通配符 示例
分钟 (0-59) 0‑59 * / , - 15 → 每小时的第15分钟
小时 (0-23) 0‑23 * / , - 2 → 凌晨2点
日期 (1-31) 1‑31 * / , - 1,15 → 每月1号和15号
月份 (1-12) 1‑12 或 JAN‑DEC * / , - 6,12 → 6月和12月
星期 (0-6) 0‑6(0=周日)或 SUN‑SAT * / , - 1‑5 → 周一到周五

举个例子:30 3 * * 1-5 表示 “每个工作日(周一到周五)的凌晨3:30执行一次”。

4.2 Splunk 的 cron 有何不同?

嗯,这里要注意——Splunk 的定时任务管理器底层用的是 Python 的 crontab 模块,所以在兼容标准五字段的基础上,有几个关键差异:

  • 秒级支持:*/30 * * * * * 代表每30秒执行一次(六字段,前面加秒)
  • 星期字段使用 0‑6:0 是周日,1 是周一……6 是周六,跟标准完全一致
  • 不支持 “L”、“W”、“#” 等扩展:这是 Quartz 的那套,Splunk 原生不认

最常用技巧:如果你想避免 “整点并发压力”,把分钟设成一个随机偏移量。

别用 0 * * * *,换成 23 * * * *——这种小习惯能救你一跳。

4.3 实战:我踩过的一个坑

我曾经配过一个定时报表,要求 “每月最后一天晚上23:00跑”。当时脑子一热写了:

0 23 31 * *

结果 2 月直接跳过不执行。3 月 31 号跑了,4 月又跳过…… 问题在哪里?

cron 无法判断 “最后一天”。正确方案是:

用脚本绕开限制:每天跑一遍,检查明天是否跨月。

0 23 28-31 * *  /opt/splunk/bin/scripts/check_last_day.sh
# 脚本里判断如果今天是当月最后一天,才真正触发报表

或者更简单——设成每月 1 号凌晨 00:30 跑上个月报表:30 0 1 * *

4.4 常用 cron 模式速查表

我个人习惯把这几个模板存成碎片,用的时候直接改数字:

需求 cron 表达式 说明
每5分钟一次 */5 * * * * 适合高频监控告警
每小时的第15分钟 15 * * * * 错开整点负载
每天凌晨3点 0 3 * * * 日汇总任务
每周一凌晨4点 0 4 * * 1 周报生成
每月1号凌晨5:30 30 5 1 * * 月报
每天9点到18点之间每10分钟 */10 9-18 * * * 工作时段的监控
每30秒一次 */30 * * * * * 六字段,用于实时刷新仪表板(慎用)

4.5 踩坑避雷指南

我曾遇到一次事故:把 0 0 * * * 写成了 0 0 * * 0,以为0是代表每天,结果任务只在周日跑。排查了两小时才发现字段顺序和星期映射搞反了。

记住:星期的 0 是周日,不是周一。Splunk 的星期字段里 7 无效,别用。

还有其他常见坑:

  • 月份字段里 0 无效:月份从 1 开始
  • 日期和星期同时使用 = 或:如 0 0 1,15 * 1-5 表示 “每月1号或15号,或者任意工作日”,不是你想象的两者交集
  • 步长行为:1-10/2 等于 1,3,5,7,9,不是 0‑10 隔两步

4.6 用一张图看懂 cron 工作原理

下面这张我画的流程图,把 cron 引擎的每次匹配逻辑讲清楚了。你只要看一遍,以后写表达式就知道哪个字段先比、哪个字段后比。

cron 引擎匹配决策流程 定时器触发 解析当前时间:分 时 日 月 周 第1步:分钟匹配 ? 否 是 第2步:小时匹配 ? 否 是 第3步:日期 OR 星期至少一个匹配 且月份匹配 否 是 执行定时任务 跳过本次调度 等待下一分钟

看到没?分钟、小时依次检查,日期和星期却是 “或” 的关系。这个设计让很多人意外——我建议你把这个图存下来,写表达式时对着脑子过一遍。

4.7 调试 cron 表达式的三板斧

  1. Splunk Web 里的 “Next Scheduled Time”:创建任务时点 “Show Advanced”,它能实时计算出接下来5次触发时间。我一般先用它验证。
  2. Python 命令行验证:
python3 -c "
import croniter, datetime
base = datetime.datetime.now()
cron = croniter.croniter('30 3 * * 1-5', base)
for i in range(5):
    print(cron.get_next(datetime.datetime))
"

这个脚本我几乎每周都用,特别是调试月末、跨年场景时。

  1. 打日志观察:在任务脚本开头加一行 echo "$(date) 任务触发" >> /tmp/cron_debug.log,然后观察实际触发时间序列。

小技巧:怀疑 cron 被跳过时,先在线上建一个只写日志的空任务,跑两三天,收集触发记录。比你对着文档猜快多了。

4.8 一个完整的生产案例

我去年给一个电商平台配的 “大促期间业务监控任务”,要求:

  • 大促日(比如双11当天)每分钟扫一次库存异常
  • 平时每 10 分钟扫一次
  • 凌晨 1-5 点不扫(数据库备份窗口)

你的第一反应可能是建两个任务。我用了更优雅的方式——写一个 wrapper 脚本,里面判断日期,再决定执行频率。cron 表达式统一设成:

*/1 5-23,0 * * *

然后在脚本里:

# 如果是非大促日,且当前分钟不是10的倍数,直接退出
if [ "$(date +%Y%m%d)" != "20251111" ]; then
  min=$(date +%M)
  if [ $((10#$min % 10)) -ne 0 ]; then
    exit 0
  fi
fi
# 执行真正的库存监控逻辑

这样 cron 每分钟都在尝试,但脚本自己决定要不要干活。好处是——不用改 crontab,哪天临时加个大促日,改脚本里的日期变量即可。

注意:这个方案的代价是每分钟 fork 一个进程判断时间,对高并发服务器可能会有几毫秒开销。如果机器资源紧张,还是老老实实建两个独立任务吧。