当前位置: 首页 > news >正文

时间轮:XXL-JOB 高效、精准定时任务调度实现思路分析

大家好,我是此林。

定时任务是我们项目中经常会遇到的一个场景。那么如果让我们手动来实现一个定时任务框架,我们会怎么做呢?

1. 基础实现:简单的线程池+时间轮询

最直接的方式是创建一个定时任务线程池,用户每提交一个定时任务,就分配一个线程去执行。每个线程使用 Thread.sleep() 或者 while (true) 不断轮询,检查当前时间是否达到了任务的触发时间。

这种方式的问题:
  • 一个线程只能执行一个任务,如果定时任务很多,线程池的线程很快就会被占满,导致新的任务无法执行。比如机器是 4 核 CPU,最多可能支持 10 个线程同时执行任务。超过这个数量的任务只能阻塞等待,影响可用性。

  • 一般定时任务都是要永久执行,不可能就执行一次或几次就丢弃了。在这种方案下,一台机器只能执行那几个任务,因为那几个任务一直在占用线程池,其他任务无法执行。

2. 优化方案:任务列表+线程池

为了解决上面的问题,我们可以改进方案,使用 检查任务-派发任务 模式:

  • 创建一个任务列表,用来存放所有待执行的任务。

  • 使用一个独立的线程,不断扫描任务列表,找到即将到达触发时间的任务。

  • 将快到触发时间的任务提交给线程池,线程池里的线程只负责执行任务,而不需要一直轮询等待。

  • 任务执行完后,线程自动释放回到线程池,提高并发能力。

这种方式的优点:
  • 线程池里的线程专注执行任务,不需要每个线程都去检测触发时间,提高了 CPU 的利用率。

  • 可以同时执行多个任务,避免任务阻塞导致的执行延迟。

  • 任务调度逻辑集中管理,便于扩展和优化。

存在的问题:任务过期

但这种方式也有一个问题:当任务列表里有几百、几千个任务时,扫描任务的线程可能处理不过来,导致一些任务在被扫描到时已经过期了。

那你可能会说,可以增加时间啊,比如提前两秒就提交入线程池。但这样会导致触发时间精度下降,比如某个任务严格要求每3秒执行一次,提前两秒去执行显然是不行的。

3. 进一步优化:任务预读+时间轮

为了解决任务过期问题,我们接下来引入 任务预读时间轮 的概念。

这里就援引一张网络上的时间轮图片。

时间轮的核心思想

可以把时间轮想象成 时钟表

  • 时间轮 = 一个圆形数组(环形结构)
  • 每个刻度 = 一秒钟(时间槽位,slot)
  • 当前指针 = 记录当前时间进度
  • 任务 = 被分配到不同的槽位,等指针走到对应的槽位时执行

当时间轮的指针随着时间推进时,就会触发当前槽位内的任务执行。

同样的,我们开启一个独立的线程,不断扫描任务列表,通过任务的触发时间,计算每个任务在时间轮上的槽位。如下图,为 XXL-JOB 源码。

JobScheduleHelper.java  
Thread scheduleThread 部分源码

  1. 通过任务触发时间计算在 时间轮 上的槽位
  2. 把任务添加入 时间轮 相应的槽位(一个槽位上可以同时存在多个任务,用一个列表维护)
  3. 更新任务的下一次触发时间。

上述线程的把任务添加到时间轮的操作称之为:预读

同时,还有一个独立线程 ringThread,可以理解为时间轮上的 指针 。它通过 while 循环不断获取当前的秒数(java.util.Calendar)。

int nowSecond = Calendar.getInstance().get(Calendar.SECOND);

通过对当前秒数(nowSecond)对60秒取模,去时间轮里相应的槽位得到相应的任务列表,提交给线程池执行任务(JobTriggerPoolHelper)。

如上操作,检查任务的线程(scheduleThread) 无需等到任务即将到达触发时间时,再向线程池提交任务,只需要预读任务加入到时间轮即可。派发任务的工作由 指针(ringThread)完成即可。从而实现了防止了任务的过期,保证了精准触发。

4. XXL-JOB任务调度源码

JobScheduleHelper.java

 XXL-JOB 的时间轮没有使用环形结构,而是一个ConcurrentHashMap。后续是对60取模来实现类似的循环功能。

scheduleThread

由于调度中心将来可能会集群部署,所以这里使用 select...for update 的悲观锁,保证在同一时刻只能有一个调度中心在调度任务,防止任务重复调度。

XXL-JOB的MySQL表有个任务表,这里默认一次最多读前6000个任务到内存任务列表中(preReadCount = 6000,PRE_READ_MS = 5000,即5秒)。

所以这里读取的任务列表是未来5秒内将要触发的任务(最多前6000个,防止OOM或线程池来不及处理)

 遍历任务列表,对60取模,计算出槽位,将任务放入时间轮的对位槽位。

问:如果一个任务每300秒执行一次呢?是不是应该还有个记录圈数字段呢?

答:由于预读操作是未来5秒内将要触发的任务,所以不需要额外记录圈数,这个地方也是XXL-JOB和一般时间轮稍微不一样的点。

 

ringThread

这里的任务触发会把任务添加到线程池里,线程池并行地通过自研RPC的方式通知执行器执行。

(分布式环境下调度中心和执行器分别部署)

JobTriggerPoolHelper.java

这里有两个线程池,fastTriggerPoolslowTriggerPool。

线程池选择逻辑

  • 默认情况下,任务会分配给 fastTriggerPool,即高频率触发的线程池。
  • 如果某个任务在过去一分钟内超时超过 10 次(即 jobTimeoutCount 超过 10),那么该任务会被分配到 slowTriggerPool,即低频率触发的线程池。这是为了 限制高频超时任务对资源的占用,避免它们占用过多线程池资源,影响正常任务的调度。

源码见下图。

相关文章:

时间轮:XXL-JOB 高效、精准定时任务调度实现思路分析

大家好,我是此林。 定时任务是我们项目中经常会遇到的一个场景。那么如果让我们手动来实现一个定时任务框架,我们会怎么做呢? 1. 基础实现:简单的线程池时间轮询 最直接的方式是创建一个定时任务线程池,用户每提交一…...

CTF-web: Python YAML反序列化利用

PyYAML存在以下几个特殊标签,如果这些标签被不安全的解析,会造成解析漏洞 从 PyYaml 版本 6.0 开始,load 的默认加载器已切换到 SafeLoader,以降低远程代码执行的风险。更新后易受攻击的是 yaml.unsafe_load 和 yaml.load(input, Loaderyaml.UnsafeLoade…...

代码随想录算法训练营第三十八天-动态规划-完全背包-139.单词拆分

类似于回溯算法中的拆分回文串题目是要求拆分字符串,问这些字符串是否出现在字典里。但这道题可以反着来考虑,从字典中的单词能不能组成所给定的字符串 如果这样考虑, 这个字符串就背包,容器字典中的单词就是一个一个物品问题就转…...

ML基础-Jupyter notebook中的魔法命令

在 Jupyter Notebook 或 IPython 环境中,“魔法命令”(Magic Commands)是一些以百分号(%)或惊叹号(!)开头的特殊命令,用于执行一些与代码运行环境相关的操作,而不仅仅是执行普通的 P…...

Zookeeper入门部署(单点与集群)

本篇文章基于docker方式部署zookeeper集群,请先安装docker 目录 1. docker初期准备 2.启动zookeeper 2.1 单点部署 2.2 集群部署 3. Linux脚本实现快速切换启动关闭 1. docker初期准备 拉取zookeeper镜像 docker pull zookeeper:3.5.6 如果拉取时间过长&#xf…...

Kafa分区策略实现

引言 Kafka 的分区策略决定了生产者发送的消息会被分配到哪个分区中,合理的分区策略有助于实现负载均衡、提高消息处理效率以及满足特定的业务需求。 轮询策略(默认) 轮询策略是 Kafka 默认的分区策略(当消息没有指定键时&…...

Pyside/Pyqt中QWebEngineView和QWebEnginePage的区别

在 PySide/Qt 的 WebEngine 模块中,QWebEngineView 和 QWebEnginePage 是两个紧密相关但职责不同的类。以下是它们的核心区别和关系: 1. 职责区分 类名核心职责模块归属QWebEngineView作为可视化的窗口部件(Widget),负…...

Kafka的内部通信协议

引言 kafka内部用到的常见协议和优缺点可以看看原文 Kafka用到的协议 本文奖详细探究kafka核心通信协议和高性能的关键 网络层通信的实现 基于 Java NIO:Kafka 的网络通信层主要基于 Java NIO 来实现,这使得它能够高效地处理大量的连接和数据传输。…...

强大到工业层面的软件

电脑数据删不干净,简直是一种让人抓狂的折磨!明明已经把文件扔进了回收站,清空了,可那些残留的数据就像牛皮癣一样,怎么也除不掉。这种烦恼简直无处不在,让人从头到脚都感到无比烦躁。 首先,心…...

数据分析和AI丨应对AI实施挑战,工程领域AI应用的五大方法

工程领域的人工智能 (AI) 已经开始发挥价值,低代码和无代码工具正在使曾经仅属于专业数据科学家的 AI 能力变得大众化。 然而,并非工程领域的每个人都能从中受益,使用新的便捷的 AI 工具提高工作效率并不难&#xff0c…...

54. UDP协议

UDP协议 UDP(User Datagram Protocol,用户数据报协议)是一个无连接的传输层协议,它提供简单的、不可靠的信息传送服务。与TCP(传输控制协议)不同,UDP不提供数据包的排序、错误检查(仅…...

AJAX笔记入门篇

黑马程序员视频地址: 黑马程序员前端AJAX入门到实战全套教程https://www.bilibili.com/video/BV1MN411y7pw?vd_source0a2d366696f87e241adc64419bf12cab&spm_id_from333.788.videopod.episodes&p2https://www.bilibili.com/video/BV1MN411y7pw?vd_source…...

深入解析Java集合框架:春招面试要点

在上一篇文章中,我们深入探讨了Java核心基础,这是学习Java的基石。而在实际的Java开发中,集合框架的使用频率极高,它为我们提供了丰富的数据结构和算法实现,极大地提高了开发效率。对于春招面试来说,集合框…...

【Elasticsearch】Elasticsearch的查询

Elasticsearch的查询 DSL查询基础语句叶子查询全文检索查询matchmulti_match 精确查询termrange 复合查询算分函数查询bool查询 排序分页基础分页深度分页 高亮高亮原理实现高亮 RestClient查询基础查询叶子查询复合查询排序和分页高亮 数据聚合DSL实现聚合Bucket聚合带条件聚合…...

STM32 PWM驱动直流电机

接线图: 代码配置: 根据驱动舵机的代码来写,与舵机不同的是,这次的引脚接到了PA2上,所以需要改一下引脚以及改为OC3通道。 另外还需在配置两个GPIO引脚,来控制电机的旋转方向,这里连接到了PA4与…...

系统思考—心智模式

“我们的大脑对连贯性的渴望远胜于对准确性的追求。”—诺贝尔经济学得主丹尼尔卡尼曼 在面对复杂的决策时,我们往往更倾向于寻找那些能够迅速串联起来的信息,而非深入挖掘每一个细节的真实性。这种倾向在日常生活中或许能帮助我们迅速作出决策&#xf…...

JavaScript_02 表单

表单常用演示: 1.图片 结果失真了... 2.切换图片 切换结果 3.表单:...

【Qt】06-对话框

对话框 前言一、模态和非模态对话框1.1 概念1.2 模态对话框1.2.1 代码QAction类 1.2.2 模态对话框运行分析 1.3 非模态对话框1.3.1 代码局部变量和成员变量setAttribute 类 1.3.2 现象解释 二、标准对话框2.1 提示对话框 QMessageBox2.1.1 现象及解释 2.2 问题对话框2.2.1 现象…...

AI学习指南Ollama篇-使用Ollama构建自己的私有化知识库

一、引言 (一)背景介绍 随着企业对数据隐私和效率的重视,私有化知识库的需求日益增长。私有化知识库不仅可以保护企业数据的安全性,还能提供高效的知识管理和问答系统,提升企业内部的工作效率和创新能力。 (二)Ollama和AnythingLLM的结合 Ollama和AnythingLLM的结合…...

2.策略模式(Strategy)

定义 定义一系列算法,把它们一个个封装起来,并且使他们可互相替换(变化)。该模式使算法可独立于使用它的客户程序(稳定)而变化(拓展,子类化)。 动机(Motiva…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...

RocketMQ延迟消息机制

两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后&#xf…...

《通信之道——从微积分到 5G》读书总结

第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2

每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题

在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题

分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...

作为测试我们应该关注redis哪些方面

1、功能测试 数据结构操作:验证字符串、列表、哈希、集合和有序的基本操作是否正确 持久化:测试aof和aof持久化机制,确保数据在开启后正确恢复。 事务:检查事务的原子性和回滚机制。 发布订阅:确保消息正确传递。 2、性…...

云原生周刊:k0s 成为 CNCF 沙箱项目

开源项目推荐 HAMi HAMi(原名 k8s‑vGPU‑scheduler)是一款 CNCF Sandbox 级别的开源 K8s 中间件,通过虚拟化 GPU/NPU 等异构设备并支持内存、计算核心时间片隔离及共享调度,为容器提供统一接口,实现细粒度资源配额…...

Windows电脑能装鸿蒙吗_Windows电脑体验鸿蒙电脑操作系统教程

鸿蒙电脑版操作系统来了,很多小伙伴想体验鸿蒙电脑版操作系统,可惜,鸿蒙系统并不支持你正在使用的传统的电脑来安装。不过可以通过可以使用华为官方提供的虚拟机,来体验大家心心念念的鸿蒙系统啦!注意:虚拟…...