️ 1. 基础变体:类时间轮
这其实是一种简化版的时间轮设计,核心思路是"双线程协作"。
- 底层结构:
使用一个哈希集合,Key 是任务的执行时间,Value 是对应的任务列表。
- 运作方式:
调度线程 (ScheduleThread):充当"搬运工",负责把未来几秒内即将要执行的任务,提前捞出来塞进哈希集合里。
*轮询线程 (RingThread):充当"执行者",只负责盯着当前这一秒,把集合里到点的任务捞出来执行。
- 典型应用:
分布式任务调度框架 XXL-JOB。
2. 分布式扩展:分片时间轮
当单机扛不住海量任务时,就需要用到分片。
- 核心逻辑:
本质上是分布式任务调度。通过哈希等运算,把海量的定时任务分散到不同的机器节点上去执行。
- 内部实现:
每个节点内部依然可以采用单层或多层时间轮的结构来处理分到自己头上的任务。
3.带 Round(圈数)的单层时间轮
为了解决"单层轮子太小,任务延迟时间太长"的矛盾,引入了"圈数"的概念。
- 设计结构:
单层时间轮 + Round(圈数)变量。
- 落点与计算:
槽位 = (延迟时间 / 时间粒度)% 槽位总数
圈数 (Round) = (延迟时间 / 时间粒度)/ 槽位总数
- 执行逻辑:
指针转到某个槽位时,不会直接执行任务,而是检查任务的 Round。如果 Round > 0,就减 1(Round--)继续等下一圈;只有当 Round = 0 时,任务才会被真正触发。
- 缺点:
每次指针扫过槽位,都要遍历链表做减法运算,存在一定的 CPU 开销。
- 实战场景:
- 分布式心跳检测(如 Nacos、Eureka):
比如心跳超时设为 90 秒,时间轮一圈 60 格。任务会被标记 Round=1 放入对应槽位。指针转第一圈时只减圈数,转第二圈 Round=0 时才判定节点失联。这避免了轮询所有节点,极大节省资源。
- 订单超时自动取消:
比如下单后 30 分钟未支付自动关单。相比传统的定时任务不断轮询数据库(空跑率极高),时间轮只在到期那一刻触发操作,能减少 90% 以上的无效数据库查询。
️ 4.多层时间轮
这是目前处理海量、跨度大定时任务最高效的方案(类似现实中的时钟:秒针带动分针,分针带动时针)。
- 设计结构:
由多层数组组成(例如:毫秒轮、秒轮、分轮、时轮)。
- 核心机制:
降级(Downgrade)
任务最初会根据延迟时间,落入最高层级能容纳它的那个轮子。
当高层级的指针转到该任务时,任务不会立即执行,而是被"降级"投放到下一层级的对应槽位中。
层层降级,直到落入最低层级(如秒轮),才会被真正执行。
- 核心优势:
极大地节省内存。哪怕要处理从毫秒到小时级的跨度,只需要极少的槽位(如 24+60+60+1000 ≈ 1100 个槽位)即可覆盖,非常适合高并发场景。
- 实战场景:
- 电商订单超时关单:
秒级轮:处理 30 秒内即将超时的紧急订单。
分钟/小时轮:管理常规超时和预售锁定等长延迟任务。
动态削峰:在支付高峰期,可以将集中超时的任务平滑打散到前后几分钟的窗口内执行,避免瞬间流量把数据库打挂。
- Kafka 的延迟消息管理:
Kafka 内部维护了一个精妙的三层时间轮来处理生产/消费链路的定时任务:
第一层(毫秒级):覆盖 0-2 秒的紧急任务(如副本同步超时)。
第二层(秒级):覆盖 2-40 秒的常规任务(如短延迟消息)。
第三层(分钟级):覆盖 40 秒到 13 分钟的长任务(如消息过期清理)。
总结一下:
任务少、逻辑简单用类时间轮(XXL-JOB);
任务多需要横向扩展用分片时间轮;
延迟时间跨度不大,用带 Round 的单层轮(Netty 常用);
延迟时间跨度极大且追求极致性能,用多层时间轮(Kafka 常用)。