定时任务工具 (cronutil)
定时任务工具 (cronutil)
空轮指数退避、单实例防重叠、去抖聚合队列
cronutil
定时任务与后台作业的三个性能防护组件。高频 tick 任务、批处理循环、批量写外部系统是单核 CPU 被打满的高发点,三个组件分别对应三条防护规则。
IdleBackoff — 空轮指数退避
"每秒 tick → 扫描待办"型任务在待办清空后仍每 tick 全量扫描会空转打满 CPU。退避判断放在查库之前,仍在退避窗口内的 tick 直接返回。
var backoff = cronutil.NewIdleBackoff(2*time.Second, time.Minute) // 2s 起步, 封顶 60s
func tick() {
if !backoff.ShouldRun() {
return // 退避窗口内, 不查库也不写状态
}
items := loadPending()
if len(items) == 0 {
backoff.Miss() // 空轮: 退避间隔翻倍
return
}
backoff.Hit() // 有活干: 清零退避, 恢复高频
process(items)
}
// 管理员手动触发时绕过退避
backoff.Reset()TickGuard — 单实例防重叠
单轮 tick 耗时可能超过触发间隔。上一轮未返回时下一轮直接跳过(不排队),避免并发重叠扫描同一批待办。零值即可用,fn panic 时闸门也会正确释放。
var guard cronutil.TickGuard
func tick() {
if !guard.TryRun(processRound) {
log.Println("上一轮未结束, 本轮跳过")
}
}Debounced — 去抖聚合队列
批量写外部索引/搜索引擎/webhook 时,攒到阈值条数或时间窗先到者刷一次,把"每批一提交"合并为"跨批攒一提交"。刷新回调串行执行,不会并发。
// 2000 条或 30s 先到者触发
queue := cronutil.NewDebounced(2000, 30*time.Second, func(docs []Doc) {
if err := searchEngine.AddDocuments(docs); err != nil {
queue.Add(docs...) // 回调内回灌重试是安全的, 不会死锁
}
})
// 采集/回灌/元数据管道全部走队列
queue.Add(doc)
// 优雅退出时刷新剩余
queue.Close()行为保证:
- 刷新回调串行执行且批次间保持 FIFO 顺序(先入队的批先交付)
- 回调内可安全调用本实例的
Add/Flush/Close(失败回灌重试场景),不会死锁 - 回调 panic 会被捕获并丢弃该批(时间窗刷新跑在定时器协程,未捕获 panic 会击穿进程);业务错误与重试在回调内自行处理
重启兜底:内存队列重启会丢,需配合定期全量/增量同步保证最终一致。即时直发仅限管理员手动的低频单条操作。