应使用 github.com/robfig/cron/v3 替代已归档的 v1 版本,初始化时传 cron.WithLocation(time.Local) 支持本地时区,AddFunc 内需 defer recover 捕获 panic,秒级任务应改用 time.Ticker 而非 cron 表达式。

Go 里用 cron 库跑定时任务,别直接上 github.com/robfig/cron
这个库已归档(archived),不再维护,Go 1.20+ 下遇到 undefined: time.ParseInLocation 或泛型兼容问题很常见。它底层用 time.Ticker + 字符串解析,对夏令时、跨月边界、秒级精度支持弱,且不支持上下文取消。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 新项目统一用
github.com/robfig/cron/v3(注意带/v3)——它修复了时区、panic 捕获和context.Context支持 - 若需更高可控性(比如任务失败重试、依赖注入、可观测性),直接用
time.Ticker+time.AfterFunc手写更轻量、更透明 - 避免在
cron.AddFunc里传闭包捕获循环变量,容易所有任务执行同一份值 —— 用显式参数传入
怎么让 cron 表达式真正按本地时区跑(不是 UTC)
默认所有 cron 实例都用 time.UTC 解析时间,哪怕你机器 TZ=Asia/Shanghai 也没用。结果就是你写 "0 0 * * *" (每天 0 点),实际在 UTC 0 点(北京时间早 8 点)触发。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 初始化时显式传入时区:
cron.New(cron.WithLocation(time.Local)) - 不要依赖
time.LoadLocation("Asia/Shanghai")后硬编码 —— 容器环境可能没时区数据,time.Local更可靠 - 验证是否生效:打印
c.Entry().Schedule.Next(time.Now())返回的时间,看小时数是否符合预期
cron.AddFunc 里 panic 会导致整个调度器挂掉
这是最常踩的坑:某个定时任务里触发空指针或除零,cron 默认不 recover,整个 c.Run() 流程退出,后续所有任务停摆,且无日志提示。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 所有
AddFunc内部包一层defer+recover,至少打日志:func() { defer func() { if r := recover(); r != nil { log.Printf("cron job panic: %v", r) } }() // 你的业务逻辑 }() - 或者改用
cron.New(cron.WithChain(cron.Recover(cron.DefaultLogger))),但注意它只捕获 panic,不处理返回 error - 别把数据库连接、HTTP 客户端等长生命周期对象在
AddFunc里反复创建 —— 外部初始化好再传入,避免资源泄漏
需要秒级精度?别迷信 cron 表达式
cron/v3 最小粒度是分钟(* * * * * 中第一个 * 是分钟),不支持 */5 * * * * * 这种“每 5 秒”写法。强行用 "* * * * *" + 里面 sleep 5 秒,会漂移、堆积、不可控。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 真要秒级,用
time.Ticker:ticker := time.NewTicker(5 * time.Second) for range ticker.C { // 执行任务 } - 如果还需 cron 表达式能力(比如“每周一 9:30:15 执行”),用
github.com/gorhill/cronexpr解析表达式,再结合time.AfterFunc手动计算下次触发时间 - 注意
Ticker不自动处理任务耗时超时 —— 如果任务执行超过间隔,下一次会立即触发(不等待),必要时加锁或跳过
时区、panic 恢复、精度边界这三块,最容易在线上静默出问题。尤其容器里时区没挂载、日志没捕获 panic、任务卡死没超时控制 —— 看似跑着,其实早就停了。


















