别用robfig/cron(v1/v2),它已归档、不兼容Go 1.21+,易panic;新项目优先选robfig/cron/v3或gocron/v2,二者均需显式Start()并阻塞主goroutine,否则任务不执行。

直接上结论:别用 robfig/cron(v1/v2),它已归档、不兼容 Go 1.21+,线上容易 panic;新项目优先选 github.com/robfig/cron/v3 或 github.com/go-co-op/gocron/v2,二者都需显式 Start() + 主 goroutine 阻塞,否则任务根本不会跑。
为什么 cron.Start() 后任务没执行?
最常见原因是主 goroutine 立即退出,进程终止。哪怕你调了 c.Start(),没阻塞,调度器连第一次 tick 都等不到。
-
c.Start()是非阻塞的,它只启动内部 goroutine,不挂起 main - 写
select {}最简单,但无法优雅退出;生产环境推荐用os/signal监听SIGINT/SIGTERM - 别在
log.Fatal()里包c.Start()—— 错误被吞掉,看起来像“静默失败” - 如果用 Docker,确认镜像装了
tzdata并设了TZ=Asia/Shanghai,否则time.Local退化成 UTC,时间全乱
秒级任务总不准?检查 WithSeconds 和表达式段数是否匹配
robfig/cron/v3 默认只支持 5 字段(分、时、日、月、周),写 "*/10 * * * * *" 会解析失败,报错 expected exactly 5 fields, found 6。
- 必须初始化时传
cron.WithSeconds():c := cron.New(cron.WithSeconds()) - 启用后才支持 6 字段,
"*/10 * * * * *"表示每 10 秒一次;"0 */2 * * * *"表示每两小时整点(第 0 秒)触发 - 别混用:
cron.WithSeconds()+ 5 字段表达式(如"0 0 * * *")会 panic;没启用就写 6 字段也会 panic -
gocron/v2默认支持秒级,用gocron.DurationJob(10 * time.Second)更直观,不用记字段顺序
动态加载配置里的 cron 表达式,怎么避免运行时报错?
c.AddFunc() 内部解析失败时只打 log,不抛 error,任务静默失效 —— 这在从 DB 或 config.yaml 读表达式时特别危险。
立即学习“go语言免费学习笔记(深入)”;
- 提前校验:用
cron.ParseStandard(expr)或cron.ParseQuartz(expr)主动解析,检查返回的error - 空格敏感:
"0 0 * * *"合法,"0 0 * * *"(双空格)会解析失败 - 语义别名(如
@daily)仅v3支持,v2不认;gocron用gocron.DailyJob(1, ...)替代 - 若任务可能 panic(比如 HTTP 调用超时),
gocron的NewTask可包装 recover,robfig/cron的AddFunc会终止整个调度器
真正麻烦的不是写对一行表达式,而是时区、Go 版本、goroutine 生命周期这三者叠在一起出问题——本地调试正常,一上 K8s 就漏跑,日志里还找不到线索。动手前先跑 c.Entries() 看是否成功加载,再确认 c.Location() 输出是不是你预期的时区。


















