跳转到主要内容
技术

cron 时区陷阱 - 避免定时任务的漂移

cron 的时区行为 - 对系统设置的隐式依赖

Linux 的 cron 守护进程默认使用系统时区 (/etc/localtime 或 TZ 环境变量) 来解释调度表达式。写下“0 2 * * *”意味着系统本地时间的凌晨 2:00。这在单台服务器上直观易懂,但在多台服务器运行不同时区或云实例默认为 UTC 的环境中,任务会在非预期的时间运行。

AWS EC2 实例默认为 UTC。要在日本时间凌晨 2:00 运行批处理任务,必须写“0 17 * * *”(UTC 17:00 = JST 次日凌晨 2:00)。手动转换容易出错,而夏令时地区还会增加一层复杂性,进一步混淆计算。

夏令时危害 - 重复执行与跳过

当 cron 以本地时间运行在有夏令时的地区时,每年会出现两次问题。春季切换时 (如凌晨 2:00 跳到 3:00),计划在 2:00-2:59 执行的任务会被完全跳过。秋季切换时 (如凌晨 2:00 回拨到 1:00),计划在 1:00-1:59 执行的任务会运行两次。

数据库备份运行两次可能不会造成太大实际危害,但计费或汇总批处理运行两次则是严重问题。反过来,一个关键的每日任务被跳过可能直到第二天才被发现。根本的解决方案是让 cron 以 UTC 运行,从根本上规避这个问题。

UTC 运行 - 最安全的默认方案

将系统时区设为 UTC 并以 UTC 编写 cron 调度表达式是最安全的运维模式。UTC 没有夏令时,因此原则上既不会重复执行也不会跳过。要在“日本时间凌晨 2:00”运行某任务,只需写一次“0 17 * * *”即可; 该任务全年在相同的 UTC 时刻运行。

UTC 运行也有注意事项。一个原本应在“每月 1 日午夜 (JST)”运行的任务,在 UTC 中变成了“上月最后一天的 15:00”。由于上月最后一天在 28 到 31 之间变化,没有固定的 cron 表达式能完美表达这一需求。这类任务需要在固定的 UTC 时间启动,并在内部逻辑中验证 JST 月份是否已经切换。

Kubernetes CronJob - timeZone 字段

Kubernetes CronJob 自 v1.27 (GA) 起支持 timeZone 字段。将 spec.timeZone 设为 IANA 名称 (如 Asia/Tokyo) 会使调度按该时区解释,消除手动 UTC 转换并提高可读性。工程师可以按照自己的思维方式编写调度表达式。

即使使用了 timeZone,夏令时行为仍需关注。Kubernetes 控制器能感知夏令时切换,但其对跳过或重复时间的处理方式可能因版本而异。对于关键任务,以 UTC 定义调度并将 timeZone 保留给可读性最重要的场景,是更安全的折中方案。

云调度器 - 内置时区支持

AWS EventBridge Scheduler、Google Cloud Scheduler 和 Azure Logic Apps 都原生支持时区指定。AWS EventBridge 在 ScheduleExpression 中接受时区并自动处理夏令时切换。然而,各服务对“春季前拨时不存在的时间”的处理方式各不相同,因此请查阅所使用服务的具体文档。

无论使用哪种调度器,都要在注释中记录每个调度的意图。仅凭“0 17 * * *”本身,未来的维护者无法判断这是 UTC 17:00 还是用 UTC 表示的 JST 2:00。即使是基础设施即代码也应该像记录应用程序业务逻辑一样,以显式注释记录预期的时区。

XB!LINE

这篇文章对您有帮助吗?

相关文章