robfig/cron/v3 是 Go 生态中最成熟、线程安全、支持秒级精度的单机定时调度库,需显式启用 WithSeconds(),配合 Recover 链防止单点 panic,并通过外部锁解决多实例重复调度问题。
github.com/robfig/cron/v3 实现精准定时调度Go 生态里最成熟、线程安全、支持秒级精度的调度库是 robfig/cron/v3,不是标准库的 time.Ticker,也不是轻量但功能受限的 jobber 或 gocron。它能处理时区、跳过阻塞任务、支持 CRON_TZ 和 @every/@hourly 等语义,生产可用。
关键点:
cron.New(cron.WithSeconds()) 必须显式启用秒级支持,否则默认只到分钟级cron.WithChain(cron.Recover(cron.DefaultLogger)) 防止单个 panic 导致整个调度器停摆func() 里直接写耗时逻辑;应启动 goroutine 或投递到 worker pool,否则会阻塞下一个 tickpackage mainimport ( "fmt" "log" "time" "github.com/robfig/cron/v3" )
func main() { c := cron.New( cron.WithSeconds(), cron.WithChain(cron.Recover(cron.DefaultLogger)), )
// 每 5 秒执行一次(注意:格式为 "秒 分 时 日 月 周") _, err := c.AddFunc("*/5 * * * * *", func() { fmt.Printf("task run at %s\n", time.Now().Format("15:04:05")) }) if err != nil { log.Fatal(err) } c.Start() defer c.Stop() select {} // 阻塞主 goroutine}
如何避免重复调度与并发冲突
多个实例部署时,
cron本身不提供分布式锁能力,同一任务会在所有节点上同时触发。这不是 bug,是设计使然 —— 它定位是单机调度器。常见应对方式:
SET key value EX 30 NX 做租约,执行前抢锁,失败则跳过INSERT IGNORE 或 ON CONFLICT DO NOTHING 判断是否首次执行github.com/go-co-op/gocron(v2+)配合 WithDistributedRunner,但它依赖 Redis,且需自行管理锁续期切忌用文件锁或本地内存标志位,跨进程无效。
cron.Cron 支持运行时添加/移除任务,但要注意:cron.EntryID 是 int 类型,不是稳定 ID;重启后重置,不能持久化依赖它。
动态管理建议:

string]*cron.Entry 自行维护任务别名 → Entry 映射,别名可存 DB 或配置中心c.Remove(entryID) 后,原 EntryID 不再有效;再次 AddFunc 会返回新 IDc.AddFunc 或 c.Remove,可能引发 panic(内部 map 并发读写);应通过 channel 通知主 goroutine 处理time.Ticker 替代 cron?time.Ticker 只适合固定间隔(如每 30 秒拉一次指标),无法表达“每月 1 号凌晨 2 点”或“每周五 9:00-18:00 每 15 分钟一次”这类复杂规则。它的误差会累积,且无任务元数据(名称、下次执行时间、运行统计)。
典型误用场景:
time.AfterFunc + 递归调用模拟 cron —— 缺少错误隔离,一次 panic 全挂真正需要自研调度器的场景极少:一般是超低延迟(μs 级)、硬实时、或已有专用基础设施(如 Kubernetes CronJob + 自定义 operator)。普通后台任务,老实用 robfig/cron/v3。