正确使用 time.ticker 需启动 goroutine 处理业务、加 context 控制生命周期;推荐 cron/v3 或 gocron,注意时区、秒级精度、日志、http/db 资源复用、并发安全及热重载配置。

用 time.Ticker 做简单轮询,但别在主线程里直接 for range
Go 里最轻量的定时触发就是 time.Ticker,但它不是“任务调度器”,只是按固定间隔发信号。常见错误是写成这样:
ticker := time.NewTicker(5 * time.Second)
for range ticker.C {
doSomething() // 阻塞在这里?整个 ticker 就卡住了
}
问题在于:如果 doSomething() 耗时超过 5 秒,下一次触发会被跳过(ticker.C 是带缓冲的 channel,但缓冲区只有 1),而且没有错误反馈。
- 正确做法是启动 goroutine 处理业务,避免阻塞接收循环
- 务必加
context控制生命周期,否则服务退出时 ticker 不会释放 - 别用
time.Sleep模拟定时——精度差、无法取消、不响应中断
cron 包选哪个?robfig/cron 已归档,推荐 github.com/robfig/cron/v3 或 github.com/go-co-op/gocron
搜 “golang cron” 会看到一堆包,但实际能用的不多。robfig/cron v2 及更早版本已停止维护,v3 是当前事实标准;gocron 更现代,API 清晰,支持 async job 和链式调用。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
-
robfig/cron/v3兼容 Unix cron 表达式(如"0 */2 * * *"),适合迁移老系统 -
gocron默认使用time.Now().UTC()解析时间,若本地时区重要,得显式传入location - 两者都不支持秒级精度的表达式(如
"*/5 * * * * *")——v3 需要启用WithSeconds()选项,gocron默认支持 - 注意日志:cron v3 默认不打日志,出错静默失败;
gocron有.WithLogger()可插拔
定时任务里调 HTTP 或 DB,为什么总超时或 panic?
不是定时逻辑本身的问题,而是没处理好资源生命周期和并发安全。
- HTTP client 别每次新建:
http.DefaultClient可复用,但更推荐自定义&http.Client{Timeout: 10 * time.Second},防止 hang 住整个 ticker - DB 连接池(如
*sql.DB)必须复用,且在服务启动时初始化;切勿在定时函数里sql.Open——连接泄漏、fd 耗尽 - 多个定时任务共用同一结构体字段?加
sync.Mutex或改用原子操作,尤其计数类字段 - panic 不会自动恢复:用
recover()包一层,否则一次 panic 会让整个 cron job 停摆(gocron提供.WithDistributedRunner()可缓解,但非银弹)
如何让定时任务支持热重载配置(比如改 cron 表达式不重启)?
硬编码 "0 0 * * *" 在代码里等于放弃运维灵活性。真要热更新,得靠外部驱动 + 任务注册表。
- 把 cron 表达式存在 etcd / Redis / 文件中,用
fsnotify监听文件变化,或定期轮询读取 - 不要直接
cron.AddFunc后再cron.Remove—— v3 的Remove是异步的,可能删不干净;推荐先Stop()整个 cron 实例,重建新实例 -
gocron支持job.Tags,可按 tag 批量停用/启用,适合灰度控制 - 注意竞态:配置变更和任务执行同时发生?加锁范围要覆盖“读配置 → 停旧任务 → 启新任务”整段逻辑
真正麻烦的从来不是怎么跑一次任务,而是当它跑了一年之后,你忘了当初为啥加了那个 time.Sleep(100 * time.Millisecond)。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










