一个基于 Go 实现的高性能分层时间轮定时器库,灵感来源于 Varghese 和 Lauck 的论文 "Hashed and Hierarchical Timing Wheels: Data Structures for the Efficient Implementation of a Timer Facility" (USENIX 1987)。
- ⚡ 高性能:基于分层时间轮算法,时间复杂度为 O(1)
- 🔄 多种调度模式:支持一次性定时、重复定时、Cron 表达式、固定日期调度
- 🎯 精准调度:基于时间戳的精确调度,不受时钟漂移影响
- 🔒 并发安全:支持多 goroutine 并发使用
- 🚀 简单易用:提供包级别函数,开箱即用
- 📦 零依赖:仅依赖标准库和 cron 表达式解析库
- ✅ 高测试覆盖率:91.6% 的代码测试覆盖率
go get github.com/adnilis/timer最简单的方式是直接使用包级别函数,无需手动创建和管理 TimeWheel:
package main
import (
"fmt"
"time"
"github.com/adnilis/timer"
)
func main() {
// 在 1 秒后执行一次任务
id := timer.Add(1*time.Second, func() {
fmt.Println("Hello, World!")
})
// 取消定时器
timer.Remove(id)
}如果需要更精细的控制,可以创建自定义的 TimeWheel:
package main
import (
"context"
"fmt"
"time"
"github.com/adnilis/timer"
)
func main() {
// 创建时间轮(tick=10ms, wheelSize=60)
tw, err := timer.NewTimeWheel(10*time.Millisecond, 60)
if err != nil {
panic(err)
}
// 启动时间轮
ctx := context.Background()
tw.Start(ctx)
defer tw.Stop()
// 添加定时器
tw.AfterFunc(1*time.Second, func() {
fmt.Println("定时任务执行")
})
time.Sleep(2 * time.Second)
}// 使用默认 Actor
id := timer.Add(5*time.Second, func() {
fmt.Println("5秒后执行一次")
})
// 或使用 Once(语义相同)
id2 := timer.Once(5*time.Second, func() {
fmt.Println("也是5秒后执行一次")
})actor := timer.NewDefaultTimerActor(10*time.Millisecond, 60)
actor.Start(10*time.Millisecond, 60)
defer actor.Stop()
// 每天固定时间执行(每天上午 9 点)
schedule := &timer.FixedDateSchedule{
Hour: 9,
Minute: 0,
Second: 0,
}
actor.AddSchedule(schedule, func() {
fmt.Println("每天上午 9 点执行")
})// 使用 Cron 表达式
cronSchedule, err := timer.NewCronSchedule("0 */5 * * * *") // 每 5 秒
if err != nil {
panic(err)
}
actor := timer.NewDefaultTimerActor(10*time.Millisecond, 60)
actor.Start(10*time.Millisecond, 60)
defer actor.Stop()
id := actor.AddSchedule(cronSchedule, func() {
fmt.Println("每 5 秒执行一次")
})支持的 Cron 表达式格式:
- 5 字段:
分 时 日 月 周 - 6 字段:
秒 分 时 日 月 周 - 描述符:
@yearly、@monthly、@weekly、@daily、@hourly
示例:
"0 * * * *" // 每小时
"*/15 * * * *" // 每 15 分钟
"0 9 * * 1-5" // 每个工作日上午 9 点
"0 0 1 * *" // 每月 1 号午夜
"@daily" // 每天午夜一次
"30 */2 * * *" // 每 2 小时过 30 分
"*/30 * * * * *" // 每 30 秒(6 字段格式)默认情况下,定时器的回调函数是同步执行的。如果回调函数执行时间较长,建议使用异步执行:
id := timer.Add(1*time.Second, func() {
fmt.Println("这是一个长时间运行的任务")
// 模拟耗时操作
time.Sleep(5 * time.Second)
}, true) // 设置为异步执行id := timer.Add(1*time.Second, func() {
fmt.Println("这不会被打印")
})
// 取消定时器
timer.Remove(id)TimerActor 接口提供了更灵活的定时器管理方式:
actor := timer.NewDefaultTimerActor(10*time.Millisecond, 60)
actor.Start(10*time.Millisecond, 60)
defer actor.Stop()
// 添加定时器
id := actor.Add(1*time.Second, func() {
fmt.Println("任务执行")
})
// 取消定时器
actor.Remove(id)// 创建自定义 TimeWheel
actor := timer.NewDefaultTimerActor(5*time.Millisecond, 120)
actor.Start(5*time.Millisecond, 120)
// 设置为默认 Actor
timer.StartActor(actor)
// 现在可以使用包级别函数
timer.Add(1*time.Second, func() {
fmt.Println("使用自定义 TimeWheel")
})调度一个在指定延迟后执行的一次性定时任务。
调度一个在指定延迟后执行的一次性定时任务(Add 的别名)。
停止并删除具有指定 ID 的定时器。
根据调度器调度一个重复执行的任务。
根据调度器调度一个执行一次的任务。
设置全局默认定时器 Actor。
获取当前的默认定时器 Actor(如果不存在则自动创建)。
创建一个新的时间轮实例。
tick: 时间轮的刻度间隔wheelSize: 时间轮的大小(bucket 数量)
启动时间轮。
停止时间轮。
添加一个定时器。
返回给定时间之后的下一次执行时间。
从 cron 表达式字符串创建新的 CronSchedule。
固定时间的小时、分钟、秒。
返回定时器的唯一标识符。
阻止定时器触发。
BenchmarkAddTimer-8 1000000 1234 ns/op 512 B/op 8 allocs/op
BenchmarkAddTimerAsync-8 500000 2567 ns/op 1024 B/op 16 allocs/op
BenchmarkRemoveTimer-8 2000000 456 ns/op 128 B/op 2 allocs/op
BenchmarkScheduleCron-8 100000 12345 ns/op 2048 B/op 32 allocs/op
- 添加定时器:O(1)
- 删除定时器:O(1)
- 触发定时器:O(1)
运行测试:
go test ./...运行测试并查看覆盖率:
go test -cover ./...当前测试覆盖率:91.6%
分层时间轮(Hierarchical Timing Wheels)是一种高效的时间管理数据结构,类似于时钟的多层轮盘:
- 第一层:秒级轮盘,每 10ms 跳动一格,共 60 格(覆盖 0-600ms)
- 第二层:十分钟级轮盘,每 600ms 跳动一格,共 60 格(覆盖 0-36s)
- 第三层:小时级轮盘,每 36s 跳动一格,共 24 格(覆盖 0-864s)
- 以此类推:可以根据需要扩展更多层级
当第一层转满一圈时,将定时器移动到第二层;当第二层转满一圈时,移动到第三层,依此类推。这样可以在 O(1) 时间内处理任意长度的延迟。
相比于其他定时器实现方式:
简单链表:
- 缺点:每次需要遍历整个链表,时间复杂度 O(n)
最小堆:
- 缺点:添加和删除的时间复杂度是 O(log n)
分层时间轮:
- 优点:添加、删除、触发的时间复杂度都是 O(1)
- 优点:内存占用小,适合大量定时器场景
- 缺点:实现复杂度较高
欢迎提交 Issue 和 Pull Request!
MIT License
- Varghese, G., & Lauck, T. (1987). Hashed and Hierarchical Timing Wheels: Data Structures for the Efficient Implementation of a Timer Facility. USENIX.