Quartz集群增强版_01.集群及缺火处理(ClusterMisfireHandler)
Quartz集群增强版_01.集群及缺火处理(ClusterMisfireHandler)
主要目的
应用(
app
)与节点(node
)状态同步不管是
node
还是app
,都可以通过对应state
来控制节点及整个应用的启停
,这是很重要的功能,同时对于集群/缺火的锁操作也是基于app
来做的,同时附加在app
上的这个锁是控制所有应用及集群之间的并发操作,同样也是很重要的~任务状态与执行状态更新
因为任务扫描主要操作的是执行时间项(
execute
)信息,同时变更的也是执行项的状态(state
),故此需要更新任务(job
)状态熄火任务恢复执行
任务扫描调度的过程可能存在
GC
及DB断连
的情况,需要及时修正next_fire_time
以保证在异常恢复后能正常被扫到并被执行清理历史记录
清理的执行频度很低,如果可以的话建议是后管接入 click sdk 手动操作,这里的自动清理是兜底方案,基于数据库锁的任务并发在表数据越少时性能理论上就越好~ ,自动清理有两大任务:
- 1.清理执行无效应用及非执行节点
- 2.清理任务及执行配置
创建应用及执行节点
这是必要的操作,预创建节点及应用方便后续管理,同时执行调度也依赖于节点及应用的状态
前置处理
前置处理指的是 Quartz
启动时必做的维护,主要包含三部分主要内容:
- 01.写入应用(
app
) 及 节点(node
) ,这是很重要的 - 02.恢复/更新应用状态
- 将执行中或异常的
job
拿出来并检查其关联的执行项,通过执行项(execute
)的状态更新任务(job
)状态,如果
多执行项存在多个状态,状态的优先级为(从高到低):ERROR
->EXECUTING
->PAUSED
->COMPLETE
代码表象为 :
List<QrtzExecute> executes = getDelegate().getExecuteByJobId(conn,job.getId());
boolean hasExecuting = false;
boolean hasPaused = false;
boolean hasError = false;
boolean hasComplete = false;
for( QrtzExecute execute:executes ){
final String state = execute.getState();
if("EXECUTING".equals(state)){
hasExecuting=true;
}else if("PAUSED".equals(state)){
hasPaused=true;
}else if("ERROR".equals(state)){
hasError=true;
}else if("COMPLETE".equals(state)){
hasComplete=true;
}else{
continue; // 这里一般是INIT
}
}
// 如果所有状态都有则按以下优先级来
String beforeState = job.getState();
if(hasError){
job.setState("ERROR");
}else if(hasExecuting){
job.setState("EXECUTING");
}else if(hasPaused){
job.setState("PAUSED");
}else if(hasComplete){
job.setState("COMPLETE");
}else{
continue; // 这里对应上面的INIT状态,不做处理
}
// 不做无谓的更新...
if(!job.getState().equals(beforeState)){
job.setUpdateTime(now);
getDelegate().updateRecoverJob(conn,job);
}
- 03.恢复/更新执行状态
获取当前应用下的所有执行中或异常的任务(job
),并逐步恢复任务下所有执行中(EXECUTING
)或异常(ERROR
)的任务,主要是重新计算 next_fire_time
后置处理
- 01.后置处理的内容是包含所有前置处理,同时对集群并发做了加锁 (这个很重要,后一段会讲到)
- 02.同步节点状态与应用状态不一致的问题
- 03.更新
check
标志,这个check
标志主要方便于后续清理之使用,同时app
上的 check (time_next
) 是作为锁定周期的判断依据
?关于并发锁的处理
这个问题可以详细说明一下,一般一个loop(循环)是 15s(TIME_CHECK_INTERVAL
) ,在集群环境中同时存在多个节点的并发问题,所以对集群及缺火的处理就存在重复执行
一开始我的思考是按照乐观锁的思路来做,代码大概是这样的:
int ct = getDelegate().updateQrtzAppByApp(conn,app);
// 5.获取app锁的才可执行 clear 清理以及 recover 恢复,以减少读写
if( ct>0 ){
// 获取到锁后的处理
}
但是这样存在重复执行的情况,具体情况先看图:
上图中node1
与 node2
的开始时间相差5s
,所以造成了他们获取锁的时间存在5s
的时间差异,因为有这5s
的存在,多个节点几乎都可以执行这个update
语句以获取锁,这样往下的逻辑必然存在重复执行!
任务调度扫描(QuartzSchedulerThread
)是统一等到 next_fire_time
的那一刻来竞争锁,而集群/缺火处理(ClusterMisfireHandler
)在一个 while
的大循环内 这个循环每次是15s
,所以每个节点的所执行的周期是15s
(TIME_CHECK_INTERVAL
),而锁的竞争却是在执行 update
的那一刻
如果借用 任务扫描(QuartzSchedulerThread
)的处理思路就是 再加一个 while
或者 sleep
等待到下一个 check_time(time_next
),代码将如下:
long t=0;
// 这里的 check_time 就是应用的check时间,loop_time则是当前循环开始时间
if( (t=check_time-loop_time)> 0 ){
Thread.sleep(t);
}
int ct = getDelegate().updateQrtzAppByApp(conn,app);
// 5.获取app锁的才可执行 clear 清理以及 recover 恢复,以减少读写
if( ct>0 ){
// 获取到锁后的处理
}
以上这样就可以可以基本保证多个node
在同一时间竞争同一把锁了... ,这样做还有一个好处,就是基本保证了各个节点的 ClusterMisfireHandler
的循环时间
基本一致,同时通过sleep可以随机打散循环时间(添加偏移量)将
ClusterMisfireHandler
的循环处理打散在其他节点执行 。
但是,但是哦,如果使用 sleep
+ update
的方式 也可能导致同一时间加锁(update
)竞争的开销,所以,我借鉴了 shedlock
开源项目的启发,就是思考能不能在竞争锁之前判断锁定时间
,获取到锁之后加一个锁定时间
锁定时间
内的不再去竞争锁,锁定时间外的则可以,大致如图:
看图,如果我们假定 node1
是先于 node2
执行, 当 node1
在 14:15 成功获取锁后 他的下一次执行时间预期就是 14:30 ,同时如果加一个10s
的锁定时间
(图中蓝线),就是在 15:25 及之前是不可以去竞争锁,这样当
node2
在 14:20 去尝试获取锁之前发现最近一个锁定时间点是 14:25 (及之后) ,此时 node2
会自动放弃竞争锁(执行update
),同时进入下一时间点 14:35 并再次判断锁定时间点儿,当然这并不是没有代价的,各位自行领悟吧
经过改造后的代码如下:
// TIME_CHECK_INTERVAL 是循环周期,固定为15秒
long tw = TIME_CHECK_INTERVAL/10*3; // 70% 减少并发
if( (app.getTimeNext()-_start)>tw ){
continue;
}
// 5.获取app锁的才可执行 clear 清理以及 recover 恢复,以减少读写
if( ct>0 ){
// 获取到锁后的处理
}
Quartz集群增强版_01.集群及缺火处理(ClusterMisfireHandler)的更多相关文章
- 【Quartz】配置最简单的集群
在许多情况,我们希望我们的定时任务是可靠的,不会因系统故障.机器宕机而导致某一笔定时任务不能按时运行.这种情况下,我们就需要为Quartz做个集群. 最简单的情况,有两台机器或两个应用,同时维护一批定 ...
- 高可用OpenStack(Queen版)集群-13.分布式存储Ceph
参考文档: Install-guide:https://docs.openstack.org/install-guide/ OpenStack High Availability Guide:http ...
- docker搭建kafka集群(高级版)
1. 环境docker, docker-compose 2.zookeeper集群 /data/zookeeper/zoo1/config/zoo.cfg # The number of millis ...
- Kubernetes集群搭建之Etcd集群配置篇
介绍 etcd 是一个分布式一致性k-v存储系统,可用于服务注册发现与共享配置,具有以下优点. 简单 : 相比于晦涩难懂的paxos算法,etcd基于相对简单且易实现的raft算法实现一致性,并通过g ...
- Kubernetes使用集群联邦实现多集群管理
Kubernetes在1.3版本之后,增加了“集群联邦”Federation的功能.这个功能使企业能够快速有效的.低成本的跨区跨域.甚至在不同的云平台上运行集群.这个功能可以按照地理位置创建一个复制机 ...
- 庐山真面目之十二微服务架构基于Docker搭建Consul集群、Ocelot网关集群和IdentityServer版本实现
庐山真面目之十二微服务架构基于Docker搭建Consul集群.Ocelot网关集群和IdentityServer版本实现 一.简介 在第七篇文章<庐山真面目之七微服务架构Consul ...
- 用 edgeadm 一键安装边缘 K8s 集群和原生 K8s 集群
背景 目前,很多边缘计算容器开源项目在使用上均存在一个默认的前提:用户需要提前准备一个标准的或者特定工具搭建的 Kubernetes 集群,然后再通过特定工具或者其他方式在集群中部署相应组件来体验边缘 ...
- 分布式缓存技术redis学习系列(四)——redis高级应用(集群搭建、集群分区原理、集群操作)
本文是redis学习系列的第四篇,前面我们学习了redis的数据结构和一些高级特性,点击下面链接可回看 <详细讲解redis数据结构(内存模型)以及常用命令> <redis高级应用( ...
- redis 集群环境搭建-redis集群管理
集群架构 (1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽. (2)节点的fail是通过集群中超过半数的节点检测失效时才生效. (3)客户端与redi ...
- 分布式缓存技术redis学习(四)——redis高级应用(集群搭建、集群分区原理、集群操作)
本文是redis学习系列的第四篇,前面我们学习了redis的数据结构和一些高级特性,点击下面链接可回看 <详细讲解redis数据结构(内存模型)以及常用命令> <redis高级应用( ...
随机推荐
- 使用CyFES对配体运动轨迹进行数据透视
技术背景 如果我们有一个蛋白质X和一个配体Y,那么可以对这个X+Y的体系跑一段长时间的分子动力学模拟,以观测这个体系在不同结合位点下的稳定性.类似于前面一篇博客中计算等高面的方法,我们可以计算轨迹的K ...
- Maven 设置 JDK 版本
Maven 设置 JDK 版本是通过 Apache Maven Compiler Plugin 插件实现的.它用于编译项目的源代码. 方法一 有时候你可能需要将某个项目编译到与当前使用的 JDK 版本 ...
- (八)Redis 主从复制、切片集群
一.主从复制 1.主从关系 都说的 Redis 具有高可靠性,这里有两层含义:一是数据尽量少丢失,二是服务尽量少中断.AOF 和 RDB 保证了前者,而对于后者,Redis 的做法就是将一份数据同时保 ...
- 【YashanDB知识库】应用绑定参数的慢查询,慢日志抓取不到
[问题分类]功能使用 [关键字]慢日志.绑定参数 [问题描述]应用下发的绑定参数的SQL,慢日志功能抓取不到查询耗时超过阈值的执行SQL [问题原因分析]慢日志的功能没有考虑绑定参数的情况 [解决/规 ...
- Innodb 单表索引查询和连接查询效率分析
一.MySQL查询访问方法 mysql执行查询语句的方式叫做访问方法或访问类型,这些访问类型具体为 const.ref.range.index.all等. 同一个查询语句可以使用多种不同的访问方法来执 ...
- 超轻量级、支持插件的 .NET 网络通信框架
前言 给大家推荐一个轻量级的.支持插件的综合网络通信库:TouchSocket. TouchSocket 的基础通信功能包括 TCP.UDP.SSL.RPC 和 HTTP.其中,HTTP 服务器支持 ...
- 二叉树的 Morris 中序遍历——O(1)空间复杂度
回顾 问题陈述: 给定一棵二叉树,实现中序遍历并返回包含其中序序列的数组 例如给定下列二叉树: 我们按照左.根.右的顺序递归遍历二叉树,得到以下遍历: 最终中序遍历结果可以输出为: [3, 1, 9, ...
- Nuxt Kit 中的上下文处理
title: Nuxt Kit 中的上下文处理 date: 2024/9/16 updated: 2024/9/16 author: cmdragon excerpt: Nuxt Kit 提供的上下文 ...
- C++ 指针基础
指针 指针具有强大的能力,其本质是协助程序员完成内存的直接操作 指针: 特定类型数据在内存中的存储地址,即内存地址 指针只是一个逻辑概念,其实际应用是:指针变量 语法 * 符号有两种含义: 声明时:* ...
- 【赵渝强老师】使用Docker Compose进行服务编排
一.什么是Docker Compose? Docker Compose是一个用来定义和运行复杂应用的Docker工具.一个使用Docker容器的应用,通常由多个容器组成.使用Docker Compos ...