time.Ticker仅支持固定间隔定时,不支持Cron表达式;robfig/cron/v3是推荐方案,需注意时区、字段模式、并发控制及优雅关闭——用c.Stop()配合context实现。

用 time.Ticker 实现基础定时任务,但别直接上生产
它能跑,但不是 Cron:只支持固定间隔(如每 5 秒),不支持类 Unix 的 * * * * * 表达式。适合心跳、轮询这类简单场景。
常见错误是误以为 time.Ticker 能处理“每天凌晨 2 点”这种需求——它不能,时间偏移、程序重启后状态丢失、无并发控制,全得自己补。
- 启动后立即执行?
time.AfterFunc或手动触发一次更可控 - 任务 panic 会导致 ticker 卡死:必须用
defer func() { recover() }()包住执行体 - 不要在
Tick循环里做阻塞操作(比如没超时的 HTTP 请求),否则下一次调度会被拖慢
选 robfig/cron/v3 还是 github.com/robfig/cron?
v3 是当前维护主线,v2 已归档,别用 v1(有 goroutine 泄漏 bug)。注意导入路径:github.com/robfig/cron/v3,不是 gopkg.in/... —— 后者是旧版重定向,可能拉错版本。
关键差异在时区和运行模型:
立即学习“go语言免费学习笔记(深入)”;
-
cron.New()默认用本地时区;要 UTC 就得传cron.WithLocation(time.UTC) -
cron.New(cron.WithChain(...))可加日志、recover、限流中间件,否则 panic 会 kill 整个 cron 实例 - 默认并发:同一 cron 表达式多次触发会并行执行;加
cron.WithSingletonMode()才串行
示例:
c := cron.New(cron.WithLocation(time.UTC), cron.WithChain(cron.Recover(cron.DefaultLogger)))
“每天凌晨 2 点”写成 0 0 2 * * * 就错了
robfig/cron 默认是 5 字段(秒可选),6 字段是 Quartz 模式,需显式启用:cron.New(cron.WithSeconds())。否则 0 0 2 * * * 会被截断或报错。
正确写法取决于模式:
- 标准 Unix 模式(5 字段):
0 0 2 * *→ 每天 2:00 - 带秒的 Unix 模式(6 字段):
0 0 0 2 * *→ 每天 2:00:00,前提是启用了WithSeconds() - Quartz 模式(7 字段):
0 0 0 2 * ? *,需用cron.New(cron.WithParser(cron.QuartzParser()))
最常踩的坑:本地开发用的是 Quartz 配置,部署时忘记配 parser,结果表达式被静默忽略,任务从不触发。
如何让 Cron 作业支持 graceful shutdown?
cron.Cron 本身没有内置 shutdown 信号,靠 c.Stop() 停止调度,但正在运行的任务不会中断——这是设计使然,不是 bug。
真正要保的,是“正在跑的任务别被粗暴杀掉”:
- 给每个任务加
context.Context,并在 HTTP client、database query、sleep 等地方传入 - 在
c.Start()前监听os.Interrupt,收到信号后调用c.Stop(),再等几秒让活跃任务自然结束 - 别依赖
os.Exit()强退:goroutine 里的 defer 不保证执行,文件写一半、DB 事务没 commit 就凉了
容易被忽略的一点:如果任务里起了子 goroutine 且没传 context,主任务停了,子 goroutine 还在后台跑,变成幽灵 goroutine。










