定时器升级为分级时间轮
前言
之前skynet_fly的定时器是用一个间隔60秒的check循环来实现的,这样做的好处是避免了skynet注册长时间定时器占用lua携程的问题。但随着使用场景的增多,这种实现暴露出了一些不足:1是精度被限制在check间隔内;2是取消操作需要遍历查找;3是大量定时器时check开销增大。所以决定把底层换成经典的分级时间轮(Hierarchical Timing Wheel),对标Linux内核的实现方式,在保证O(1)创建和取消的前提下,大幅提升性能。
旧实现的问题
旧的实现方式:
1 | |
优点:
- 避免了skynet.timeout长时间占用携程
缺点:
- 取消定时器需要标记后等待下次check才真正停止
- 精度最高60秒(虽然实际可以更短的check间隔,但太短又增加开销)
- 大量定时器时遍历check开销 O(n)
- 延长定时器需要额外计算
分级时间轮实现
数据结构
借鉴Linux内核经典的4级时间轮结构:
| 层级 | 槽数 | 覆盖范围 | 精度 |
|---|---|---|---|
| tv1 | 256 | [cur, cur+255] | 1 tick |
| tv2 | 64 | [cur+256, cur+16383] | 256 ticks |
| tv3 | 64 | [cur+16384, cur+1048575] | 16384 ticks |
| tv4 | 64 | [cur+1048576, cur+67108863] | 1048576 ticks |
最大可调度延迟:67108863 ticks ≈ 671088 秒 ≈ 7.77天。
核心操作复杂度
| 操作 | 复杂度 | 说明 |
|---|---|---|
| create | O(1) | 根据延迟时间直接定位槽位 |
| cancel | O(1) | 双向链表直接摘除,无搜索 |
| extend | O(1) | cancel + create |
| dispatch | O(k) | k = 当前tick到期数量 |
关键实现思路
1. 定位槽位
根据到期时间和当前游标的差值,决定插入哪一级的哪个槽:
- 差值 < 256 → tv1
- 差值 < 16384 → tv2
- 差值 < 1048576 → tv3
- 否则 → tv4
2. 级联(Cascade)
当时间推进时,低级轮转完一圈,需要从高级轮取出对应槽的定时器重新分配到低级轮。这就是”cascade”操作,类似钟表的进位。
3. 驱动调度
使用skynet.timeout作为底层tick驱动。通过维护一个pending_expire和pending_version:
- 只注册一个skynet.timeout指向最近到期时间
- 当新定时器比当前pending更早到期时,用version机制让旧timeout自动失效
- 空闲时用心跳tick保持轮转(最长60秒间隔)
4. 双向链表
每个槽是一个双向链表,定时器节点直接挂载。取消时直接从链表摘除,O(1)完成,无需搜索。
性能优化
P0: 消除dispatch的table分配
cascade过程中需要暂存定时器列表。使用栈式复用的pending_readd数组,避免每次dispatch都创建新table。
P0: 回调同步执行
回调使用xpcall直接执行,不fork新携程,减少携程创建开销。
P0: skynet.timeout局部缓存
1 | |
减少全局查找(注意skynet.now不缓存,兼容录像系统)。
P2: 对象池复用
已完成/已取消的定时器table放入对象池,下次new时直接取出复用,减少GC压力。
1 | |
引用计数策略:用户通过release()主动归还,忘记调用则由GC自然回收。
新增API
相比旧版本,新增了以下便捷API:
| API | 说明 |
|---|---|
M:once(expire, callback, ...) |
创建单次定时器 |
M:new_loop(expire, callback, ...) |
创建循环定时器 |
timer_obj:is_cancelled() |
是否已取消 |
timer_obj:is_finished() |
是否已完成 |
timer_obj:is_loop() |
是否循环 |
timer_obj:remain_times() |
剩余次数 |
timer_obj:is_valid() |
是否仍有效 |
timer_obj:release() |
释放对象供池复用 |
M.set_warn_threshold(ticks) |
设置耗时告警阈值 |
M.set_pool_max(max) |
设置对象池大小 |
使用示例
1 | |
时间常量
1 | |
API文档
定时器升级为分级时间轮
https://huahua132.github.io/2026/05/15/skynet_fly_ss/timer_wheel/