周期任务与定时轮询
周期任务按时间规则产生执行实例。定时轮询则反复查询某个状态,例如每隔 10 秒扫描到期订单。一次周期执行可以领取多条业务任务,两者的执行粒度不同。
固定间隔与日历规则
| 规则 | 示例 | 需要明确的语义 |
|---|---|---|
| 固定频率 | 每隔 60 秒开始一轮 | 上一轮未结束时是否重叠 |
| 完成后等待 | 上一轮结束 60 秒后开始 | 耗时会让实际开始时间逐渐后移 |
| 日历规则 | 上海时间每天 02:00 | 时区、日历边界和错过后的处理 |
“每天”与“每隔 24 小时”在涉及夏令时的地区可能产生不同结果。日历计划应保存 IANA 时区,例如 Asia/Shanghai;执行实例同时保存解析后的计划时间,便于追溯当时应该执行哪一轮。
下面使用 Kubernetes v1.27 起稳定可用的时区配置方式;该特性在 v1.24 为 Alpha,v1.25–v1.26 为默认启用的 Beta,参见官方特性门控历史。其他字段含义见 CronJob 文档。镜像是示例占位,需要替换为自己的报表程序。
apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-sales-report
spec:
schedule: "0 2 * * *"
timeZone: "Asia/Shanghai"
concurrencyPolicy: Forbid
startingDeadlineSeconds: 1800
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
restartPolicy: Never
containers:
- name: report
image: example.invalid/sales-report:replace-me这里禁止同一个 CronJob 的活跃 Job 重叠,并允许在计划时间后 30 分钟内补启动。该并发策略不协调其他 CronJob,也不提供业务去重;任务处理逻辑仍要识别相同的报表日期。
调度计划、实例与重试
假设销售报表每天 02:00 执行:
- 计划保存“每天 02:00、上海时区、处理前一天数据”。
- 2026-10-27 02:00 对应一轮执行实例,其数据窗口是前一天。
- 这一实例第一次失败后,第二次尝试仍处理相同数据窗口。
实例可以用 schedule_id + scheduled_at 建唯一约束。报表写入也需要按日期或窗口覆盖、合并或版本化,避免同一轮重试后产生两份可见结果。
补跑历史报表时,业务日期应来自实例参数。直接使用 Worker 启动时的“昨天”,会让延迟多天的任务处理错误日期。
上一轮没有完成
| 策略 | 行为 | 代价 |
|---|---|---|
| 允许重叠 | 新实例按计划启动 | 下游并发增加,可能竞争同一数据 |
| 跳过本轮 | 活跃实例结束前不再启动 | 必须记录漏掉的窗口 |
| 排队执行 | 保存新实例,等待资源空闲 | 任务可能落后很多轮 |
| 合并执行 | 将多个待处理窗口合并 | 需要业务允许合并,保留窗口边界 |
对需要完整覆盖数据窗口的对账任务,可以保留实例再限流执行;对只关心最新状态的缓存刷新,合并旧轮次可能更合适。中止旧进程也不能撤回它已经提交的写入。
错过执行后的补跑
系统停机三小时后恢复,计划可能已经错过多轮。补跑需要同时限定时间窗口、最多生成多少实例,以及是否沿用原来的业务日期。
Kubernetes 官方说明 CronJob 的调度是近似的,特定情况下可能重复创建或漏建 Job,因此任务需要幂等。它对补跑也有自身的检查规则,不能把某种调度器的行为当成所有定时系统的默认承诺。参见 Job creation。
轮询间隔与扫描成本
假设到期时间相对扫描周期均匀分布,扫描每 10 秒执行一轮,且没有积压、暂停和查询耗时,单纯等待下一轮扫描的时间平均约 5 秒。真实开始延迟还包括查询、领取、排队和 Worker 等待,不能把 10 秒间隔当成严格上界。
轮询频率提高前,先检查查询是否只扫描“未完成且已到期”的索引范围。分页扫描应保存推进位置或不断领取状态发生变化的批次,避免用大 OFFSET 重复跳过前面的数据。
兜底扫描还要有修复目标。例如扫描“到期但没有关闭检查任务的订单”,补建缺失任务;仅重复扫描任务表,发现不了从未创建过的任务。
参考资料
- Kubernetes:CronJobTimeZone 特性门控历史
- Kubernetes:CronJob:并发策略、截止时间与调度限制
- PostgreSQL 18:SELECT:LIMIT、OFFSET 与行锁