Q60: 如何设计定时器系统?
核心结论
定时器系统不是“到点回调”这么简单,它本质上是在大量延迟任务之间做时间调度。
真正需要先明确的是:
- 追求的是精度还是吞吐
- 定时器数量级有多大
- 回调执行是否允许阻塞主循环
- 取消和重复调度如何处理
如果这些边界不清楚,定时器很快会变成线上抖动和性能问题的来源。
一、定时器系统真正负责什么
常见用途包括:
- 技能 CD
- Buff 周期触发
- 连接超时
- 副本倒计时
- 定期任务
这意味着定时器系统服务的是很多上层模块,所以它必须足够稳定。
二、先区分几类定时器
1. 一次性定时器
触发一次后结束,例如:
- 超时检测
- 延迟执行
2. 重复定时器
按固定间隔重复执行,例如:
- 心跳检查
- 周期 Buff
3. 逻辑帧定时器
按游戏 tick 推进,而不是按墙钟时间触发。
这在需要与战斗、技能、状态机对齐时很常见。
三、时间精度和性能一定要权衡
不是所有定时器都需要毫秒级精确。
例如:
- 技能 CD 和战斗事件可能需要更细粒度
- 日常清理、离线回收则完全不需要
如果所有定时器都用同一套高精度结构,成本会很高。
四、常见数据结构怎么选
1. 最小堆
适合:
- 中等数量
- 需要较高精度
优点是语义清晰,缺点是插入和删除都不是常数级。
2. 时间轮
适合:
- 定时器数量大
- 精度要求相对可控
很多在线系统最终会选择时间轮或分层时间轮来承载大量定时任务。
没有绝对最好,关键是看数量级和精度需求。
五、回调执行不能直接把调度器拖死
定时器系统最常见的实际问题之一,不是时间不准,而是回调太重。
更稳妥的做法通常是:
- 调度器只负责发现到期任务
- 实际回调交给逻辑线程或任务队列
否则一个慢回调就会拖延后面所有定时器。
六、取消和重复触发要特别小心
定时器经常会遇到:
- 回调前被取消
- 回调执行时再次注册
- 对象已销毁但定时器还在
所以定时器系统必须有明确的生命周期约束和失效检查。
七、工程上更稳妥的设计
常见做法是:
- 按用途拆不同精度层级
- 调度结构和执行结构分离
- 定时器绑定拥有者或上下文
- 取消采用懒删除或失效标记
这样数量、精度和安全性更容易平衡。
八、常见误区
1. 定时器越精确越好
不对。很多任务根本不值得为极高精度付出高昂成本。
2. 定时器到点直接执行回调最简单
简单,但很容易阻塞整个调度链。
3. 一个全局定时器管理器就够了
规模一大后,按用途分层往往更合理。
参考资料
- 时间轮、最小堆和大规模定时调度相关资料
