Quartz集群增强版_02.任务轮询及优化

转载请著名出处 https://www.cnblogs.com/funnyzpc/p/18555665

开源地址 https://github.com/funnyzpc/quartz

任务轮询的主要工作是按固定频度(时间5s)去执行项表捞未来5s内将要执行的任务,轮询这些任务待到执行时间点时将任务扔到线程池去执行。

    看似很简单其实也有各种各样的问题存在,这里不表 请往下看 ~

    另外,任务轮询的主要逻辑在:QuartzSchedulerThread ,读者有兴趣可以看看源码~

轮询窗口内的任务

情况是这样子的,先看图:

假使,现在有一个任务 task1 ,他的执行时间是每2秒执行一次,但是记录执行项里面只会存一个下一次执行时间(next_fire_time),落在上图就是2s的位置,这样在每5秒轮询一次的时候会漏掉一次执行(4s的位置)

    这个问题解决起来其实很简单,就是每次从db获取到的执行项再做计算,除当前次外 5s 内的执行的时间全部计算出来,这其中尤其要注意的是同一个时间项在当前次内有多次执行的一定要有顺序

    在后续会有循环等待,但在特殊情况下,用上图说:由于同批次其他任务存在延迟(假如延迟大于等于2s) ,这时候4s时的这个任务可能早于 2s 时的任务执行,同时又由于 4s 时的任务的 参照时间是 2s 时的任务的时间(pre_fire_time) (可能很难理解吧,建议看看后续update语句)

    在被扔到线程池前,数据库由于 2s 时的任务并没有执行,数据库里面存的是 0s 时的任务配置,从而就会导致4s时的任务不会执行(因为他竞争不到锁)(2s任务参照的是0s时的任务 4s参照的是2s时的任务),这是很严重的问题; 如果任务是有序的且计算出来的4s时的任务总是排在 2s 时的任务之后,即使其他任务存在延迟,也会相应保证后续时间点儿任务正常执行,很大程度避免了任务丢失~

获取执行权限(获取锁)

因为存在集群并发的问题,所以一个任务同一时间必须只由一个节点来执行,同时也为了保证执行顺序 所以在任务被丢到线程池之前需要在数据库 做一个 UPDATE 的竞争操作,具体SQL语句如下:

UPDATE
QRTZ_EXECUTE SET
PREV_FIRE_TIME =? ,
NEXT_FIRE_TIME = ?,
TIME_TRIGGERED =?,
STATE =?,
HOST_IP =?,
HOST_NAME =?,
END_TIME =?
WHERE ID = ?
AND STATE = ? -- old STATE
AND PREV_FIRE_TIME = ? -- old PREV_FIRE_TIME
AND NEXT_FIRE_TIME = ? -- old NEXT_FIRE_TIME

可以看到,必须是被更新记录必须是要对齐 STATEPREV_FIRE_TIMENEXT_FIRE_TIME 才可更新~

使用动态线程池

Quartz 一般使用的是 SimpleThreadPool 作为其任务的线程池,既然简单必然是: 内部使用固定线程处理

    一开始,我是准备就着源码做部分改动来着,后来发现没这边简单,原 Quartz 在获取锁的

时候会使用线程本地变量(ThreadLocal) 缓存 执行线程 以做并发控制,后来不得已将逻辑大部分推翻做重构,这是很大的变化; 现在,对于 Quartz集群增强版 来说,不再有 ThreadLocal 的困扰, 只需关注自身 执行线程池配置的实现逻辑即可,这就有了 MeeThreadPool 不仅有了线程分配控制也有了队列,这是一大变化,现在你可以使用 MeeThreadPool 也可以继续使用 SimpleThreadPool

这是 MeeThreadPool 的主要逻辑:


protected void createWorkerThreads(final int createCount) {
int cct = this.count = createCount<1? Runtime.getRuntime().availableProcessors() :createCount;
final MyThreadFactory myThreadFactory = new MyThreadFactory(this.getThreadNamePrefix(), this);
this.poolExecutor = new ThreadPoolExecutor(cct<=4?2:cct-2,cct+2,6L, TimeUnit.SECONDS, new LinkedBlockingDeque(cct+2),myThreadFactory);
} private final class MyThreadFactory implements ThreadFactory {
final String threadPrefix ;//= schedulerInstanceName + "_QRTZ_";
final MeeThreadPool meeThreadPool;
private final AtomicInteger threadNumber = new AtomicInteger(1);
public MyThreadFactory(final String threadPrefix,final MeeThreadPool meeThreadPool) {
this.threadPrefix = threadPrefix;
this.meeThreadPool = meeThreadPool;
} @Override
public Thread newThread(Runnable r) {
WorkerThread wth = new WorkerThread(
meeThreadPool,
threadGroup,
threadPrefix + ((threadNumber.get())==count?threadNumber.getAndSet(1):threadNumber.getAndIncrement()),
getThreadPriority(),
isMakeThreadsDaemons(),
r);
if (isThreadsInheritContextClassLoaderOfInitializingThread()) {
wth.setContextClassLoader(Thread.currentThread().getContextClassLoader());
}
return wth;
}
}

伸缩性以及可用性有了大大的提高,需要提一嘴的是 如果使用 ThreadPoolExecutor 开发 Quartz 线程池一定要注意:

  • 核心线程打满之后 task 一定是先进入队列
  • 队列满了之后才会依次创建线程直至最大线程数
  • 一定要注意是否有线程被打满后的异常拒绝处理策略,如果不希望出现异常拒绝 那是否要考虑在提交任务之前判断线程池是否被打满
  • 开发完成一定要进行广泛的测试,以符合预期

轮询超时/执行超时问题

JVM执行GC或者DB或者网络存在故障,亦或是主机性能存在瓶颈,或是线程池被打满 ... 等等,均会出现超时的问题,对于此类问题本 Quartz集群增强版 做了以下优化:

  • 做了容忍度偏移,让任务不拘泥于几毫秒的差异提前执行
   //1.时间偏移(6毫秒)
long ww = executeList.size()-1000<0 ? 4L : ((executeList.size()-1000L)/2000L)+4L ;
ww= Math.min(ww, 8L);
while( !executeList.isEmpty() && (System.currentTimeMillis()-now)<=LOOP_INTERVAL ){
long _et = System.currentTimeMillis();
QrtzExecute ce = null; // executeList.get(0);
for( int i = 0;i< executeList.size();i++ ){
QrtzExecute el = executeList.get(i);
// 这是要马上执行的任务
if( el.getNextFireTime()-_et <= ww){
ce=el;
break;
}
if(i==0){
ce=el;
continue; // 如果执行列表长度为一,则会直接进入下面sleep等待
}
// 总是获取最近时间呢个
if( el.getNextFireTime() <= ce.getNextFireTime() ){
ce = el;
}
}
executeList.remove(ce); // 一定要移除,否则无法退出while循环!!!
// 延迟
long w = 0;
if((w = (ce.getNextFireTime()-System.currentTimeMillis()-ww)) >0 ){
try {
Thread.sleep(w);
}catch (Exception e){
}
}
// 后续代码略
}
  • 对于任务轮询,保证轮询时间间隔的同时也做了偏移修正
     // 延迟
long st = 0;
if((st = (LOOP_INTERVAL-(System.currentTimeMillis()-now)-2)) >0 ){
try {
Thread.sleep(st);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if( st<-10 && st%5==0 ){
LOG.error("当前次任务轮询超时:"+st);
}
// 防止因轮询超时的必要手段
now = st<-1000?
System.currentTimeMillis()/1000*1000 :
System.currentTimeMillis()+(st<-10?st:0);
  • 对于事实的延迟做了任务修正

    这个修正主要依赖于 ClusterMisfireHandler 的轮询处理,以保证后续中断的任务能及时恢复~

对于偏移,需要解释下: 偏移是对于整个循环而言的,任务循环一次是 5s ,由于写表或任务提交可能造成整个循环会有 几毫秒几十毫秒的偏差 ,这是向后偏移,如果任务提前执行完成 则整个循环可能不足 5s 这是向前偏差 ~

不管是向前还是向后都是需要避免的~

最后

为了更清楚的了解 Quartz集群增强版 建议过一遍结构图:

Quartz集群增强版_02.任务轮询及优化❤️的更多相关文章

  1. Quartz集群

    为什么选择Quartz: 1)资历够老,创立于1998年,比struts1还早,但是一直在更新(27 April 2012: Quartz 2.1.5 Released),文档齐全. 2)完全由Jav ...

  2. 项目中使用Quartz集群分享--转载

    项目中使用Quartz集群分享--转载 在公司分享了Quartz,发布出来,希望大家讨论补充. CRM使用Quartz集群分享  一:CRM对定时任务的依赖与问题  二:什么是quartz,如何使用, ...

  3. Quartz集群原理及配置应用

    1.Quartz任务调度的基本实现原理 Quartz是OpenSymphony开源组织在任务调度领域的一个开源项目,完全基于Java实现.作为一个优秀的开源调度框架,Quartz具有以下特点: (1) ...

  4. quartz集群调度机制调研及源码分析---转载

    quartz2.2.1集群调度机制调研及源码分析引言quartz集群架构调度器实例化调度过程触发器的获取触发trigger:Job执行过程:总结:附: 引言 quratz是目前最为成熟,使用最广泛的j ...

  5. (1)quartz集群调度机制调研及源码分析---转载

    quartz2.2.1集群调度机制调研及源码分析 原文地址:http://demo.netfoucs.com/gklifg/article/details/27090179 引言quartz集群架构调 ...

  6. 使用sqlserver搭建高可用双机热备的Quartz集群部署【附源码】

    一般拿Timer和Quartz相比较的,简直就是对Quartz的侮辱,两者的功能根本就不在一个层级上,如本篇介绍的Quartz强大的序列化机制,可以序列到 sqlserver,mysql,当然还可以在 ...

  7. 【原理、应用】Quartz集群原理及配置应用

    一.Quartz任务调度的基本实现原理 Quartz是OpenSymphony开源组织在任务调度领域的一个开源项目,完全基于Java实现.作为一个优秀的开源调度框架,Quartz具有以下特点: 强大的 ...

  8. Springboot2.X集成Quartz集群

    为什么要使用Quzrtz集群 在项目进行集群部署时,如果业务在执行中存在互斥关系,没有对定时任务进行统一管理,就会引起业务的多次执行,不能满足业务要求.这时就需要对任务进行管理,要保证一笔业务在所有的 ...

  9. 双机热备的Quartz集群

    sqlserver搭建高可用双机热备的Quartz集群部署[附源码]   一般拿Timer和Quartz相比较的,简直就是对Quartz的侮辱,两者的功能根本就不在一个层级上,如本篇介绍的Quartz ...

  10. quartz集群报错but has failed to stop it. This is very likely to create a memory leak.

    quartz集群报错but has failed to stop it. This is very likely to create a memory leak. 在一台配置1核2G内存的阿里云服务器 ...

随机推荐

  1. Flutter调试debug或者打包release帧率只有60的原因

    问题描述 最近发现Flutter中引入像素较大的静态图片或者字体导致调试或者打包之后在高刷手机上帧率只有60的问题. 测试设备为小米13,可在开发者选项中直接打开帧率显示, 也可使用statsfl插件 ...

  2. 【爬虫实战】——利用bs4和sqlalchemy操作mysql数据库,实现网站多行数据表格爬取数据

    前言 此篇接上一篇的内容,在其基础上爬取网站的多行表格数据,以及把数据写入到mysql数据库中 目录 一.定位表格查找元素 二.提取数据 三.写入mysql数据库 四.附录 一.定位表格查找元素 首先 ...

  3. AI产品经理的探索:技能、机遇与未来展望

    Ai时代的产品经理 随着人工智能(AI)的飞速发展,AI已经从一个前沿技术概念逐步演变为驱动各行业创新的核心力量.从智能助手到自动驾驶,从个性化推荐系统到图像识别,AI正在以不可思议的速度改变着我们的 ...

  4. docker高级篇-docker-compose容器编排介绍及实战

    Docker-compose是什么?能干嘛?解决了哪些痛点? 是什么? Docker-compose是Docker官方推出 的一个工具软件,可以管理多个Docker容器组成的一个应用.你需要编写一个一 ...

  5. release版本 APP 出现waiting for debugger

    前提:同一包代码,打包两个版本,修改包名,在同一设备上存在两个版本 状态:运行时,发现 一版本是正常 另一版本打开会提示 Waiting for debugger弹窗,(此时由于是开发状态,设备一直开 ...

  6. .NET 最好用的验证组件 FluentValidation

    前言 一个 .NET 验证框架,支持链式操作,易于理解,功能完善,组件内提供十几种常用验证器,可扩展性好,支持自定义验证器,支持本地化多语言. 项目介绍 FluentValidation 是一个开源的 ...

  7. JavaScript 中 structuredClone 和 JSON.parse(JSON.stringify()) 克隆对象的区别

    JavaScript 中 structuredClone 和 JSON.parse(JSON.stringify()) 克隆对象的异同点 一.什么是 structuredClone? 1. struc ...

  8. 为什么Java已经不推荐使用Stack了?

    为什么不推荐使用Stack Java已不推荐使用Stack,而是推荐使用更高效的ArrayDeque 为什么不推荐使用 性能低:是因为 Stack 继承自 Vector, 而 Vector 在每个方法 ...

  9. HTML & CSS – dir, direction, writing-mode, ltr (left to rigth), rtl (right to left)

    前言 世界上有很多语言的阅读方向是不同的. 英文 中文 (以前才有竖排文字, 现在中文和英语一样了) 阿拉伯文 (Arabic) 面对不同的语言, HTML 和 CSS 就需要不同的写法. 虽然我没有 ...

  10. SimpleAISearch:C# + DuckDuckGo 实现简单的AI搜索

    最近AI搜索很火爆,有Perplexity.秘塔AI.MindSearch.Perplexica.memfree.khoj等等. 在使用大语言模型的过程中,或许你也遇到了这种局限,就是无法获取网上最新 ...