午夜不是闲时,是所有批处理撞车的时刻

举报
yd_257405204 发表于 2026/11/11 09:06:25 2026/11/11
【摘要】 午夜不是闲时,是所有批处理撞车的时刻 很多人把定时任务默认排在 0 0 * * * ,理由都是“凌晨没人用”。但真实情况是:数据库全量备份、日志切割(logrotate)、数仓 ETL、云厂商账单结算、监控日聚合报表,全

午夜不是闲时,是所有批处理撞车的时刻

很多人把定时任务默认排在 0 0 * * *,理由都是“凌晨没人用”。但真实情况是:数据库全量备份、日志切割(logrotate)、数仓 ETL、云厂商账单结算、监控日聚合报表,全都盯着这个时刻。你的任务并不孤单,只是要和其他任务在最忙的时段抢 CPU 和 IO。

我们线上一个导出任务从 00:00 改到 03:20 后,平均耗时从 47 分钟降到 9 分钟,失败率从 6% 降到 0。改动只是把它和备份窗口错开,代码一行没动。

时区:你的 0 点不一定是机器的 0 点

容器基础镜像默认 TZ=UTC,宿主机是 Asia/Shanghai,cron 写在镜像里按 UTC 解释。结果你以为的“每天零点跑一次”变成“每天早上 8 点跑一次”。反过来更常见:业务按北京时间 0 点切日,任务在 UTC 0 点(北京时间 8 点)执行,读到的是已经切过日的表。

  • 显式设置容器环境变量 TZ=Asia/Shanghai,并在启动日志里打印一次 date 的结果;
  • 在 cron 表达式旁写注释,注明它按哪个时区解释;
  • 业务逻辑里用 ZoneId.of("Asia/Shanghai") 显式取时区,不要直接调用 LocalDate.now()。

跨日边界是幂等性的检验点

午夜任务天然要处理“昨天”和“今天”。如果任务按天补数据,中途失败后重跑,而统计逻辑里用了 now() 而不是参数化日期,重跑就会把两天数据混在一起,出现重复计数。这类 bug 平时不显形,只在跨零点前后几分钟被触发。

实操:把“业务日期”作为唯一入参传进任务,写成 run --date=2024-06-01,任务内部禁止读取当前时间;落库用唯一索引 (biz_date, metric) 兜底,重跑时覆盖而不是追加。这样即使半夜重试三次,结果也不会翻倍。

任务不是失败了,是被杀掉了

多数“半夜失败”其实是进程被杀:K8s 节点凌晨做镜像 GC 或滚动升级,Pod 收到 SIGTERM,默认 30 秒宽限期一到就 SIGKILL;任务已经跑了 3 小时,进度全丢,日志里只剩一行 exit code 137。翻应用日志看不到异常,查事件才看到 Evicted。

  • 把长任务拆成可续跑的批次,每处理 1000 条提交一次游标;
  • 给这类 Pod 配 terminationGracePeriodSeconds: 300,并实现 SIGTERM 优雅退出;
  • 用 startingDeadlineSeconds 限制补跑,避免关机一夜后堆积出几十个并发实例。

让失败在第二天早上被看见

定时任务最大的问题不是失败,是失败后没人知道:它静默少跑一天数据,两周后报表对不上才被发现。三个低成本动作:

  • 任务结束上报一条心跳(Prometheus pushgateway 或一张 job_heartbeat 表),超过预期时间没心跳就告警;
  • 加结果行数校验,count == 0 直接判失败,空跑比报错更危险;
  • 重试要有上限和退避(3 次,间隔 1/5/15 分钟),无限重试会把下游打挂。

做完这三件事,你的任务仍然可能失败,但至少不会再“总在半夜失败,而且没人知道”。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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