【Rust】时间轮的数据结构于设计模式

概念来源

  • 源自"时间轮/定时轮"(Timing Wheel) 的数据结构,最经典的是 Hashed/Hierarchical Timing Wheels(1987 年提出),此后被 Linux 内核、Netty、Kafka 等广泛采用,用于高性能定时器管理。

工作原理

  • 将时间离散化为固定步长的"槽"(slot),以环形数组表示当前层的时间跨度。每次 tick,指针前进一个槽,只处理当前槽中的定时器。
  • 对超出当前层跨度的定时器,放到更高层(更大步长)的槽;随着指针循环,定时器逐级"下沉"到更精细的一层,直到到期执行。
  • 你仓库里的实现就是多层时间轮:L1~L5 层分别对应 100ms/1s/1min/1h/1d 步长,见 crates/focus-rs/src/slot/timer.rs:166 和 advance_time_wheel 逻辑 crates/focus-rs/src/slot/timer.rs:520。每次只清"当前槽位"的定时器,见 take_current_slot_timers crates/focus-rs/src/slot/timer.rs:199。短时(<1s)用一个小堆/有序结构处理,见 process_short_timers crates/focus-rs/src/slot/timer.rs:466。

为什么用"槽"

  • 插入/删除近似 O(1):定时器加入对应槽位,tick 时只处理当前槽,避免对全量定时器做 O(n) 扫描或 O(log n) 堆操作。
  • 批量处理友好:同一时间段到期的定时器集中在同一槽,tick 触发时成批拉取执行,降低锁竞争和调度开销。
  • 可扩展性强:层级化后,支持从毫秒到天的宽时间范围,且每个 tick 的工作量与当前槽内的计数成正比,而不是与定时器总数成正比。
  • 抖动可控:粒度由槽步长决定,可在"精度 vs 性能/内存"之间调参;你们将 <1s 交给短时队列,>=1s 用时间轮,兼顾精度与效率。
  • 简化并发:槽是天然的分桶,减少全局结构上的热锁;你们代码中还将短时/长时映射分离,进一步降低争用。

相对其他方案的优势/权衡

  • 相比最小堆/优先队列:插入 O(1) vs O(log n),每 tick 无需堆顶比较/调整;但时间轮存在"步长"精度限制,需要权衡 tick 和内存占用。
  • 相比纯有序表:不需要每 tick 全表扫描;但取消/重置时要注意"从槽中即时移除"(我们已在 cancel/reset 中补上移除,避免槽内残留导致同 ID 冲突)。

与你们实现的映射

  • 槽结构与操作:
    • 层与槽:crates/focus-rs/src/slot/timer.rs:166(TimeWheelLevel)
    • 取当前槽定时器:crates/focus-rs/src/slot/timer.rs:199
    • 推进并级联:crates/focus-rs/src/slot/timer.rs:520
  • 短时定时器(<1s):
    • 处理:crates/focus-rs/src/slot/timer.rs:466
  • 我们新增的即时清理(避免槽内残留):
    • 从槽位移除:crates/focus-rs/src/slot/timer.rs:221
    • 取消清槽:crates/focus-rs/src/slot/timer.rs:867
    • 重置清槽:crates/focus-rs/src/slot/timer.rs:932

  • 层级时间轮示意(含"槽"/slot)

L4: 天(1天/槽, 30槽)

指针→ \] \[ 0 \]\[ 1 \]\[ 2 \] ... \[ 29

↑ T4=3天 放在 L4 +3 槽

L3: 小时(1小时/槽, 24槽)

指针→ \] \[ 0 \]\[ 1 \]\[ 2 \] ... \[ 23

↑ T3=5小时 放在 L3 +5 槽

L2: 分钟(1分钟/槽, 60槽)

指针→ \] \[ 0 \]\[ 1 \]\[ 2 \] ... \[ 59

↑ T2=3分钟 放在 L2 +3 槽

L1: 秒(1秒/槽, 60槽)

指针→ \] \[ 0 \]\[ 1 \]\[ 2 \] ... \[ 59

↑ T1=7秒 放在 L1 +7 槽

L0: 毫秒(100ms/槽, 10槽)

指针→ \] \[ 0 \]\[ 1 \] ... \[ 9

(<1s 的短时定时器不入轮,走短时队列)

  • 运行流程(简化)

    • 新定时器加入时,根据剩余时间选择层级和槽位:
      • <1s → 短时队列(按到期时间排序,独立处理)
      • ≤60s → L1(秒级槽)
      • ≤60min → L2(分钟级槽)
      • ≤24h → L3(小时级槽)
      • ≤30d → L4(天级槽)
    • 每个 tick:
      • 处理短时队列中到期项
      • 低层(L0→L4)按顺序推进当前槽位指针,仅取"当前槽"的定时器触发
      • 若某层转了一圈(指针回到0),就把上层(更大粒度)的定时器根据精确剩余时间"下沉"到本层合适的槽(级联/下沉),等待后续更精细的触发
  • 示例(与你日志一致)

    • 6.94s → L1 +7 槽;70s → L2 +2 槽(先挂分钟级,等 L1 多圈后再下沉)
    • 339ms → 短时队列(不入轮),到时直接触发
    • 触发闭环:触发 → 激活下一段 → 立刻按剩余时长重新入轮(或入短时队列)
  • 直观好处

    • 每次 tick 只处理"当前槽",近似 O(1),不用全量扫描或堆调整
    • 到期集中批处理,锁竞争小,可扩展到大量定时器
    • 多层粒度兼顾"精度与性能":短时精确,长时高效
相关推荐
IT笔记15 分钟前
【Rust】Rust数组和Vec安全读写笔记
笔记·安全·rust
Source.Liu1 小时前
【Chrono库】时间区域(TimeZone)Rust实现详解(src/offset/local/tz_info/timezone.rs)
rust·time
CNRio1 小时前
GitCode CLI:从Python到Rust的重构之旅
python·rust·gitcode
xcLeigh2 小时前
【新】Rust入门:基础语法应用
开发语言·算法·rust
星释2 小时前
Rust 练习册 103:维吉尼亚密码与安全通信
网络·安全·rust
美味小鱼2 小时前
DupFinder:一个用 Rust 编写的高性能重复文件查找工具
开发语言·后端·rust
Source.Liu2 小时前
【Chrono库】 时区转换规则(TransitionRule)实现详解(src/offset/local/tz_info/rule.rs)
rust·time
星释11 小时前
Rust 练习册 100:音乐音阶生成器
开发语言·后端·rust
木易 士心16 小时前
Go、Rust、Kotlin、Python 与 Java 从性能到生态,全面解读五大主流编程语言
java·golang·rust
badmonster017 小时前
AI ETL需要不同的原语:从构建CocoIndex中学到的Rust经验🦀
rust·aigc