半夜失败不是玄学,是时区在作祟

举报
yd_222002169 发表于 2026/10/26 08:19:16 2026/10/26
【摘要】 半夜失败不是玄学,是时区在作祟 最常见的“半夜失败”场景:白天手动跑得好好的任务,一到凌晨两点就报错或漏跑。第一嫌疑人永远是 cron 的时区。服务器、容器、数据库可能各用一套时区,而 cron 只认系统时区。 先确认三

半夜失败不是玄学,是时区在作祟

最常见的“半夜失败”场景:白天手动跑得好好的任务,一到凌晨两点就报错或漏跑。第一嫌疑人永远是 cron 的时区。服务器、容器、数据库可能各用一套时区,而 cron 只认系统时区。

先确认三处时区是否一致:

  • 宿主机:timedatectl 或 date +"%Z %z",看是否为 CST +0800
  • 容器:docker exec 容器名 date,很多基础镜像默认 UTC,比北京时间慢 8 小时
  • 数据库:SELECT @@global.time_zone, @@session.time_zone;,MySQL 常见的 SYSTEM 会跟随宿主

真实踩坑:任务写在 0 2 * * *,容器是 UTC,实际在北京时间 10:00 执行,恰好和上午的备份窗口撞车,锁表超时。修复方式不是改 cron 表达式猜时区,而是在容器里显式设置 TZ=Asia/Shanghai 并挂载 /etc/localtime,然后重启 cron 守护进程。若用 crond,改完时区必须 systemctl restart crond,否则旧进程仍持有启动时的时区缓存。

夏令时与跨天边界:少跑一次或多跑一次

如果你的业务涉及海外节点,夏令时切换当天会出现“2:30 这个时间不存在”或“2:30 出现两次”。cron 的默认行为是:不存在的时刻跳过,重复的时刻只执行一次——但不同实现(cronie、busybox crond、k8s CronJob)行为并不统一。

工程上的稳妥做法是避开 0:00–3:00 这个危险区间,把任务放到 3:30 之后,或者改用带时区语义的调度器。例如 k8s 从 1.27 起支持:

  • spec.timeZone: "Asia/Shanghai",让 CronJob 的表达式按指定时区解释
  • 老版本只能靠 startingDeadlineSeconds 兜底,避免节点重启后堆积的任务一次性全跑

还有个易忽略点:@daily 在部分实现里等价于 0 0 * * *,跨天时如果前一次运行耗时超过 24 小时,flock 没加就会叠加执行。给所有长任务套一层文件锁是成本最低的保险:flock -n /var/lock/backup.lock /opt/scripts/backup.sh。

资源竞争:半夜正是别人跑批的黄金时段

凌晨不是“没人用”,而是全公司的批处理都挤在这时候。你以为是自己的任务挂了,其实是隔壁的 ETL 把磁盘 IO 或数据库连接池吃满,你的查询等锁超时。

  • 数据库侧:SHOW PROCESSLIST 抓当时的长事务;确认是否被 lock_wait_timeout 或 innodb_lock_wait_timeout 打断
  • 系统侧:用 sar -d 1 60 或 iostat -x 1 回看凌晨的 %util,超过 90% 基本就是 IO 瓶颈
  • 日志侧:给任务统一加开始/结束时间戳和 set -x,失败时能立刻定位卡在哪一步

实操建议:把重任务错峰到 1:15、2:45、4:00 分散开,并在脚本开头加一个轻量的预检——查询当前活跃连接数或磁盘剩余空间,不达标就 exit 75(EX_TEMPFAIL),交由重试机制处理,而不是硬跑失败。

重试与告警:别让失败静默到第二天

半夜失败最致命的不是失败本身,是没人知道。默认 cron 会把输出邮件投递给本地 mail,而多数服务器根本没配 MTA,输出直接进黑洞。

最低配的方案:在 crontab 顶部声明 MAILTO 之外,把每条任务的 stdout/stderr 重定向到带日期的日志,再用退出码驱动告警:

  • 0 3 * * * /opt/run.sh >> /var/log/job/$(date +\%F).log 2>&1 || /opt/alert.sh "job failed"
  • 注意 % 在 crontab 里必须转义成 \%,否则会被当成换行
  • 更稳的是用 systemd timer 替代 cron:OnFailure=alert@%n.service 原生支持失败回调,还有 RandomizedDelaySec 天然错峰

重试要区分错误类型:网络抖动、临时锁冲突值得重试(指数退避 3 次);数据校验失败、权限不足重试只是浪费时间,应当直接告警。最后,给任务加一个“心跳”——每次成功运行写一行到监控系统或健康检查端点,超过预期周期没更新就报警。这样即使 cron 本身没触发,你也能在第二天上班前发现问题,而不是等业务方打电话来问“昨天的报表怎么没出”。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。