xxl-job源码阅读——(六)调度线程与时间轮算法

article/2025/7/28 0:15:51

文章目录

  • 一. 时间对齐
  • 二. scheduleThread 调度线程
  • 三. ringThread 时间轮(算法)线程
    • 原理
    • 源码实现


本章介绍init()最后一个步骤,初始化调度线程。
另外 第六步的JobLogReportHelper.getInstance().start()只是做了一个日志整理收集,最终在页面图表展示的工作,这里不再深入。

JobScheduleHelper.getInstance().start()开启了两个线程,基础调度scheduleThread,与时间轮调度ringThread

一. 时间对齐

scheduleThread作为时间调度线程,自身的时间是如何对齐到整秒上的呢?
下图可见,在线程启动的同时,sleep了5000-System.currentTimeMillis()%1000 个毫秒。
举例: 现在是17:37:05的100ms,那么上述公式 = 4900ms,从现在开始睡眠4900ms,唤醒的时刻,便是整秒的17:37:10。

那么为什么是5s呢?再看预读时间变量,一致。预读时间变量的作用是每次读取现在开始的未来5s内的任务,用于处理执行。
shceduled线程在while循环的最后还有一次时间对齐:如果预读处理了一些数据,那么就等待到下一个整s,如果没有预读到数据说明当前无任务,直接等待下一个5s。

时间轮线程也有同样的时间对齐,只不过不是5s,而是1s,不再展开。

二. scheduleThread 调度线程

跳过时间对齐,往后看


  1. 计算预读数据,这里的数据是作者根据qps平均计算得到的,正常case下5s内能够处理的数量。这里的时间计算只涉及调度过程,实际trigger业务已经被快慢线程池接手,所以这里的数量预估理论上是没问题的。
  2. 悲观锁,这一步感觉目前没有意义,调度器本身并没有支持多节点部署。
  3. 根据预读数量和预读时间,取出即将要处理的任务。
  4. 如果当前时间已经超过了任务原定计划时间+5s的范围,则跳过,本次不再执行。
  5. 如果当前时间已经超过原定计划时间但是未超过5s,还能抢救一下,执行任务。 并且如果下一次执行时间再未来5s内,那么直接将任务塞给时间轮线程,让时间轮线程负责下一次执行
  6. 还未到执行时间,直接扔给时间轮线程。
  7. 更新任务信息,(下一次执行时间,任务状态等)。

由上可见,未过期的任务,在5s的时间范围内,精确的调度都被交给了时间轮线程,下面我们就继续深入,了解一下时间轮算法的实现。

三. ringThread 时间轮(算法)线程

原理

思考一下,我们实现一个遵循cron表达式的调度功能会怎么做?

  • 方案1,启动一个线程,计算将要执行时间到当前时间的秒数,直接sleep这个秒数。当执行完一次任务后,再计算下次执行时间到当前时间的秒数,继续sleep。

这个方法想想也不是不行,但是缺点是,当我们需要多个cron任务时,需要开启多个线程,造成资源的浪费。

  • 方案2,只用一个守护线程,任务死循环扫任务数据,拿执行时间距离当前最近的任务,如果该任务时间等同于当前时间(或者在当前之间很小的一个范围内),则执行,否则不执行,等待下一个循环。

此方案似乎解决了线程数量爆炸的问题,但是又会引入一个新的问题,如果某一个任务执行时间太长,显然会阻塞其他任务,导致其他任务不能及时执行。

  • 方案3,在方案2的基础上,责任拆分,一个线程为调度线程,另外有一个线程池为执行线程池,这样便可以一定程度避免长任务阻塞的问题。

但是,毫无限制的死循环查询数据,无论这个任务数据存在数据库还是其他地方,似乎都不是一个优雅的方案。那么有没有一种方式,能如同时钟一般,指针到了才执行对应时间的任务。

  • 时间轮算法
    顾名思义,时间轮其实很简单,就是用实际的时钟刻度槽位来存储任务,如下图,我们以小时为单位,9:00执行A任务,10:00执行B,C任务。


    这里的刻度当然也可以更细致,比如把一天切分成246060个秒的刻度,秒的刻度上挂任务。
    我们只需要在方案3的基础上改造:
    • 声明一个变量Map<时间刻度,所属任务集合>。
    • 任务增加时,只需要增加到对应的时间轮上。
    • 仍然有一个线程在死循环,按照秒的刻度1秒执行一次(如何对齐时间请看第一部分),到达这一秒时从Map中取出对应任务,使用线程池进行执行。

时间轮算法也不是完美的,如果某一个刻度上的任务太多,即便任务的执行使用线程池处理,仍然可能会导致执行到下一秒还没完成。毕竟我们对任务的调度,总要对任务的状态等细节进行处理,尤其是这些状态的更新依赖数据库等外部数据源时。

源码实现

xxl-job 的时间轮算法实现与上述有所区别,通过之前的描述我们已经知道scheduleThread已经做了调度的一部分工作,包括取出任务,对过期/到期任务进行执行。
而对将来5秒内将要执行的任务,scheduleThread则是通过下图0pushTimRing方法扔给了时间轮Map:

private volatile static Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>();

ringData是一个秒级的时间轮,时间轮的范围是0~59.


  • 第一步,线程启动时,对齐这一秒。
  • 第二步,通过当前秒获取ringData中的任务,同时为了防止之前有延时产生,也检查一下前一秒的刻度中是否还存在未处理的任务。
  • 第三步,触发任务,扔到快慢线程池去处理。
  • 第四步,清理临时变量。
  • 第五步,对齐这一秒。

总结: xxl-job实现的是一个5s一次的定时任务调度,同时对未来5s将被执行的任务,使用一个范围为一分钟,刻度为秒的时间轮算法来执行。


欢迎关注微信公众号 【JAVA技术分享官】,公众号首发,持续输出原创高质量JAVA开发者知识点


http://chatgpt.dhexx.cn/article/0DT8xGpG.shtml

相关文章

时间轮(七)

时间轮 Kafka 中存在大量的延时操作&#xff0c;比如延时生产、延时拉取和延时删除等。Kafka 并没有使用 JDK 自带的 Timer 或 DelayQueue 来实现延时的功能&#xff0c;而是基于时间轮的概念自定义实现了一个用于延时功能的定时器&#xff08;SystemTimer&#xff09;。JDK 中…

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

前言 我在2. SOFAJRaft源码分析—JRaft的定时任务调度器是怎么做的&#xff1f;这篇文章里已经讲解过时间轮算法在JRaft中是怎么应用的&#xff0c;但是我感觉我并没有讲解清楚这个东西&#xff0c;导致看了这篇文章依然和没看是一样的&#xff0c;所以我打算重新说透时间轮算法…

Redis之时间轮机制(五)

&#x1f680; 优质资源分享 &#x1f680; 学习路线指引&#xff08;点击解锁&#xff09;知识定位人群定位&#x1f9e1; Python实战微信订餐小程序 &#x1f9e1;进阶级本课程是python flask微信小程序的完美结合&#xff0c;从项目搭建到腾讯云部署上线&#xff0c;打造一…

C++定时器和时间轮

文章目录 定时器最小堆实现定时器 时间轮单层级时间轮多层级时间轮 定时器 有些时候我们需要延迟执行一些功能&#xff0c;比如每10s进行一次数据采集。或者告知用户技能冷却有多少时间&#xff0c;如果我们将执行这些功能的任务交给主线程&#xff0c;就会造成主线程的阻塞。…

简单描述时间轮

时间轮 作用 也是用来作定时器触发任务&#xff0c;只是他更高效&#xff0c;时间复杂度为O(1)。 运行原理 为了方便理解我们参考钟表&#xff0c;它分为3个层次&#xff1a;时、分、秒&#xff0c;只有秒针在运动&#xff0c;走动一格时间为1秒&#xff0c;走一圈为1分钟&…

再谈时间轮

时间轮很早前就很流行了&#xff0c;在很多优秀开源框架中都有用到&#xff0c;像kafka、netty。也算是现在工程师基本都了解的一个知识储备了。有幸在工作中造过两次轮子&#xff0c;所以今天聊聊时间轮。 时间轮是一种高性能定时器。 时间轮&#xff0c;顾名思义&#xff0c…

时间轮原理及其在框架中的应用

一、时间轮简介 1.1 为什么要使用时间轮 在平时开发中&#xff0c;经常会与定时任务打交道。下面举几个定时任务处理的例子。 1&#xff09;心跳检测。在Dubbo中&#xff0c;需要有心跳机制来维持Consumer与Provider的长连接&#xff0c;默认的心跳间隔是60s。当Provider在3…

时间轮分析

背景 在需要完成重试机制或者心跳机制一类的业务&#xff0c;除了使用excutors做定时任务。还可以使用时间轮完成这类需求 分析 使用dubbo中的心跳重试作为案例分析时间轮的使用 源码分析 在HeaderExchangeClient启动的时候调用startHeartBeatTask()开启心跳任务&#xff0…

Netty时间轮

概述 时间轮是一个高性能&#xff0c;低消耗的数据结构&#xff0c;它适合用非准实时&#xff0c;延迟的短平快任务&#xff0c;例如心跳检测。在netty和kafka中都有使用。 比如Netty动辄管理100w的连接&#xff0c;每一个连接都会有很多超时任务。比如发送超时、心跳检测间隔等…

高性能定时器时间轮的实现

时间轮的概念 关于定时器有很多种&#xff0c;有基于升序的定时器时间链表&#xff0c;但是这种链表存在效率的不足&#xff0c;就是当插入定时器的时候时间复杂度是O(n).今天&#xff0c;我们来认识一下高性能定时器时间轮。 如上图所示&#xff0c;就是一个时间轮的基本轮廓…

深度剖析 -- 时间轮算法(TimingWheel)是如何实现的?

前言 时间轮的应用场景很多&#xff0c;在 Netty、Akka、Quartz、ZooKeeper 、Kafka等组件中都存在时间轮的踪影。我们下面讲解的时间轮的实现以JRaft中的为例子进行讲解&#xff0c;因为JRaft这部分的代码是参考Netty的&#xff0c;所以大家也可以去Netty中去寻找源码实现。 …

Kafka原理--时间轮(延时操作)

原文网址&#xff1a;Kafka原理--时间轮(延时操作)_IT利刃出鞘的博客-CSDN博客 简介 说明 本文介绍Kafka的时间轮的原理。 Kafka没有延迟队列功能供用户使用&#xff0c;本文介绍的延时操作仅仅是Kafka内部的使用&#xff0c;用户无法使用。 Kafka延时操作 Kafka中存在大量的…

时间轮算法HashedWheelTimer

文章目录 一.HashedWheelTimer是什么?二.能干什么?为什么需要这个东西?优点适用场景 三.怎么用?使用步骤1.引入pom2.使用举例 四.时间轮原理五.使用注意点1.一个HashedWheelTimer对象只有一个worker线程2.每次添加的任务只会执行一次3.时间轮的参数非常重要4.所有的任务都是…

时间轮-理论篇

1. 引言 定时任务再开发过程中无处不在&#xff0c;定时发送消息&#xff0c;定时更新数据库表的状态&#xff0c;Linux系统定时执行脚本等等。这些操作都离不开定时任务&#xff0c;那么这些定时任务是怎么实现的是否又去想过。如果让开发者自己去实现一个定时任务又该怎么实…

定时任务的实现原理:时间轮算法

文章目录 前言时间轮定时使用方式时间轮定时内部原理时间轮定时源码剖析构造方法添加任务工作线程启动工作线程run方法指针跳动将队列任务放入时间轮中链表任务遍历定时任务执行 前言 最近在思考实现定时任务的几种方式&#xff0c;比如 quartz&#xff0c;delay queue&#xf…

时间轮-Java实现篇

在前面的文章《时间轮-理论篇》讲了时间轮的一些理论知识&#xff0c;然后根据理论知识。我们自己来实现一个简单的时间轮。 1. 理论抽象 将时间轮的理论进行抽象&#xff0c;主要有两个方面&#xff1a; 时间轮的转动每一个时间间隔任务的处理&#xff0c;从时间间隔的Buke…

时间轮timewheel算法

时间轮是个不太常见&#xff0c;但在部分场景有较高使用价值的工具。 时间轮常用于延时任务&#xff0c;在Netty、akka、Quartz、Zookeeper等高性能组件中都存在时间轮定时器的踪影。 从定时任务说起 自然界中定时任务无处不在&#xff0c;太阳每天东升西落&#xff0c;候鸟的…

定时器实现原理——时间轮

时间轮 时间轮算法是通过一个时间轮去维护定时任务&#xff0c;按照一定的时间单位对时间轮进行划分刻度。然后根据任务延时计算任务落在该时间轮的第几个刻度上&#xff0c;如果任务时长超出了刻度数量&#xff0c;则需要增加一个参数记录时间轮需要转动的圈数。 简单时间轮…

时间轮算法

一、时间轮算法 1. 时间轮的基本概念 Kafka中存在大量的延时操作&#xff0c;如延时生产、延时消费等&#xff1b;而JDK中自带的 Timer 和 DelayQueue 的插入和删除操作的平均复杂度为 O&#xff08;nlogn&#xff09;&#xff0c;无法满足 Kafka 的高性能要求&#xff0c;因…

时间轮算法(TimingWheel)

时间轮算法的应用非常广泛&#xff0c;在 Dubbo、Netty、Kafka、ZooKeeper、Quartz 的组件中都有时间轮思想的应用&#xff0c;甚至在 Linux 内核中都有用到。 其思想理论基础可以参考论文&#xff1a;http://www.cs.columbia.edu/~nahum/w6998/papers/ton97-timing-wheels.pd…