├── .idea ├── compiler.xml ├── description.html ├── encodings.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── README.md ├── TimerTask.png ├── TimerTaskList.png ├── TimingWheel.png ├── src ├── Main.java └── com │ └── utils │ ├── TimeWheel.java │ ├── Timer.java │ ├── TimerTask.java │ └── TimerTaskList.java ├── timer.png ├── timing_wheel.iml └── timing_wheel.png /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/description.html: -------------------------------------------------------------------------------- 1 | Simple Java application that includes a class with main() method -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 时间轮 2 | 时间轮是一种环形数据结构,类似于时钟,秒针、分针、时针分别为一层,每层分成多个格子,每个格子中存放任务集合,一个单独的线程推进时间一格一格的移动,并执行格子中的任务。 3 | TimingWheel并非简单的环形时间轮,而是多层级时间轮,每个时间轮由多个时间格组成,每个时间格为一个时间间隔,底层的时间格跨度较小,然后随着延迟任务延迟时间的长短逐层变大; 4 | 如下图,下层的时间轮每个时间格为1ms,整个时间轮为10ms,而上面一层的时间轮中时间格为10ms,整个时间轮为100ms, 5 | 上级时间轮添加的规则为:当前currentTime为上级时间轮的startMs,当前interval为上级时间轮的tickDuration,ticksPerWheel相同; 6 | 简单点说就是上层时间轮跨度为当前的M倍,时间格为当前的N倍; 7 | 时间轮常用于延时任务,在Netty、akka、Quartz、Zookeeper等高性能组件中都存在时间轮定时器的踪影。 8 | - 时间轮数据结构 9 | 10 | ![时间轮数据结构](https://github.com/Xun-Zhou/timing_wheel/blob/master/timing_wheel.png "时间轮数据结构") 11 | 12 | - 时间轮名词解释 13 | 14 | 时间格:环形结构中用于存放延迟任务的区块 15 | 指针(currentTime):指向当前操作的时间格,代表当前时间 16 | 格数(ticksPerWheel):时间轮中时间格的个数 17 | 间隔(tickDuration):每个时间格之间的间隔 18 | 总间隔(interval):当前时间轮总间隔,也就是等于ticksPerWheel*tickDuration 19 | 20 | ## kafka时间轮 21 | 在Kafka中应用了大量的延迟操作,但在Kafka中 并没用使用JDK自带的Timer或是DelayQueue用于延迟操作,而是使用自己开发的DelayedOperationPurgatory组件用于管理延迟操作, 22 | Kafka这类分布式框架有大量延迟操作并且对性能要求及高,而java.util.Timer与java.util.concurrent.DelayQueue的插入和删除复杂度都为对数阶O(log n)并不能满足Kafka性能要求, 23 | 所以Kafka实现了基于时间轮的定时任务组件,该时间轮定时任务实现的插入与删除(开始定时器与暂停定时器)的时间复杂度都为常数阶O(1)。 24 | 25 | - Timer 26 | 27 | Timer是kafka中的定时器类,定义了共客户端调用的方法。SystemTimer是对Timer的具体实现 28 | taskExecutor:过期任务执行线程,为了不影响性能,过期任务单独开辟线程执行 29 | delayQueue:一个Timer只有一个delayQueue,Timer中所有timingWheel共用,用于获取过期任务 30 | timingWheel:最底层时间轮tickMs(间隔)为1ms,wheelSize(格数)为20 31 | 32 | ![Timer](https://github.com/Xun-Zhou/timing_wheel/blob/master/timer.png "Timer") 33 | 34 | 关键代码 35 | ```scala 36 | //添加任务 37 | def add(timerTask: TimerTask): Unit = { 38 | readLock.lock() 39 | try { 40 | addTimerTaskEntry(new TimerTaskEntry(timerTask, timerTask.delayMs + Time.SYSTEM.hiResClockMs)) 41 | } finally { 42 | readLock.unlock() 43 | } 44 | } 45 | //添加任务,失败直接执行 46 | private def addTimerTaskEntry(timerTaskEntry: TimerTaskEntry): Unit = { 47 | if (!timingWheel.add(timerTaskEntry)) { 48 | if (!timerTaskEntry.cancelled) 49 | taskExecutor.submit(timerTaskEntry.timerTask) 50 | } 51 | } 52 | //获取过期任务,推进时间,任务执行或降轮重入 53 | def advanceClock(timeoutMs: Long): Boolean = { 54 | var bucket = delayQueue.poll(timeoutMs, TimeUnit.MILLISECONDS) 55 | if (bucket != null) { 56 | writeLock.lock() 57 | try { 58 | while (bucket != null) { 59 | timingWheel.advanceClock(bucket.getExpiration()) 60 | bucket.flush(reinsert) 61 | bucket = delayQueue.poll() 62 | } 63 | } finally { 64 | writeLock.unlock() 65 | } 66 | true 67 | } else { 68 | false 69 | } 70 | } 71 | ``` 72 | 73 | - TimingWheel 74 | 75 | interval:时间轮时间范围tickMs * wheelSize 76 | buckets:TimerTaskList数组,长度对应wheelSize 77 | currentTime:当前时间startMs - (startMs % tickMs),取整为tickMs的倍数 78 | overflowWheel:上级时间轮 79 | 80 | ![TimingWheel](https://github.com/Xun-Zhou/timing_wheel/blob/master/TimingWheel.png "TimingWheel") 81 | 82 | 关键代码 83 | ```scala 84 | //添加或获取上级时间轮 85 | private[this] def addOverflowWheel(): Unit = { 86 | synchronized { 87 | if (overflowWheel == null) { 88 | overflowWheel = new TimingWheel( 89 | tickMs = interval, 90 | wheelSize = wheelSize, 91 | startMs = currentTime, 92 | taskCounter = taskCounter, 93 | queue 94 | ) 95 | } 96 | } 97 | } 98 | //添加任务 失败返回false直接执行 99 | def add(timerTaskEntry: TimerTaskEntry): Boolean = { 100 | val expiration = timerTaskEntry.expirationMs 101 | if (timerTaskEntry.cancelled) { 102 | //取消 103 | false 104 | } else if (expiration < currentTime + tickMs) { 105 | //已过期 106 | false 107 | } else if (expiration < currentTime + interval) { 108 | //当前时间轮可以容纳该任务 109 | val virtualId = expiration / tickMs 110 | val bucket = buckets((virtualId % wheelSize.toLong).toInt) 111 | bucket.add(timerTaskEntry) 112 | if (bucket.setExpiration(virtualId * tickMs)) { 113 | queue.offer(bucket) 114 | } 115 | true 116 | } else { 117 | //加入上级时间轮 118 | if (overflowWheel == null) addOverflowWheel() 119 | overflowWheel.add(timerTaskEntry) 120 | } 121 | } 122 | //推进时间 123 | def advanceClock(timeMs: Long): Unit = { 124 | if (timeMs >= currentTime + tickMs) { 125 | currentTime = timeMs - (timeMs % tickMs) 126 | //推进上级时间轮时间 127 | if (overflowWheel != null) overflowWheel.advanceClock(currentTime) 128 | } 129 | } 130 | ``` 131 | 132 | - TimerTaskList 133 | 134 | TimerTaskEntry:用于封装TimerTask 135 | 136 | ![TimerTaskList](https://github.com/Xun-Zhou/timing_wheel/blob/master/TimerTaskList.png "TimerTaskList") 137 | 138 | - TimerTask 139 | 140 | trait TimerTask extends Runnable继承java Runnable接口 141 | 142 | ![TimerTask](https://github.com/Xun-Zhou/timing_wheel/blob/master/TimerTask.png "TimerTask") 143 | 144 | -------------------------------------------------------------------------------- /TimerTask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xun-Zhou/timing_wheel/a9a3223e9f8d5d6f3ca5e067251159e98b59fd3c/TimerTask.png -------------------------------------------------------------------------------- /TimerTaskList.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xun-Zhou/timing_wheel/a9a3223e9f8d5d6f3ca5e067251159e98b59fd3c/TimerTaskList.png -------------------------------------------------------------------------------- /TimingWheel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xun-Zhou/timing_wheel/a9a3223e9f8d5d6f3ca5e067251159e98b59fd3c/TimingWheel.png -------------------------------------------------------------------------------- /src/Main.java: -------------------------------------------------------------------------------- 1 | import com.utils.Timer; 2 | import com.utils.TimerTask; 3 | 4 | import java.util.concurrent.CountDownLatch; 5 | 6 | public class Main { 7 | 8 | static int inCount = 0; 9 | 10 | static int runCount = 0; 11 | 12 | public static void main(String[] args) { 13 | CountDownLatch countDownLatch = new CountDownLatch(1000); 14 | Timer timer = new Timer(); 15 | for(int i=1;i<=1000;i++){ 16 | TimerTask timerTask = new TimerTask(i,()->{ 17 | countDownLatch.countDown(); 18 | int index = addRun(); 19 | System.out.println(index+"----------执行"); 20 | }); 21 | timer.addTask(timerTask); 22 | System.out.println(i+"++++++++++加入"); 23 | inCount++; 24 | } 25 | TimerTask timerTask = new TimerTask(5000,()->{ 26 | countDownLatch.countDown(); 27 | int index = addRun(); 28 | System.out.println(index+"----------执行"); 29 | }); 30 | timer.addTask(timerTask); 31 | try { 32 | countDownLatch.await(); 33 | System.out.println("inCount" + inCount); 34 | System.out.println("runCount" + runCount); 35 | } catch (Exception e){ 36 | e.printStackTrace(); 37 | } 38 | } 39 | 40 | public synchronized static int addRun(){ 41 | runCount++; 42 | return runCount; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/com/utils/TimeWheel.java: -------------------------------------------------------------------------------- 1 | package com.utils; 2 | 3 | import java.util.concurrent.DelayQueue; 4 | 5 | /** 6 | * 时间轮 7 | */ 8 | public class TimeWheel { 9 | 10 | /** 11 | * 一个时间槽的范围 12 | */ 13 | private long tickMs; 14 | 15 | /** 16 | * 时间轮大小 17 | */ 18 | private int wheelSize; 19 | 20 | /** 21 | * 时间跨度 22 | */ 23 | private long interval; 24 | 25 | /** 26 | * 时间槽 27 | */ 28 | private TimerTaskList[] timerTaskLists; 29 | 30 | /** 31 | * 当前时间 32 | */ 33 | private long currentTime; 34 | 35 | /** 36 | * 上层时间轮 37 | */ 38 | private volatile TimeWheel overflowWheel; 39 | 40 | /** 41 | * 一个Timer只有一个delayQueue 42 | */ 43 | private DelayQueue delayQueue; 44 | 45 | public TimeWheel(long tickMs, int wheelSize, long currentTime, DelayQueue delayQueue) { 46 | this.currentTime = currentTime; 47 | this.tickMs = tickMs; 48 | this.wheelSize = wheelSize; 49 | this.interval = tickMs * wheelSize; 50 | this.timerTaskLists = new TimerTaskList[wheelSize]; 51 | //currentTime为tickMs的整数倍 这里做取整操作 52 | this.currentTime = currentTime - (currentTime % tickMs); 53 | this.delayQueue = delayQueue; 54 | for (int i = 0; i < wheelSize; i++) { 55 | timerTaskLists[i] = new TimerTaskList(); 56 | } 57 | } 58 | 59 | /** 60 | * 创建或者获取上层时间轮 61 | */ 62 | private TimeWheel getOverflowWheel() { 63 | if (overflowWheel == null) { 64 | synchronized (this) { 65 | if (overflowWheel == null) { 66 | overflowWheel = new TimeWheel(interval, wheelSize, currentTime, delayQueue); 67 | } 68 | } 69 | } 70 | return overflowWheel; 71 | } 72 | 73 | /** 74 | * 添加任务到时间轮 75 | */ 76 | public boolean addTask(TimerTask timerTask) { 77 | long expiration = timerTask.getDelayMs(); 78 | //过期任务直接执行 79 | if (expiration < currentTime + tickMs) { 80 | return false; 81 | } else if (expiration < currentTime + interval) { 82 | //当前时间轮可以容纳该任务 加入时间槽 83 | Long virtualId = expiration / tickMs; 84 | int index = (int) (virtualId % wheelSize); 85 | System.out.println("tickMs:" + tickMs + "------index:" + index + "------expiration:" + expiration); 86 | TimerTaskList timerTaskList = timerTaskLists[index]; 87 | timerTaskList.addTask(timerTask); 88 | if (timerTaskList.setExpiration(virtualId * tickMs)) { 89 | //添加到delayQueue中 90 | delayQueue.offer(timerTaskList); 91 | } 92 | } else { 93 | //放到上一层的时间轮 94 | TimeWheel timeWheel = getOverflowWheel(); 95 | timeWheel.addTask(timerTask); 96 | } 97 | return true; 98 | } 99 | 100 | /** 101 | * 推进时间 102 | */ 103 | public void advanceClock(long timestamp) { 104 | if (timestamp >= currentTime + tickMs) { 105 | currentTime = timestamp - (timestamp % tickMs); 106 | if (overflowWheel != null) { 107 | //推进上层时间轮时间 108 | this.getOverflowWheel().advanceClock(timestamp); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/com/utils/Timer.java: -------------------------------------------------------------------------------- 1 | package com.utils; 2 | 3 | import java.util.concurrent.DelayQueue; 4 | import java.util.concurrent.ExecutorService; 5 | import java.util.concurrent.Executors; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | /** 9 | * 定时器 10 | */ 11 | public class Timer { 12 | 13 | /** 14 | * 底层时间轮 15 | */ 16 | private TimeWheel timeWheel; 17 | 18 | /** 19 | * 一个Timer只有一个delayQueue 20 | */ 21 | private DelayQueue delayQueue = new DelayQueue<>(); 22 | 23 | /** 24 | * 过期任务执行线程 25 | */ 26 | private ExecutorService workerThreadPool; 27 | 28 | /** 29 | * 轮询delayQueue获取过期任务线程 30 | */ 31 | private ExecutorService bossThreadPool; 32 | 33 | /** 34 | * 构造函数 35 | */ 36 | public Timer() { 37 | timeWheel = new TimeWheel(1, 20, System.currentTimeMillis(), delayQueue); 38 | workerThreadPool = Executors.newFixedThreadPool(100); 39 | bossThreadPool = Executors.newFixedThreadPool(1); 40 | //20ms获取一次过期任务 41 | bossThreadPool.submit(() -> { 42 | while (true) { 43 | this.advanceClock(20); 44 | } 45 | }); 46 | } 47 | 48 | /** 49 | * 添加任务 50 | */ 51 | public void addTask(TimerTask timerTask) { 52 | //添加失败任务直接执行 53 | if (!timeWheel.addTask(timerTask)) { 54 | workerThreadPool.submit(timerTask.getTask()); 55 | } 56 | } 57 | 58 | /** 59 | * 获取过期任务 60 | */ 61 | private void advanceClock(long timeout) { 62 | try { 63 | TimerTaskList timerTaskList = delayQueue.poll(timeout, TimeUnit.MILLISECONDS); 64 | if (timerTaskList != null) { 65 | //推进时间 66 | timeWheel.advanceClock(timerTaskList.getExpiration()); 67 | //执行过期任务(包含降级操作) 68 | timerTaskList.flush(this::addTask); 69 | } 70 | } catch (Exception e) { 71 | e.printStackTrace(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/com/utils/TimerTask.java: -------------------------------------------------------------------------------- 1 | package com.utils; 2 | 3 | /** 4 | * 任务 5 | */ 6 | public class TimerTask { 7 | 8 | /** 9 | * 延迟时间 10 | */ 11 | private long delayMs; 12 | 13 | /** 14 | * 任务 15 | */ 16 | private Runnable task; 17 | 18 | /** 19 | * 时间槽 20 | */ 21 | protected TimerTaskList timerTaskList; 22 | 23 | /** 24 | * 下一个节点 25 | */ 26 | protected TimerTask next; 27 | 28 | /** 29 | * 上一个节点 30 | */ 31 | protected TimerTask pre; 32 | 33 | /** 34 | * 描述 35 | */ 36 | public String desc; 37 | 38 | public TimerTask(long delayMs, Runnable task) { 39 | this.delayMs = System.currentTimeMillis() + delayMs; 40 | this.task = task; 41 | this.timerTaskList = null; 42 | this.next = null; 43 | this.pre = null; 44 | } 45 | 46 | public Runnable getTask() { 47 | return task; 48 | } 49 | 50 | public long getDelayMs() { 51 | return delayMs; 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return desc; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/com/utils/TimerTaskList.java: -------------------------------------------------------------------------------- 1 | package com.utils; 2 | 3 | import java.util.concurrent.Delayed; 4 | import java.util.concurrent.TimeUnit; 5 | import java.util.concurrent.atomic.AtomicLong; 6 | import java.util.function.Consumer; 7 | 8 | /** 9 | * 时间槽 10 | */ 11 | public class TimerTaskList implements Delayed { 12 | 13 | /** 14 | * 过期时间 15 | */ 16 | private AtomicLong expiration = new AtomicLong(-1L); 17 | 18 | /** 19 | * 根节点 20 | */ 21 | private TimerTask root = new TimerTask(-1L, null); 22 | 23 | { 24 | root.pre = root; 25 | root.next = root; 26 | } 27 | 28 | /** 29 | * 设置过期时间 30 | */ 31 | public boolean setExpiration(long expire) { 32 | return expiration.getAndSet(expire) != expire; 33 | } 34 | 35 | /** 36 | * 获取过期时间 37 | */ 38 | public long getExpiration() { 39 | return expiration.get(); 40 | } 41 | 42 | /** 43 | * 新增任务 44 | */ 45 | public void addTask(TimerTask timerTask) { 46 | synchronized (this) { 47 | if (timerTask.timerTaskList == null) { 48 | timerTask.timerTaskList = this; 49 | TimerTask tail = root.pre; 50 | timerTask.next = root; 51 | timerTask.pre = tail; 52 | tail.next = timerTask; 53 | root.pre = timerTask; 54 | } 55 | } 56 | } 57 | 58 | /** 59 | * 移除任务 60 | */ 61 | public void removeTask(TimerTask timerTask) { 62 | synchronized (this) { 63 | if (timerTask.timerTaskList.equals(this)) { 64 | timerTask.next.pre = timerTask.pre; 65 | timerTask.pre.next = timerTask.next; 66 | timerTask.timerTaskList = null; 67 | timerTask.next = null; 68 | timerTask.pre = null; 69 | } 70 | } 71 | } 72 | 73 | /** 74 | * 重新分配 75 | */ 76 | public synchronized void flush(Consumer flush) { 77 | TimerTask timerTask = root.next; 78 | while (!timerTask.equals(root)) { 79 | this.removeTask(timerTask); 80 | flush.accept(timerTask); 81 | timerTask = root.next; 82 | } 83 | expiration.set(-1L); 84 | } 85 | 86 | @Override 87 | public long getDelay(TimeUnit unit) { 88 | return Math.max(0, unit.convert(expiration.get() - System.currentTimeMillis(), TimeUnit.MILLISECONDS)); 89 | } 90 | 91 | @Override 92 | public int compareTo(Delayed o) { 93 | if (o instanceof TimerTaskList) { 94 | return Long.compare(expiration.get(), ((TimerTaskList) o).expiration.get()); 95 | } 96 | return 0; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /timer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xun-Zhou/timing_wheel/a9a3223e9f8d5d6f3ca5e067251159e98b59fd3c/timer.png -------------------------------------------------------------------------------- /timing_wheel.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /timing_wheel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xun-Zhou/timing_wheel/a9a3223e9f8d5d6f3ca5e067251159e98b59fd3c/timing_wheel.png --------------------------------------------------------------------------------