Kratos 中定时任务应优先使用内置 cron(秒级)或 hptimer(毫秒级),二者均自动适配 App 生命周期,避免 goroutine 泄漏;注册 cron 需在 app.New() 后、app.Run() 前调用 app.Handle(c),且任务函数不可含阻塞 IO。

直接说结论:Kratos 里做定时任务,别自己 new time.Ticker 或 goroutine sleep,优先用 cron(秒级)或 hptimer(毫秒级),它们已内置生命周期管理,能随 App 启停自动调度/销毁,否则容易漏 stop、goroutine 泄漏、测试难 mock。
kratos/v2 中怎么注册 cron 定时任务?
用的是 github.com/go-kratos/cron/v2,不是社区原版 robfig/cron,它已适配 Kratos 的 app.Start()/app.Stop() 流程。
- 必须在
app := kratos.New(...)之后、app.Run()之前注册,否则调度器不会被纳入生命周期 - 任务函数不能带阻塞 IO(如未设 timeout 的 HTTP 调用),否则会卡住整个 cron 协程池;建议封装成 biz 方法再调用
- 不要在
init()或包级变量里初始化 cron 实例,它依赖 Kratos 的日志、配置等组件,早于 app 初始化会 panic - 示例写法:
import "github.com/go-kratos/cron/v2"
<p>func initCron(app <em>kratos.App, data </em>data.Data) {
c := cron.New()
<em>, </em> = c.AddJob("0 <em>/5 </em> <em> </em> ?", cron.NewFuncJob(func() {
data.SyncCache() // 调用 data 层方法
}))
app.Handle(c) // 关键:交给 app 管理启停
}为什么用 hptimer 而不是 cron?
当你的场景需要 毫秒级精度 或 高频动态增删(比如每秒心跳检测、连接超时清理、限流窗口刷新),cron 就不合适了——它的最小粒度是秒,且每加一个 job 都会起 goroutine 或加锁竞争。
-
hptimer底层只用 1 个 goroutine + 最小堆,万级任务内存/协程开销几乎不变 - 支持三种注册方式:
After(100 * time.Millisecond, fn)、Every(500 * time.Millisecond, fn)、At(time.Now().Add(2*time.Second), fn) - 注意:
hptimer不支持 Cron 表达式,只能用绝对时间或间隔;若业务强依赖表达式(如“每月1号凌晨1点”),必须用cron - 引入路径是
github.com/go-kratos/kratos/v2/transport/hptimer,注册方式同上:app.Handle(hptimer.New())
常见错误:任务没执行、重复执行、启动报 panic
这些基本都出在生命周期和依赖注入顺序上。
- 任务没执行 → 检查是否漏掉
app.Handle(c),或c.Start()被手动调用了(v2 版本里不需要,app.Run() 会自动 start) - 重复执行 → 多个服务实例同时加载了同一个 cron job(分布式场景),需加分布式锁,或改用中心化调度(如 asynq + Redis)
- panic: “logger not found” → cron job 函数里直接用了
log.Helper,但该 logger 未通过app.WithLogger注入;应把 logger 作为参数传入 job 函数,或从data层取已注入的实例 - 时间不准 → 检查系统时区:Docker 容器默认 UTC,而 cron 表达式按本地时区解析;建议统一用 UTC,或显式设置
cron.WithLocation(time.UTC)
真正麻烦的从来不是“怎么写个定时任务”,而是“怎么让它在微服务启停、扩容缩容、日志链路、错误重试、可观测性上不出问题”。cron 和 hptimer 的差异不在语法,而在它们背后绑定的生命周期契约——一旦脱离这个契约,就退化成裸 goroutine,后面全是坑。


















