当前位置: 首页 > news >正文

固定数组时间轮的槽过载优化:桶链表与批次执行

固定数组时间轮的槽过载优化:桶链表与批次执行

时间轮是处理海量定时任务的标准结构,它把时间切分成固定长度的槽,每个槽挂一个定时器链表。tick 推进时扫描当前槽链表,触发所有到期任务。理想情况下,每次 tick 的复杂度是 O(K),K 为该槽上的到期定时器数,通常是常数。但当瞬时大量任务堆积在同一个槽时——比如 1ms 精度的轮子,某个时刻同时到了上万个超时——遍历链表会拖慢整个 tick,后续槽的处理被推迟,定时精度整体恶化。

这篇笔记记录一种简单直接的工程优化:让一个槽可以挂多个子桶(桶链表),并限制每次 tick 触发的任务数量,用可接受的延迟抖动换取处理时间的上界。


1. 基础结构:数组 + 双向链表

先看最简模型。一个时间轮就是一个定长数组slots[N],每个元素是双向链表的头指针。定时器节点内嵌prev/next指针,插入时计算到期槽位(cur + delay_ticks) % N并加入链表头,tick 时遍历该槽链表、触发回调并摘除。数组大小固定,没有动态分配。

当槽内定时器很少时,这完全够用。


2. 槽过载:一个桶装不下

当某个槽的链表过长,单次 tick 遍历成本可能超过一个槽的时长(比如 1ms),形成雪崩。直观解法是横向扩展——一个槽不再只是一个链表,而是一串“小桶”,每个小桶内部又是一个定时器链表。

原结构: slot[i] -> Node -> Node -> ... (单链表) 新结构: slot[i] -> Bucket1 -> Bucket2 -> ... | | v v Node链表 Node链表

桶单元(Bucket)是一个固定或动态分配的小结构:

  • 内部包含一个定时器双向链表的头指针(和尾指针,便于尾部插入)。
  • 一个next指针,指向同槽的下一个桶单元。
  • 可选:当前桶内的定时器数量,用于快速判断是否已满。

这样就把一个长链表切分成了多个短链表,每个桶单元内部长度可控(比如限制最多 128 个节点)。当某个桶满了,就从预分配的对象池里取一个空桶,挂到桶链尾部。


3. 插入定时器:选定桶并挂载

插入流程不变:先根据延迟计算出目标槽索引idx。然后找到该槽的桶链,一般是追加到最后一个桶单元(这样可以保证先入先出)。如果最后一个桶已满,就新建一个桶单元接在后面,再插入。

伪逻辑:

bucket = slots[idx].tail_bucket; if bucket is full: bucket = alloc_bucket(); slots[idx].tail_bucket.next = bucket; slots[idx].tail_bucket = bucket; insert timer node into bucket.timer_list;

这里完全保持了 O(1) 插入。


4. 批次执行:每次 tick 只触发固定数量

为了避免一次 tick 触发整个槽的几百上千个任务,我们引入一个批次上限BATCH_SIZE(例如 128)。每次 tick 处理当前槽时,只触发最多BATCH_SIZE个任务,然后停止。未处理完的任务继续留在桶内,等待下次 tick 再处理。

这会使部分任务的真实触发时间延后几个 tick,但只要批次上限设置合理(比如最大延迟 = BATCH_SIZE × tick_interval),这个额外延迟是可量化的、受控的。对于大多数超时场景(如 30s 连接超时,1ms tick),延时几个毫秒完全可以接受。

处理流程(每个 tick):

  1. 取当前槽索引cur = (cur + 1) % N
  2. 设置计数器processed = 0
  3. 从该槽的第一个桶单元开始,遍历其内部定时器链表。
  4. 对于每个定时器,如果已到期且未取消,触发回调,从链表删除,processed++;如果processed == BATCH_SIZE,记录当前桶和节点位置(断点),结束本轮。
  5. 如果当前桶处理完毕,移到下一个桶继续,直到批次用完或所有桶清空。
  6. 下次 tick 到同一个槽时,从断点继续处理。

这样,每次 tick 的工作量严格受BATCH_SIZE控制,保证了时间轮的推进节奏不会被突然的流量高峰打乱。


5. 线程安全要点
  • 生产者-消费者模型:业务线程插入定时器(生产者),专用 tick 线程负责推进和触发(消费者)。
  • 锁粒度:可以为每个槽设置一把锁,或者为每个桶单元设置锁。桶锁粒度更细。
  • 惰性删除:取消定时器时仅打标记,由 tick 线程遍历时真正移除,避免并发删除的复杂性。
  • 对象池:桶单元和定时器节点都从预分配池中获取,消除动态内存分配导致的延迟抖动。

6. Mermaid 结构图

数据结构示意:

时间轮数组

slot 0

Bucket 1

slot 1

Bucket 1

Bucket 2

slot 2

...

TimerNode

TimerNode

TimerNode

TimerNode

TimerNode

TimerNode

TimerNode

批次 tick 流程:

开始 tick

推进 current_slot 指针

取该槽第一个桶单元

本轮已触发数 < BATCH_SIZE?

桶内还有未处理节点?

触发一个到期定时器

processed++

有下一个桶?

移动到下一个桶

该槽处理完毕

保存断点,停止

结束 tick


7. 总结

这套方案本质上是用空间(桶链表)换时间确定性,用可接受的延迟抖动(批次导致的滞后)换处理时间上界。它保留了固定数组时间轮 O(1) 插入和 O(1) 删除的优点,同时解决了单槽过载带来的长尾延迟问题,非常适合海量定时器、高并发、对延迟抖动容忍度较高的场景(如网络框架超时管理、游戏定时事件)。

http://www.jsqmd.com/news/1013411/

相关文章:

  • OCLP-Mod:如何让2008年后的旧款Mac继续运行最新macOS系统?
  • GR3-Fourier V10.3~V10.9版本的底层驱动算法源码和工业硬件参数标定数据。算法部分涵盖Park变换、斜坡限幅、定时器配置等10个核心功能模块(1-25号)。硬件参数部分详细列出了26
  • MPC8260并行I/O端口配置:引脚复用、中断与UTOPIA/TDM实战
  • GR3六轴工业协作机械臂底层技术档案揭示了35项关键系统设计,涵盖安全保护、运动控制、通讯优化等核心模块。其多重故障保护机制实现毫秒级响应,包括电流异常连锁保护、通讯中断应急处理及分级散热策略。伺服系
  • 终极MTK设备底层调试与刷机完全指南
  • 江西省博物馆周边宝藏饭店!两口子家常菜! - 速递信息
  • 整数溢出陷阱:用除法安全比较乘积
  • 重塑链上未来的隐形基石:长期主义下的生态演进
  • Google 爬虫工作原理,及用Python实现完整的Google爬虫
  • NSK LPFC 1616-3 高刚性零背隙滚珠丝杠技术解析
  • 2026年除尘器滤芯厂家靠谱推荐@拿货质保认准滤芯芳姐? - 速递信息
  • AI 辅助的云原生容量规划:从负载预测到资源推荐的自适应策略
  • 文档下载神器kill-doc:如何三分钟搞定全网30+平台免费文档下载?
  • Wayback Machine浏览器扩展:让消失的网页永远触手可及的数字时光机
  • 你的会议麦克风真的‘智能’吗?拆解ANS噪声抑制在腾讯会议、Zoom里的实际表现
  • 5分钟掌握Arduino红外遥控:从零开始的完整教程
  • OpenPLC Editor终极指南:如何免费创建工业自动化程序
  • 2026天津黄金回收诚信TOP7门店榜单:七家透明商户告别变现套路,三十年口碑硬核护航 - 薛定谔的梨花猫
  • 深入解析wxapkg-convertor:5步掌握微信小程序反编译核心技术
  • 2026沈阳门窗公司对比测评:优选沈阳优顿门窗 - 速递信息
  • 国内餐饮设计公司推荐:从空间美学到经营全案的机构盘点 - 品牌速递
  • WaveTools抽卡记录终极指南:如何精准管理你的抽卡数据与保底计算
  • DolphinScheduler 3.x 集群部署避坑指南:从零到生产环境的完整配置流程
  • 安能物流40公斤收费标准?安能物流40公斤寄件多少钱?2026最新收费详解 - 快递物流资讯
  • 2026宁波AI搜索优化服务商深度评测:谁是宁波企业的最优选? - 品牌报告
  • 终极iOS越狱指南:2026年如何安全解锁iPhone全部潜能
  • 基于MATLAB的静止无功补偿系统设计3(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_文章底部可以扫码
  • 2026杭州黄金回收诚信:这7家透明商户让变现真正省心,三十年口碑护航 - 薛定谔的梨花猫
  • 终极LRC歌词批量下载指南:10分钟让离线音乐库焕发新生
  • dex2jar终极指南:Android逆向工程与DEX转换的完整解决方案