1、背景

时间轮算法可以用于高效的执行大量的定时任务。

在Netty中的一个典型应用场景是判断某个连接是否idle,如果idle(如客户端由于网络原因导致到服务器的心跳无法送达),则服务器会主动断开连接,释放资源。得益于Netty NIO的优异性能,基于Netty开发的服务器可以维持大量的长连接,单台8核16G的云主机可以同时维持几十万长连接,及时掐掉不活跃的连接就显得尤其重要。

2、算法简介

网上盗个图,时间轮算法可以通过上图来描述。假设时间轮大小为8,1s转一格,每格指向一个链表,保存着待执行的任务。

假设,当前位于2,现在要添加一个3s后指向的任务,则2+3=5,在第5格的链表中添加一个节点指向任务即可,标识round=0。

假设,当前位于2,现在要添加一个10s后指向的任务,则(2+10)% 8 = 4,则在第4格添加一个节点指向任务,并标识round=1,则当时间轮第二次经过第4格时,即会执行任务。

时间轮只会执行round=0的任务,并会把该格子上的其他任务的round减1。

算法的原理非常浅显易懂,但是阅读源码实现还是有益的。

3、源码解析

1、构造方法

参数:

1)threadFactory

用于生成工作线程

2)tickDuration和unit

每格的时间间隔,默认100ms

3)ticksPerWheel

一圈下来有几格,默认512,特别的,如果传入的不是2的N次方,则会调整为大于等于该参数的第一个2的N次方,好处是可以优化hash值的计算

4)leakDetection

如果false,那么只有工作线程不是后台线程时才会追踪资源泄露,这个参数可以忽略

5)maxPendingTimeouts

最大的pending数量,默认-1,表示不限制

注:可以看到构造方法执行结束时,工作线程并没有启动,那么应该是在第一次添加任务的时候启动的,我们继续看添加任务的newTimeout方法

2、newTimeout

首先,通过一个原子变量来计数当前的任务数,如果设置最大pending且超过了,则会直接throw Exception

其次,便是调用start方法来正式启用worker线程,为了防止重复调用,使用了一个原子操作,并且调用完毕之后会CountDownLatch.await阻塞住,直到线程完全起来才返回

可以看到,方法是public的,也即用户可以显示的调用,而无需等待第一次添加任务时再启动

最后,便是包装一个HashedWheelTimeout对象(计算出了deadline),丢给队列,等待工作线程处理,那么接下来的重点就是看worker线程的实现了

3、Worker线程

工作线程启动的第一步是初始化startTime,并调用countDown来通知start方法,初始化结束了

其次便是一个循环,循环内的行为就是每隔一段跳一格的操作了,我们看具体的操作:

1)首先调用waitForNextTick()

首先计算一下当前tick下的deadline,减去startTime,得到sleepTimeMs,随后sleep一下。这里面有几个小细节:

计算sleepTimeMs先加999999,应该是不足1ms的,补足1ms

因为每次执行定时任务消耗的时候是不受控制的,因此算出来的sleepTimeMs可能为负,这个时候就可以直接返回了执行下一个格子里的任务了

如果currentTime==Long.MIN_VALUE,会直接返回一个负数,这个应该是为了处理时间轮执行了很长时间导致的long值溢出,具体了解的可以评论里告诉,不胜感激

下面还有一个,如果是windows平台,先除以10再乘以10,是因为windows平台下最小调度单位是10ms,如果不处理成10ms的倍数,可能导致sleep更不准了

最后,如果线程被打断了,并且是shutdown状态,会直接返回负数,并在随后的while判断中挑出循环

2)随后调用processCanceldTasks()

该方法是为了处理那些被取消的任务,任务存放在一个queue中

3)transferTimeoutsToBuckets()

该方法是从timeouts(就是前面newTimeout是放进去的那个queue)的queue中取出任务,放到格子里(HashedWheelBucket是一个链表),为了防止这个操作销毁太多时间,导致更多的任务时间不准,因此一次最多操作10w个。几个注意点:

计算stopIndes时,含义是取模,因为mask是2的N次方减1,因此%和&可以等价操作,即x % (mask + 1) == x & mask,这个技巧在jdk的集合类中也被使用到

为了防止出现任务延迟太久,因此在计算模之前,还先取max in (calculated, tick),从而让那些本应该在 过去执行的任务,在这期先快速执行掉

4)expireTimeouts(deadline)

这是HashedWheelBucket的一个方法,就是来执行该格子里那些已经过期的任务

这步的操作比较简单,就是一次遍历链表,如果remainingRounds(剩下的圈数)小于等于0,那么就把他移除并执行expire方法(即TimerTask的run方法);如果任务被取消了,则直接移除;否则remainingRounds减一,等待下一圈

5)如果中间时间轮的状态不再是started,那么就会跳出循环,并依次取出各个bucket上的未执行且没有被取消的任务,stop方法会返回这个列表

4、总结

时间轮算法理解起来很简单,实现也似乎不难,但是通过阅读源码,可以看到,其中还是有很多很多的小细节需要注意,这个就不容易了

而且通过阅读源码,可以看到,整个时间轮的调度都是在一个线程里完成的,因此对于那些耗时较大的定时任务,如果直接扔进去处理显然会影响其他任务的正常执行,例子如下:

转载:https://sq.163yun.com/blog/article/177510753845874688

时间轮算法解析(Netty HashedWheelTimer源码解读)相关推荐

  1. java 时间轮算法_时间轮算法解析(Netty HashedWheelTimer源码解读)

    1.背景 时间轮算法可以用于高效的执行大量的定时任务. 在Netty中的一个典型应用场景是判断某个连接是否idle,如果idle(如客户端由于网络原因导致到服务器的心跳无法送达),则服务器会主动断开连 ...

  2. 面试官:知道时间轮算法吗?在Netty和Kafka中如何应用的?

    最近看 Kafka 看到了时间轮算法,记得以前看 Netty 也看到过这玩意,没太过关注.今天就来看看时间轮到底是什么东西. 为什么要用时间轮算法来实现延迟操作? 延时操作 Java 不是提供了 Ti ...

  3. 小顶堆时间复杂度_时间轮算法以及时间轮在Netty和Kafka中的应用的

    大家好,我是yes. 最近看 Kafka 看到了时间轮算法,记得以前看 Netty 也看到过这玩意,没太过关注.今天就来看看时间轮到底是什么东西. 为什么要用时间轮算法来实现延迟操作? 延时操作 Ja ...

  4. diff算法_vue源码解读 diff算法

    导语 最近碰到部分业务场景,代码逻辑需要了解"数组变更后,具体变更了哪一些元素,以及变更的位置..".于是仔细研究并覆写了一遍针对数组变化的diff算法,在这里做下diff算法的逻 ...

  5. Vue 源码解读(12)—— patch

    当学习成为了习惯,知识也就变成了常识. 感谢各位的 关注.点赞.收藏和评论. 新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn 文章已收录到 github 仓库 liyongning/b ...

  6. TimeWheel时间轮算法原理及实现(附源码)

    时间轮算法原理及实现 前言 1.时间轮核心 2.简单定时器 3.任务队列 4.优化任务队列 5.简单时间轮 6.多层时间轮 前言  在各种业务场景中,我们总是会需要一些定时进行一些操作,这些操作可能是 ...

  7. netty springmvc_springmvc源码架构解析之HandlerMapping

    说在前面 前期回顾 sharding-jdbc源码解析 更新完毕 spring源码解析 更新完毕 spring-mvc源码解析 更新完毕 spring-tx源码解析 更新完毕 spring-boot源 ...

  8. 社区发现算法原理与louvain源码解析

    前言 社区发现(community detection),或者社区切分,是一类图聚类算法,它主要作用是将图数据划分为不同的社区,社区内的节点都是连接紧密或者相似的,而社区与社区之间的节点连接则是稀疏的 ...

  9. java 时间轮算法_时间轮算法(TimingWheel)是如何实现的?

    前言 我在2. SOFAJRaft源码分析-JRaft的定时任务调度器是怎么做的?这篇文章里已经讲解过时间轮算法在JRaft中是怎么应用的,但是我感觉我并没有讲解清楚这个东西,导致看了这篇文章依然和没 ...

最新文章

  1. 微信小程序地图标记点,点击标记点显示详细信息源码加效果图
  2. SAP MIGO + 311将库存从IM管理库存地转入WM管理库存地,物料凭证号里不显示WM 选项卡
  3. 编码/解码和进制转化工具hURL
  4. Hadoop2.2.0集群在RHEL6.2下的安装实战
  5. Fibonacci数列第n项的log(n)算法
  6. 2/7 SELECT语句:排序(ORDER BY)
  7. IIS,apche,nginx,301域名重定向设置
  8. 阻尼衰减曲线用python_高阻尼隔震橡胶支座结构及防震效果
  9. mysql libs 5.1.71_用python创建数据库监控平台(1)安装MySQL5.7
  10. python可迭代对象 迭代器生成器_Python可迭代对象、迭代器和生成器
  11. 基于boost asio实现的支持ssl的通用socket框架
  12. Typename和Class在声明模板时的区别
  13. 1650显卡和1050T显卡差距大吗?
  14. 【前端应该知道的那些事儿】运动学基础
  15. Adobe Reader 9.0记住阅读位置
  16. internet信息服务(IIS)管理器 在哪里?
  17. Java EE Servlet 几个path
  18. UITableView+UITableViewStyleGrouped 处理section之间间隙
  19. 小巧票据打印软件免费下载
  20. 卸载重装Ubuntu22.04双系统

热门文章

  1. 测试Live Writer
  2. 第一个vue.js项目
  3. iOS 多线程技术总结
  4. Zabbix 3.2.6 升级到 Zabbix 3.4.3
  5. 创业公司如何做数据分析(四)ELK日志系统
  6. 汽车之家购买价格PC真正的原因阿拉丁
  7. 第三周项目5-数组作数据成员
  8. SHELL编程一二三
  9. linux java top_Linux top和负载的解释(转载)
  10. java加法器_javacc例子:加法器