知道什么是时间轮算法吗?在Netty和Kafka中如何应用的?( 四 )


每次推进都会更新 currentTime 为当前时间戳 , 当然做了点微调使得 currentTime 是 tickMs 的整数倍 。并且每次推进都会把能降级的任务重新插入降级 。
可以看到这里的 DelayQueue 的元素是每个槽 , 而不是任务 , 因此数量就少很多了 , 这应该是权衡了对于槽操作的延时队列的时间复杂度与空推进的影响 。
总结首先介绍了 Timer、DelayQueue 和 ScheduledThreadPool , 它们都是基于优先队列实现的 , O(logn) 的时间复杂度在任务数多的情况下频繁的入队出队对性能来说有损耗 。因此适合于任务数不多的情况 。
Timer 是单线程的会有阻塞的风险 , 并且对异常没有做处理 , 一个任务出错 Timer 就挂了 。而 ScheduledThreadPool 相比于 Timer 首先可以多线程来执行任务 , 并且线程池对异常做了处理 , 使得任务之间不会有影响 。
并且 Timer 和 ScheduledThreadPool 可以周期性执行任务 。而 DelayQueue 就是个具有优先级的阻塞队列 。
对比而言时间轮更适合任务数很大的延时场景 , 它的任务插入和删除时间复杂度都为O(1) 。对于延迟超过时间轮所能表示的范围有两种处理方式 , 一是通过增加一个字段-轮数 , Netty 就是这样实现的 。二是多层次时间轮 , Kakfa 是这样实现的 。
相比而言 Netty 的实现会有空推进的问题 , 而 Kafka 采用 DelayQueue 以槽为单位 , 利用空间换时间的思想解决了空推进的问题 。
可以看出延迟任务的实现都不是很精确的 , 并且或多或少都会有阻塞的情况 , 即使你异步执行 , 线程不够的情况下还是会阻塞 。
巨人的肩膀


推荐阅读