用 time.Ticker 适合秒级/毫秒级轻量周期任务,但需防 tick 积压和 goroutine 泄漏;cron/v3 适合表达式调度,需处理 panic、并发与时区;禁用 time.AfterFunc 做重复任务。

用 time.Ticker 实现简单周期任务
适合秒级或毫秒级固定间隔的轻量任务,比如每 5 秒检查一次本地缓存状态。它不处理任务执行超时、失败重试或并发冲突,只保证“按表走时”。
-
time.Ticker启动后立即发送第一个 tick,若任务执行时间 > 间隔,后续 tick 会堆积在 channel 中,可能引发 goroutine 泄漏 - 必须显式调用
ticker.Stop(),否则 goroutine 和 timer 资源不会释放 - 不要在
for range ticker.C循环里直接写耗时逻辑,应起 goroutine 或用带超时的 context 控制
ticker := time.NewTicker(5 * time.Second) defer ticker.Stop()for range ticker.C { go func() { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() // 执行实际任务 doWork(ctx) }() }
用 github.com/robfig/cron/v3 做类 crontab 调度
需要按时间表达式(如 "0 0 * * *)触发的任务,比如每天凌晨 2 点清理日志。这是目前最稳定、文档最全的 Go cron 库。
- v3 版本默认使用
Seconds字段(支持秒级),v2 不支持,初始化时注意传cron.WithSeconds() - 任务函数 panic 会导致整个 cron 实例停止调度,必须在任务内部 recover
- 多个 job 共享同一个
cron.Cron实例,但每个 job 的执行是串行的(除非手动启 goroutine)
c := cron.New(cron.WithSeconds())
c.AddFunc("0 0/5 * * * ?", func() {
defer func() {
if r := recover(); r != nil {
log.Printf("job panicked: %v", r)
}
}()
cleanupLogs()
})
c.Start()
defer c.Stop()避免用 time.AfterFunc 做重复定时任务
它只执行一次,常见误用是“递归调用自己”来模拟循环,这会累积 goroutine 和 timer,且无法统一管理生命周期。
- 错误写法:
time.AfterFunc(d, f)在f末尾再调一次AfterFunc - 没有全局 stop 控制点,无法优雅关闭
- 每次调用都新建 timer,GC 压力大,精度也随调度延迟恶化
- 真正需要单次延时(如 30 秒后发告警)才用它
生产环境必须考虑的三个现实问题
本地测试跑得通,上线后常因这些被卡住:
mallcloud商城基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba并采用前后端分离vue的企业级微服务敏捷开发系统架构。并引入组件化的思想实现高内聚低耦合,项目代码简洁注释丰富上手容易,适合学习和企业中使用。真正实现了基于RBAC、jwt和oauth2的无状态统一权限认证的解决方案,面向互联网设计同时适合B端和C端用户,支持CI/CD多环境部署,并提
立即学习“go语言免费学习笔记(深入)”;
- 时区:
cron默认用本地时区,容器中往往为 UTC,用cron.WithLocation(time.UTC)显式指定 - 并发:多个实例部署时,同一任务会被重复执行,需外接分布式锁(如 Redis SETNX + TTL)或选支持 leader election 的库(如
github.com/go-co-op/gocron) - 可观测性:不记录 start/end 时间、执行耗时、panic 日志,出问题时只能猜——至少加
log.Printf("[job:%s] start", name)和 defer 日志
定时任务最难的不是怎么启动,而是怎么让它在机器重启、部署更新、网络抖动后依然可靠运行。别省那几行日志和锁逻辑。









