Golang实现定时任务的基础方案

16次阅读

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

Golang实现定时任务的基础方案

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 秒后发告警)才用它

生产环境必须考虑的三个现实问题

本地测试跑得通,上线后常因这些被卡住:

立即学习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 日志

定时任务最难的不是怎么启动,而是怎么让它在机器重启、部署更新、网络抖动后依然可靠运行。别省那几行日志和锁逻辑。

text=ZqhQzanResources