fiber 与定时任务必须职责隔离:fiber 处理 http 请求,time.ticker/gocron 应在独立 goroutine 中运行;禁止在 app.listen() 同一线程启动调度器,禁用 fiber.context 跨请求传递,多实例需分布式锁防重,stop 信号须通过 channel 或 context 显式传递。

别把 Fiber 和定时任务混在同一个 goroutine 生命周期里跑——Fiber 是 HTTP 请求处理器,time.Ticker 或 gocron 是后台调度器,两者职责必须隔离。
fiber.New() 启动后,定时任务必须独立 goroutine 启动
Fiber 应用启动(app.Listen())会阻塞主线程,如果你把 time.Ticker 写在 main() 里、又没另起 goroutine,它根本不会运行。
- 错误写法:
ticker := time.NewTicker(10 * time.Second); for range ticker.C { doWork() }—— 这行代码永远执行不到,因为app.Listen()卡住主线程了 - 正确做法:在
app.Listen()前或后,用go func() { ... }()单独启一个 goroutine 跑调度器 - 更稳妥的是在
app.Listen()之后启动,避免服务还没 ready 就开始发请求或上报状态
用 time.AfterFunc 或 gocron.Do() 时,别传 fiber.Context
fiber.Context 是一次性的、复用的、不可跨请求持有——你把它塞进定时任务闭包里,轻则 panic(context canceled),重则内存泄漏(引用未释放)。
- 错误示例:
go func(c fiber.Ctx) { time.AfterFunc(5*time.Second, func(){ c.SendString("boom") }) }(ctx)——c在 handler 返回后就失效了 - 正确姿势:只传业务需要的纯数据(如 ID、配置、
context.Context)、或标准context.Context(比如context.Background()) - 如果任务要调用数据库或发 HTTP 请求,自己 new 一个带 timeout 的
context.WithTimeout(context.Background(), 30*time.Second),别碰ctx.Context()
多实例部署时,time.Ticker 会重复执行,gocron 也需加分布式锁
哪怕你用了 gocron,只要它跑在每个微服务实例里,且没外接 Redis 锁或数据库唯一约束,任务就会被每个实例各执行一遍。
-
time.Ticker完全无协调能力,纯本地计时器,多副本 = 多份相同任务 -
gocron默认也是进程内调度,job.Do()只是注册函数,不解决“谁来真正触发”问题 - 实操建议:用
redis.SetNX()+ Lua 脚本争抢执行权,或直接切到asynq这类中心化队列;若坚持用 gocron,至少加一层if !isLeader() { return }判断 - 别信 “加 mutex.Lock() 就能防重”——那是单机有效,跨进程无效
Stop 信号必须可传递,不能靠 defer ticker.Stop()
defer ticker.Stop() 放在 main() 函数里毫无意义:Fiber 启动后 main() 就退出了,但 goroutine 还在跑,ticker 没被 Stop,资源泄漏。
- 正确方式:声明
done := make(chan struct{})全局或传入调度 goroutine,停止时close(done) - 如果用
gocron,调scheduler.Stop(),但它不等待正在运行的任务结束——你得自己加sync.WaitGroup或context.WithCancel控制子任务生命周期 - 最容易被忽略的一点:任务函数内部如果有长耗时操作(比如同步 HTTP 调用),必须设超时,否则
done信号来了也停不住
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











