CN114756357B - 一种基于jvm的非阻塞分布式计划任务调度方法 - Google Patents
一种基于jvm的非阻塞分布式计划任务调度方法 Download PDFInfo
- Publication number
- CN114756357B CN114756357B CN202210668844.8A CN202210668844A CN114756357B CN 114756357 B CN114756357 B CN 114756357B CN 202210668844 A CN202210668844 A CN 202210668844A CN 114756357 B CN114756357 B CN 114756357B
- Authority
- CN
- China
- Prior art keywords
- task
- blocking
- distributed
- zookeeper
- node
- Prior art date
- Legal status (The legal status is an assumption and is not a legal conclusion. Google has not performed a legal analysis and makes no representation as to the accuracy of the status listed.)
- Active
Links
Images
Classifications
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F9/00—Arrangements for program control, e.g. control units
- G06F9/06—Arrangements for program control, e.g. control units using stored programs, i.e. using an internal store of processing equipment to receive or retain programs
- G06F9/46—Multiprogramming arrangements
- G06F9/48—Program initiating; Program switching, e.g. by interrupt
- G06F9/4806—Task transfer initiation or dispatching
- G06F9/4843—Task transfer initiation or dispatching by program, e.g. task dispatcher, supervisor, operating system
- G06F9/4881—Scheduling strategies for dispatcher, e.g. round robin, multi-level priority queues
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F9/00—Arrangements for program control, e.g. control units
- G06F9/06—Arrangements for program control, e.g. control units using stored programs, i.e. using an internal store of processing equipment to receive or retain programs
- G06F9/44—Arrangements for executing specific programs
- G06F9/455—Emulation; Interpretation; Software simulation, e.g. virtualisation or emulation of application or operating system execution engines
- G06F9/45504—Abstract machines for programme code execution, e.g. Java virtual machine [JVM], interpreters, emulators
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F9/00—Arrangements for program control, e.g. control units
- G06F9/06—Arrangements for program control, e.g. control units using stored programs, i.e. using an internal store of processing equipment to receive or retain programs
- G06F9/46—Multiprogramming arrangements
- G06F9/50—Allocation of resources, e.g. of the central processing unit [CPU]
- G06F9/5005—Allocation of resources, e.g. of the central processing unit [CPU] to service a request
- G06F9/5027—Allocation of resources, e.g. of the central processing unit [CPU] to service a request the resource being a machine, e.g. CPUs, Servers, Terminals
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F9/00—Arrangements for program control, e.g. control units
- G06F9/06—Arrangements for program control, e.g. control units using stored programs, i.e. using an internal store of processing equipment to receive or retain programs
- G06F9/46—Multiprogramming arrangements
- G06F9/50—Allocation of resources, e.g. of the central processing unit [CPU]
- G06F9/5061—Partitioning or combining of resources
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F9/00—Arrangements for program control, e.g. control units
- G06F9/06—Arrangements for program control, e.g. control units using stored programs, i.e. using an internal store of processing equipment to receive or retain programs
- G06F9/46—Multiprogramming arrangements
- G06F9/52—Program synchronisation; Mutual exclusion, e.g. by means of semaphores
- G06F9/526—Mutual exclusion algorithms
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F2209/00—Indexing scheme relating to G06F9/00
- G06F2209/48—Indexing scheme relating to G06F9/48
- G06F2209/482—Application
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F2209/00—Indexing scheme relating to G06F9/00
- G06F2209/48—Indexing scheme relating to G06F9/48
- G06F2209/483—Multiproc
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F2209/00—Indexing scheme relating to G06F9/00
- G06F2209/50—Indexing scheme relating to G06F9/50
- G06F2209/5011—Pool
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F2209/00—Indexing scheme relating to G06F9/00
- G06F2209/50—Indexing scheme relating to G06F9/50
- G06F2209/5018—Thread allocation
-
- Y—GENERAL TAGGING OF NEW TECHNOLOGICAL DEVELOPMENTS; GENERAL TAGGING OF CROSS-SECTIONAL TECHNOLOGIES SPANNING OVER SEVERAL SECTIONS OF THE IPC; TECHNICAL SUBJECTS COVERED BY FORMER USPC CROSS-REFERENCE ART COLLECTIONS [XRACs] AND DIGESTS
- Y02—TECHNOLOGIES OR APPLICATIONS FOR MITIGATION OR ADAPTATION AGAINST CLIMATE CHANGE
- Y02D—CLIMATE CHANGE MITIGATION TECHNOLOGIES IN INFORMATION AND COMMUNICATION TECHNOLOGIES [ICT], I.E. INFORMATION AND COMMUNICATION TECHNOLOGIES AIMING AT THE REDUCTION OF THEIR OWN ENERGY USE
- Y02D10/00—Energy efficient computing, e.g. low power processors, power management or thermal management
Landscapes
- Engineering & Computer Science (AREA)
- Software Systems (AREA)
- Theoretical Computer Science (AREA)
- Physics & Mathematics (AREA)
- General Engineering & Computer Science (AREA)
- General Physics & Mathematics (AREA)
- Debugging And Monitoring (AREA)
Abstract
本发明提供一种基于JVM的非阻塞分布式计划任务调度方法,在完全实现原有功能的基础上,解决线程数量占用过多、分布式下有节点空跑线程的问题。为实现上述目的,本发明采用如下技术方案:应用于配置有kotlin语言库及coroutines协程库的JVM虚拟机中,包括:分布式计划任务框架,还包括分布式协调组件;分布式协调组件中设置有通用非阻塞增删改查接口作为分布式协调组件所在客户端的非阻塞异步回调接口;通过协程封装分布式协调组件的非阻塞异步回调接口,并设置非阻塞循环监听器实现连续监听;最终基于封装后的协程非阻塞api重新实现一个本地节点缓存。本发明通过非阻塞改造,在执行任务的性能方面得以显著的提升。
Description
技术领域
本发明属于分布式任务调度技术领域,尤其是一种基于JVM的非阻塞分布式计划任务调度方法。
背景技术
计划任务是指有计划的定时运行或者周期性运行的程序,最常见的计划任务包括了基于Linux的 “crontab”以及基于Windows的 “计划任务程序”。但随着新建项目越来越大,系统越来越复杂,现有的计划任务就暴露出许多问题。 首先是高可用HA需求,当运行计划任务的服务器一旦出现故障,所有的计划任务将停止工作。 其次是性能问题,越来越多的大型计划任务程序出现,对CPU/IO密集操作,单个节点已经无法满足需求。因此就需要设计分布式计划任务,通过协调多台主机来执行大量的任务,解决高可用的问题。
这一问题在C语言环境下比较容易解决,例如专利号为CN201811615533.5的发明专利《数据同步的系统、方法、装置和计算机存储介质》中就公开了一种数据同步系统、方法、装置和计算机存储介质。所述系统包括数据监听模块、消息分发模块、数据同步模块、以及配置管理模块;数据监听模块,用于对数据源进行监听以获取数据源的数据变更记录;消息分发模块,用于创建与数据源对应的消息队列,将数据变更记录加入到消息队列中;配置管理模块,用于管理数据源的同步配置参数;数据同步模块,用于获取同步配置参数,按照同步配置参数创建消费协程;由消费协程监听消息队列,按照同步配置参数将数据变更记录更新到目标存储中。这样的技术方案实现于诸如Smalltalk、C++等编程语言中,但是无法实现于java环境下。
在java环境下,现有技术中通常采用XXL-JOB分布式任务调度系统来实现分布式计划任务。例如专利申请号为202111626396 .7的发明专利申请《基于XXL-JOB分布式任务调度系统的路由策略》中就公开了及一种基于XXL-JOB分布式任务调度系统的路由策略,包括以下步骤:各执行器自身根据任务流控规则判断当前是否可参与新任务;可执行新任务的执行器与调度中心之间确认系统标识,调度中心选定其中一台执行器为任务调度机器;任务调度机器接收任务参数并执行;优势在于:基于XXL-JOB分布式任务调度平台第10种分片广播路由策略,通过广播模式广播任务,所有任务流控规则验证通过的执行器机器均会尝试拉取任务,根据业务逻辑最终仅有一台机器能完整的完成系统标记身份确认以及任务调度确认,降低了出现“抢占式”业务执行逻辑时的负载风险。由此可见,为了解决线程资源浪费的问题,现有技术把所有的任务放在任务调度中心,调度中心连接关系型数据库,所有的数据放数据库里面,靠数据库锁控制并发、保证调度的一致性。这种方案一定程度上缓解了线程资源消耗过大这个问题,任务调度中心使用数量有限的线程做所有任务的调度,主体逻辑放在一个无限循环里面,任务的触发分发给工作节点,工作节点只需要一个任务一个线程持续运行就好,不需要更多的监听线程。但中心化任务调度中心也带来了性能有限、可扩展性降低、弹性调度变弱的缺点,并且在工作节点也是需要执行线程长期驻留的,属于阻塞式代码。
线程是比较重的资源,阻塞式代码阻塞了线程,让线程停止运行,并在一个地方长时间等待,资源消耗多、性能浪费。但这就是目前java环境下大多数代码的编程方式。目前java下实现非阻塞只有回调式写法,如Project Reactor、Vert.x,非阻塞回调式写法代码要求高,实现非常困难,难以编码、调试、学习、排错、代码阅读,这也导致了目前java环境下略微复杂功能的非阻塞代码无法实现。
发明内容
为克服现有技术的不足及存在的问题,本发明提供一种基于JVM的非阻塞分布式计划任务调度方法,以非阻塞的kotlin coroutine协程技术替代java多线程技术,对现有的分布式计划任务框架elastic-job重新实现,在完全实现原有功能的基础上,解决线程数量占用过多、分布式下有节点空跑线程的问题。
为实现上述目的,本发明创造采用如下技术方案:
一种基于JVM的非阻塞分布式计划任务调度方法,应用于配置有kotlin语言库及coroutines协程库的JVM虚拟机中,包括:分布式计划任务框架,所述分布式计划任务框架内包含:
并发任务执行器:调用应用逻辑实际执行的包;
定时任务调度器:进行任务的包括配置、启动、暂停、停止、删除的生命周期操作;
还包括分布式协调组件;分布式协调组件中设置有通用非阻塞增删改查接口作为分布式协调组件所在客户端的非阻塞异步回调接口;
coroutines协程库对接分布式协调组件的客户端的非阻塞异步回调接口;
通过协程封装分布式协调组件的非阻塞异步回调接口,并设置非阻塞循环监听器实现连续监听;最终基于封装后的协程非阻塞api重新实现一个本地节点缓存;
定时任务调度器在新的计划任务实例启动时,向分布式协调组件注册自己的任务实例信息、并获取已有的当前任务其他所有实例信息,通过选主操作确定当前的任务主节点;非阻塞异步回调接口创建任务时设置分片,主节点按任务实例ip信息,采用平均、轮询策略分配分片执行节点信息,写入分布式协调组件;
还包括java客户端,通过java客户端执行任务。
优选的,所述分布式协调组件为ZooKeeper组件或Etcd组件。通常采用ZooKeeper组件作为分布式协调组件,但是当ZooKeeper组件无法启动或被禁用时采用Etcd组件作为分布式协调组件。
优选的,当分布式协调组件为ZooKeeper组件时采用二级动态封装;其中,第一级封装调用suspendCancellableCoroutine方法,回调方法入参产生CancellableContinuation 实例;利用该实例进行第二级封装;;第二级封装中各个需要转换协程的ZooKeeper方法生成对应的AsyncCallback子接口的匿名内部类,所述匿名内部类内部调用传入的CancellableContinuation 实例的resume、resumeWithException方法来和协程对接。这样把ZooKeeper原本的异步回调方法转化为协程方法。优选的,所述封装过程中对ZooKeeper节点树添加增删改查操作,并在同时执行的操作数量大于2的时候,通过协程封装的形式调用异步事务提交接口。
优选的,封装了一个包装类循环监听器LoopWatcher,每次收到事件后,再自身监听器自动添加到ZooKeeper组件,然后再执行包装类循环监听器的逻辑,从而实现连续监听。基于ZooKeeper的原生节点watcher监听一次就被移除掉了,不能连续监听。因此本发明就建立了一个包装类循环监听器LoopWatcher,每次收到事件后再自动添加上去,然后执行包装watcher的逻辑。
优选的,还实现了非阻塞本地缓存及缓存监听,封装后的协程非阻塞api重新实现一个ZooKeeper本地节点缓存,在本发明中将其命名为ZooKeeperCacheImpl;具体实现使用散列表HashMap持有数据,非阻塞锁控制并发,LoopWatcher循环监听器实时监听远程ZooKeeper服务端节点子树变动的状态。
进一步的,在无法使用ZooKeeper组件的情况下,使用etcd组件作为分布式协调组件;具体通过抽出接口分离注册协调服务包;把所有和ZooKeeper组件相关的代码抽出接口,ZooKeeper组件只和该接口对接,reactive-job的实现也只和该接口对接,全局搜索不出现任何和ZooKeeper相关的字眼,然后把所述抽出接口和ZooKeeper组件实现代码分成两个jar包;新建一个etcd jar包,对接口包的所有用到的接口方法、数据结构做实现。整个过程只依赖接口jar包,不依赖ZooKeeper组件实现jar包。
优选的,使用etcd组件作为分布式协调组件时,通过添加一个job根目录前缀监听,然后对接接口分发所有远程数据变动事件来模拟实现缓存监听器。
优选的,所述并发任务执行器调用应用逻辑实际执行的包,使用协程的async +awaitAll方法结构化并发实现,相当于多线程下线程池提交submit多个任务,获取异步执行列表List<Future>,然后遍历等待所有异步执行future结束。
优选的,定时任务调度逻辑放在定时任务列表AsyncJobScheduler,核心逻辑是在协程里实现无限循环,使用Quartz框架里面的库CronExpression计算CRON表达式,获取下一个执行时间,延迟与当前时间的间隔,然后调用这个任务的并发任务执行器;并发任务执行器收到任务调度,会判断当前节点是不是这个任务当前分片的执行节点,如果是的话就执行这个任务,否的话略过。
本发明在配置有kotlin语言库及coroutines协程库的JVM虚拟机中实现,最终创建形成一个类库(Class Library),可以直接配置在各种java环境中,也可以单独打包成具体模块,比如作为微服务的计划任务模块,统一管理整个微服务系统的所有需要给定时间重复执行的任务。
但是无论在那种情况下,即便在纯java环境下也能达到预期的有益效果。本发明和现有技术相比带来的有益的效果非常明显。例如申请号 201510561242 .2的发明专利《用于基于ZooKeeper实现分布式调度的方法和装置》中的技术方案在实际业务上比较接近,该技术方案以ZooKeeper组件做协调组件、quartz做定时任务调度,但是该技术方案采用的还是阻塞式的方案。
两者相比较可见:
1、CPU占用率下降,现有技术中满负荷的CPU占用率接近99%。而在采用本发明的技术方案后同等负荷下CPU占用率下降到23%。
2、内存占用率下降,现有技术中运行时堆内存逐渐扩容到1.7G,运行稳定后每5秒在0.4G到1.2G震荡;而在采用本发明的技术方案后同等负荷下内存稳定后堆内存大约210MB左右,实际使用内存100MB-200MB震荡。
3、线程数大幅度下降,同时同步任务效率提升。以同时进行1000项任务为例,现有技术中在应用过程中线程不断增长,工作5分钟左右后增长到2000个线程,内存第一次扩容,增速明显变慢,直到第18分钟线程数量稳定,稳定后线程数量为3000左右,大致上是1个任务拖3个线程。但是其中有大量的线程在空跑。而采用本发明所述技术方案后整个应用过程中稳定在20个线程左右,启动1000个任务在10秒内完成。
由此可见本发明通过非阻塞改造,在执行任务的性能方面得以显著的提升。
附图说明
图1是本发明基于JVM的非阻塞分布式计划任务调度方法的整体系统结构图;
图2是本发明基于JVM的非阻塞分布式计划任务调度方法的ZooKeeper组件协程封装流程示意图;
图3是本发明基于JVM的非阻塞分布式计划任务调度方法的ZooKeeper组件协程时序图;
图4是本发明基于JVM的非阻塞分布式计划任务调度方法的ZooKeeper封装完毕后模块示意图;
图5是本发明基于JVM的非阻塞分布式计划任务调度方法的etcd组件封装完毕后模块示意图;
图6是本发明基于JVM的非阻塞分布式计划任务调度方法的设备部署图;
图7是本发明基于JVM的非阻塞分布式计划任务调度方法的实施例1中采用现有技术方案性能示意图;
图8是本发明基于JVM的非阻塞分布式计划任务调度方法的实施例1中采用本发明技术方案的性能示意图;
图9是本发明基于JVM的非阻塞分布式计划任务调度方法的实施例2中采用现有技术方案性能示意图;
图10是本发明基于JVM的非阻塞分布式计划任务调度方法的实施例2中采用本发明技术方案的性能示意图。
具体实施方式
为了便于本领域技术人员的理解,下面结合附图和具体实施例对本发明创造作进一步描述。
首先需要再次强调:如背景技术所述,目前java环境下没有协程,没办法做到执行到某句代码后,线程跳到另外的逻辑,等一定时间再回来执行下一句代码;只能通过lambda表达式的方式写回调,把接下来的逻辑放到回调里面,结束当前的方法后线程才能执行其他逻辑。也就是说如果单纯想要实现非阻塞式的语句,智能采用回调式写法。java环境下当遇到分支判断、循环、异常处理等逻辑时,通常是封装框架,采用Project Reactor、Vert.x、Rxjava,利用大量的方法操作符来处理这些逻辑。而一旦采用了回调式写法,就无法处理较为复杂的逻辑问题。
这里以Project Reactor为例。现有技术中如果需要实现非阻塞式结构,确实可以用just、defer等方法创建,用map、flatMap等方法转换,用doOnNext、doOnSubscribe等方法监听,用filter、ignoreElements等方法过滤,用error、onErrorReturn等方法异常处理,用timed、delaySubscription等方法处理时间相关问题。但是这样的结果是完全用一堆自创的方法来替代普通代码中的if、for、try、catch、finally、sleep等代码执行逻辑。并且以上操作符方法大部分情况下每次只能接收单个回调,处理后返回的也是单个的回调,造成认知上的困难,大量的回调也会使复杂的业务逻辑形成回调地狱。这样使得非阻塞回调式写法代码要求非常高,实际对于略微复杂的功能需求,业务逻辑就基本无法实现。即便勉强写出代码,也难以编码、调试、学习;写出来的代码在后期也难以理解、维护、排错。
正是这个问题导致了目前java下非阻塞代码基本不具有可行性。而本发明虽然可以在现有的JVM环境下实现非阻塞代码,但是只能基于kotlin语言库及coroutines协程库的环境。JVM(Java Virtual Machine)是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。引入java语言虚拟机后,java语言在不同平台上运行时不需要重新编译。
现有技术中JVM下的协程库有kotlin-coroutines、Quasar、Loom等,但是只有使用kotlin语言的coroutines维护积极,容易和java下异步库如project reactor对接,对其他业务代码逻辑无额外影响。而别的协程库,例如Quasar之类的java协程库采用了javaagent修改字节码的方式、修改已有java代码的逻辑来走协程,对其他业务逻辑也有影响,总体实现不够成熟,维护不够活跃,依然是无法实现,而openjdk loom项目至今最新的jdk版本18还未能发正式版,显然不能在生产应用。总体来讲kotlin-coroutines是目前JVM上唯一足够成熟的、能上生产的协程库,所以最终选kotlin-coroutine做为实现非阻塞的协程库。
如图1 已有的组件:JVM虚拟机;kotlin及协程库;分布式协调组件:ZooKeeper或etcd,及其java客户端;计划任务框架本身,基于开源框架elastic-job,基于kotlin协程重新实现其全部逻辑,包括:ZooKeeper非阻塞调用;etcd非阻塞调用;并发任务执行器;定时任务调度器。
如图2所示为 ZooKeeper非阻塞调用的具体流程:
ZooKeeper协程客户端自身的内部实现是异步非阻塞的,并且提供完全的回调式异步api,类似原生netty,每个异步回调式api也都有对应同步阻塞封装实现。原生ZooKeeper组件仅提供了最简陋的数据存储api,传统实现分布式调度解决方案elastic-job。elastic-job使用了开源组件curator来调用ZooKeeper组件,curator是ZooKeeper组件业务逻辑层面的封装,更易用,但调用的全是同步阻塞api,curator内部也是使用线程池来处理异步、并行、监听,而elastic-job的分布式协调大量依赖这个监听器、就造成了每个任务在每个服务器节点至少需要一个线程进行远程监听。
基于这个问题,我们进行了改造。添加了用于对接原生ZooKeeper客户端的非阻塞异步回调接口。由于ZooKeeper组件提供的接口以及api全部是使用回调来实现非阻塞的,这里使用协程简化调用就需要二者进行对接。而ZooKeeper组件异步api的回调全是空接口AsyncCallback的子接口,不同的回调入参类型、个数,就有不同的、不兼容的接口方法,并且同一个回调接口可以被不同的外部方法调用,这些就造成了对接上面的种种困难。这里采用二级封装来解决这个问题。如图3所示第一级封装方法内部全部调用suspendCancellableCoroutine方法,使用 suspendCancellableCoroutine可以将回调函数转换为协程,并且该协程的取消是可控的。其回调入参CancellableContinuation 实例,在这个方法的内部回调里面调用第二级生成的的AsyncCallback的子接口的匿名内部类;第二级各个需要转换协程的ZooKeeper方法生成对应的AsyncCallback的子接口的匿名内部类,这个实现内部调用传入的CancellableContinuation 实例的resume、resumeWithException方法来和协程对接。正常的话直接return结果,异常的话进行异常结束,并throw Exception。具体实现在ZooKeeperAsyncClient类,通过协程封装使回调式异步api,外部表现逻辑与ZooKeeper自身的同步api基本上保持一致,方便后续调用。如图4所示为按以上方式对接的封装完毕后的模块。包括ZooKeeper节点的异步回调增删改查、异步事务,封装效果是协程增删改查、协程事务/批处理,在协程状态下和原阻塞式代码的调用方式几乎一致,减少后续改动量;基于这些协程封装好的方法,实现计划任务对协调组件调用的通用接口、状态监控watcher、本地同步节点缓存、非阻塞分布式锁。
采用该模块进行非阻塞事务时直接调用原ZooKeeper的事务组装方法,添加一条条增删改查操作,只是执行的时候,通过协程封装的形式调用异步事务提交接口,就变成了在协程下可以调用的事务,用来做批处理。
协调组件通用非阻塞增删改查接口。任务框架里面具体的任务状态增删改查逻辑调用的是CoordinatorRegistryCenter接口,用于计划任务的逻辑对接调用协调组件的逻辑,先用kotlin重写,再用协程重新实现,调用协程封装的ZooKeeperAsyncClient。
在这个过程中需要应用到非阻塞循环监听器,原生ZooKeeper组件中的 watcher自身是异步非阻塞的,但ZooKeeper原生节点watcher监听一次就被移除掉了,不能连续监听。为了解决这个问题,封装了一个包装类LoopWatcher,每次收到事件后,在自身监听器自动添加到ZooKeeper,然后再执行包装监听器的逻辑,这样LoopWatcher自身就能实现连续监听。
非阻塞本地缓存及缓存监听。传统的分布式协调实现是基于curator框架的CuratorCacheListener接口的,CuratorCacheListener是分布式调度的核心接口,用于监听整个分布式计划任务集群各个时间点的状态变动,由分布式任务的各个业务实现接口,来管理自身的任务启停。而现在非阻塞改造不能使用阻塞式的curator,并且ZooKeeper自身也没有本地缓存的实现。为了解决这个问题,这里基于封装后的协程非阻塞api重新实现一个ZooKeeper本地节点缓存,并将其命名为ZooKeeperCacheImpl。具体实现使用HashMap持有数据,非阻塞锁控制并发,LoopWatcher监听器实时监听远程ZooKeeper服务端节点子树变动的状态,有新的数据变动,根据远程变动在本地内存实现相应变动逻辑,对应的事件改本地缓存状态,然后发送事件给基于协程的监听器接口CacheListener。
模块内还包括非阻塞的分布式锁,实现参考了ZooKeeper官方开源的阻塞式分布式锁实现ZooKeeper-recipes-lock,用kotlin重新实现,里面所有调用ZooKeeper阻塞式的api的方法改为上述协程封装的非阻塞调用,对应类WriteLock。
如图5所示,在无法使用ZooKeeper的情况下,这里提供备用方案,使用etcd组件作为分布式协调组件。Etcd是由golang实现的开源组件,提供和ZooKeeper组件相似的功能,只是api的完善程度、功能丰富度不及ZooKeeper组件。具体实现如下。
抽出接口,分离注册协调服务包。把所有与zookeepe组件相关的代码抽出接口,ZooKeeper只和这个接口对接,reactive-job的实现也只和接口对接,全局搜索不出现任何和ZooKeeper相关的字眼,然后把接口和ZooKeeper实现代码分成两个jar包,reactive-job逻辑只依赖接口jar包,不依赖ZooKeeper实现jar包。新建一个etcd jar包,对接口包的所有用到的接口方法、数据结构做实现。
实现类似ZooKeeper组件的子节点树。Etcd组件没有树形结构,是类似redis的键值对kv结构,沿用ZooKeeper组件用符号`/`对键做层级分隔,模拟树形结构。普通节点的增删改查操作,直接调用Etcd客户端提供的返回CompletableFuture的接口,然后使用kotlinx-coroutines-jdk8包提供的CompletableFuture.await方法对接,不需要像ZooKeeper组件那样复杂的非通用异步回调封装。
etcd组件没有ZooKeeper组件概念下的临时节点,也就是客户端断开连接就删掉的节点,而reactive-job几乎所有逻辑就是建立在这个机制之上的;这里采用etcd组件的租约机制lease加客户端不断续期做替代方案,一个客户端实例关联一个租约leaseId,关闭后3秒内lease失效,管理lease的key也自动删除。
reactive-job所有逻辑基于ZooKeeper组件的本地缓存监听器,etcd组件这里就不再这么麻烦了,缓存需要维护大量的本地状态;所有涉及缓存全部走远程,只添加一个job根目录前缀监听,然后对接接口分发所有远程数据变动事件。
etcd组件自带非阻塞分布式锁,不用像ZooKeeper组件需要自己做实现;分布式锁调用,传入的是path路径,返回的是锁键lockKey,两个是不一样的东西,释放锁需要调用unlock锁键lockKey。
无论选用哪个组件作为分布式协调组件,本发明所记载的方案还是需要并发任务执行器来调用应用逻辑实际执行的包。
并发任务执行器配置在reactive-job-executor-kernel包,是调用应用逻辑实际执行的包。原版是多线程实现的,在任务有分片的情况下还会新开线程池,进一步增加线程资源消耗。而在本发明中使用协程的async + awaitAll方法结构化并发实现,相当于多线程下线程池submit几个任务,获取List<Future>,然后遍历等待所有future结束。
任务执行接口目前以java SPI的形式提供,方便后续的扩展,内部默认实现了SimpleJob、javaMonoJob、javaBlockJob。其中SimpleJob直接使用kotlin协程接口,在执行器里面直接运行。考虑到当前的计划任务框架是java下的,需要对java做兼容适配,提供了两种java接口。其中javaMonoJob接口采用Mono返回值,以异步回调转协程的方式执行。另外的javaBlockJob是额外考虑到java下的异步回调代码实在是容易引发回调地狱,并且绝大部分java代码逻辑都是阻塞式的,就提供了阻塞式接口,采用了实际执行任务时在线程池内执行的逻辑。相比原来的每个任务靠线程常驻空跑来实现调度要节约不少线程资源,但依旧不推荐这种用法,尽量使用前面两种。
此时可以采用单元测试框架进行测试。单元测试框架使用junit,多线程并发原版使用的是开源框架Mockito来模拟接口返回进行单元测试,但Mockito不支持kotlin的空值安全、协程,直接使用的话会报各种错误。这里采用开源框架MockK,基本算是kotlin下Mockito的1:1实现,全面支持Kotlin,支持空值安全、协程测试。
最后运行时需要依靠定时任务调度器,本发明中定时任务调度器采用了基于协程的非阻塞实现。新的计划任务实例启动时,会向协调组件注册自己的任务实例信息、并获取已有的当前任务其他所有实例的信息,通过选主操作确定当前的任务主节点;任务提供设置分片的方式,默认为1个分片,主节点按任务实例ip等信息,平均、轮询等策略分配分片的执行节点信息,写入分布式协调组件;定时任务调度逻辑放在AsyncJobScheduler,核心逻辑是在协程里面的一个无限循环,使用hutool-cron工具包解析CRON表达式,获取下一个执行时间,delay与当前时间的间隔,然后调用这个任务的执行器;执行器收到任务调度,会判断当前节点是不是这个任务当前分片的执行节点,如果是的话就执行这个任务,否的话略过;针对调度器的控制,pause、shutdown直接杀掉这个协程,resume直接调用协程的start开始运行。
定时任务调度器里面的任务分发监听器用到了java.lang.Object的wait、notifyAll语义,而kotlin-coroutine没有类似语义的实现,这里就造了一个稍显复杂的工具类ObjectWaitAsync,实现类似的语义。具体实现方式如下:持有一个锁mutex,持有一个CompletableDeferred列表,每调用一次waitAsync(),就新增一个CompletableDeferred,然后让这个CompletableDeferred.await;notifyAllAsync让列表里面的CompletableDeferred全部调用complete,然后清空list;所有操作使用非阻塞锁mutex限制并发。
至于定时任务调度器的其他逻辑同elastic-job,只不过是用kotlin-coroutine重写了非阻塞实现,包括任务的配置、启动、暂停、停止、删除等生命周期操作,向分布式协调组件发布自身信息、监听任务集群中所有节点的状态信息等分布式特性实现,包括高可用性、弹性伸缩、故障转移等。
如图7-图10所示,我们采用硬件为4核8线程24g内存的电脑,应用为1000个任务,每个计划任务每5s打印一次任务名为具体实施方式,下面展示二者的性能对比。
传统方式elastic-job,堆内存逐渐扩容到1.7G后稳定,实际内存使用每5秒在0.4G到1.2G左右震荡;5分钟左右时增长到2000个线程,大概也就是内存第一次扩容,增速明显变慢,18分钟时稳定,稳定后线程数量为3014,大致上是1个任务需要消耗3个线程;
而本发明在数秒内即可实现稳定,稳定后,堆内存210MB左右,内存实际使用在100MB到200MB附近震荡;稳定后占用20个左右的线程数量。
需要注意的是在图7和图8下方显示的线程数实际是同等条件下运行的设备上的总线程数量。可以看到本发明和现有技术实际的线程数量相差3000多,符合我们的预期。
实施例2:执行具有一定执行时长的计划任务。任务执行间隔1分钟,单个任务http调用远程rest服务,Rest服务端接口执行时长为30秒,并行执行10个任务,测试结果任务能正常执行,线程总数量依旧是20个左右。
需要说明的是,在本文中,诸如第一和第二等之类的关系术语仅仅用来将一个实体或者操作与另一个实体或操作区分开来,而不一定要求或者暗示这些实体或操作之间存在任何这种实际的关系或者顺序。而且,术语“包括”、“包含”或者其任何其他变体意在涵盖非排他性的包含,从而使得包括一系列要素的过程、方法、物品或者设备不仅包括那些要素,而且还包括没有明确列出的其他要素,或者是还包括为这种过程、方法、物品或者设备所固有的要素。在没有更多限制的情况下,由语句“包括一个……”限定的要素,并不排除在包括所述要素的过程、方法、物品或者设备中还存在另外的相同要素。
以上实施例仅用以说明本公开的技术方案,而非对其限制;尽管参照前述实施例对本公开进行了详细的说明,本领域的普通技术人员应当理解:其依然可以对前述各实施例所记载的技术方案进行修改,或者对其中部分技术特征进行等同替换;而这些修改或者替换,并不使相应技术方案的本质脱离本公开各实施例技术方案的精神和范围。
Claims (6)
1.一种基于JVM的非阻塞分布式计划任务调度方法,应用于配置有kotlin语言库及coroutines协程库的JVM虚拟机中,其特征在于,包括:分布式计划任务框架,所述分布式计划任务框架内包含:
并发任务执行器:调用应用逻辑实际执行的包;
定时任务调度器:进行任务的包括配置、启动、暂停、停止、删除的生命周期操作;
还包括分布式协调组件;分布式协调组件中设置有通用非阻塞增删改查接口作为分布式协调组件所在客户端的非阻塞异步回调接口;
coroutines协程库对接分布式协调组件的客户端的非阻塞异步回调接口;
通过协程封装分布式协调组件的非阻塞异步回调接口,并设置非阻塞循环监听器实现连续监听;最终基于封装后的协程非阻塞api重新实现一个本地节点缓存;定时任务调度器在新的计划任务实例启动时,向分布式协调组件注册自己的任务实例信息、并获取已有的当前任务其他所有实例信息,通过选主操作确定当前的任务主节点;非阻塞异步回调接口创建任务时设置分片,主节点按任务实例ip信息,采用平均、轮询策略分配分片执行节点信息,写入分布式协调组件;
还包括java客户端,通过java客户端执行任务;当分布式协调组件为ZooKeeper组件时采用二级动态封装;其中,第一级封装调用suspendCancellableCoroutine方法,回调方法入参产生CancellableContinuation实例;利用该实例进行第二级封装;第二级封装中各个需要转换协程的ZooKeeper方法生成对应的AsyncCallback子接口的匿名内部类,所述匿名内部类通过内部调用传入的CancellableContinuation实例的resume、resumeWithException方法来和协程对接。
2.根据权利要求1所述的一种基于JVM的非阻塞分布式计划任务调度方法,其特征在于,封装过程中对ZooKeeper节点树添加增删改查操作,并在同时执行的操作数量大于2的时候,通过协程封装的形式调用异步事务提交接口。
3.根据权利要求1所述的一种基于JVM的非阻塞分布式计划任务调度方法,其特征在于,封装了一个包装类循环监听器,每次收到事件后,将自身监听器自动添加到ZooKeeper组件,然后再执行包装类循环监听器的逻辑,从而实现连续监听。
4.根据权利要求3所述的一种基于JVM的非阻塞分布式计划任务调度方法,其特征在于,还实现了非阻塞本地缓存及缓存监听,封装后的协程非阻塞api重新实现一个ZooKeeper本地节点缓存;
具体实现方式为:使用散列表持有数据,非阻塞锁控制并发,循环监听器实时监听远程ZooKeeper服务端节点子树变动的状态。
5.根据权利要求1所述的一种基于JVM的非阻塞分布式计划任务调度方法,其特征在于,所述并发任务执行器调用应用逻辑实际执行的包,使用协程的async+awaitAll方法结构化并发实现。
6.根据权利要求1所述的一种基于JVM的非阻塞分布式计划任务调度方法,其特征在于,定时任务调度逻辑放在定时任务列表,核心逻辑是在协程里实现无限循环,进行定时计算,获取下一个执行时间,延迟与当前时间的间隔,然后调用这个任务的并发任务执行器;并发任务执行器收到任务调度,判断当前节点是不是这个任务当前分片的执行节点,如果是就执行这个任务,否则略过。
Priority Applications (2)
Application Number | Priority Date | Filing Date | Title |
---|---|---|---|
CN202211139267.XA CN116089027A (zh) | 2022-06-14 | 2022-06-14 | 一种基于jvm的非阻塞分布式计划任务调度方法 |
CN202210668844.8A CN114756357B (zh) | 2022-06-14 | 2022-06-14 | 一种基于jvm的非阻塞分布式计划任务调度方法 |
Applications Claiming Priority (1)
Application Number | Priority Date | Filing Date | Title |
---|---|---|---|
CN202210668844.8A CN114756357B (zh) | 2022-06-14 | 2022-06-14 | 一种基于jvm的非阻塞分布式计划任务调度方法 |
Related Child Applications (1)
Application Number | Title | Priority Date | Filing Date |
---|---|---|---|
CN202211139267.XA Division CN116089027A (zh) | 2022-06-14 | 2022-06-14 | 一种基于jvm的非阻塞分布式计划任务调度方法 |
Publications (2)
Publication Number | Publication Date |
---|---|
CN114756357A CN114756357A (zh) | 2022-07-15 |
CN114756357B true CN114756357B (zh) | 2022-10-14 |
Family
ID=82336514
Family Applications (2)
Application Number | Title | Priority Date | Filing Date |
---|---|---|---|
CN202210668844.8A Active CN114756357B (zh) | 2022-06-14 | 2022-06-14 | 一种基于jvm的非阻塞分布式计划任务调度方法 |
CN202211139267.XA Pending CN116089027A (zh) | 2022-06-14 | 2022-06-14 | 一种基于jvm的非阻塞分布式计划任务调度方法 |
Family Applications After (1)
Application Number | Title | Priority Date | Filing Date |
---|---|---|---|
CN202211139267.XA Pending CN116089027A (zh) | 2022-06-14 | 2022-06-14 | 一种基于jvm的非阻塞分布式计划任务调度方法 |
Country Status (1)
Country | Link |
---|---|
CN (2) | CN114756357B (zh) |
Families Citing this family (2)
Publication number | Priority date | Publication date | Assignee | Title |
---|---|---|---|---|
CN115292025A (zh) * | 2022-09-30 | 2022-11-04 | 神州数码融信云技术服务有限公司 | 任务调度方法及装置、计算机设备及计算机可读存储介质 |
CN116257327B (zh) * | 2023-05-15 | 2023-09-15 | 浙江保融科技股份有限公司 | 一种在jvm非阻塞系统中调用阻塞式客户端库的方法 |
Citations (1)
Publication number | Priority date | Publication date | Assignee | Title |
---|---|---|---|---|
CN110177118A (zh) * | 2019-06-13 | 2019-08-27 | 上海海事大学 | 一种基于rdma的rpc通信方法 |
Family Cites Families (13)
Publication number | Priority date | Publication date | Assignee | Title |
---|---|---|---|---|
US7349958B2 (en) * | 2003-06-25 | 2008-03-25 | International Business Machines Corporation | Method for improving performance in a computer storage system by regulating resource requests from clients |
WO2014008495A2 (en) * | 2012-07-06 | 2014-01-09 | Cornell University | Managing dependencies between operations in a distributed system |
US9417918B2 (en) * | 2013-11-20 | 2016-08-16 | International Business Machines Corporation | Computing session workload scheduling and management of parent-child tasks |
CN104142858B (zh) * | 2013-11-29 | 2016-09-28 | 腾讯科技(深圳)有限公司 | 阻塞任务调度方法及装置 |
CN106980546B (zh) * | 2016-01-18 | 2021-08-27 | 阿里巴巴集团控股有限公司 | 一种任务异步执行方法、装置及系统 |
CN106131138B (zh) * | 2016-06-27 | 2019-06-04 | 浪潮软件股份有限公司 | 一种基于非阻塞队列的展示数据实时推送系统和方法 |
CN106850829B (zh) * | 2017-02-28 | 2019-11-22 | 苏州星熙数据科技有限公司 | 一种基于非阻塞通信的微服务系统设计方法 |
WO2018187160A1 (en) * | 2017-04-07 | 2018-10-11 | Satori Worldwide, Llc | Distributed scalable workload testing |
US11650862B2 (en) * | 2018-07-31 | 2023-05-16 | Parallel Wireless, Inc. | Service bus for telecom infrastructure |
US20200195718A1 (en) * | 2018-12-12 | 2020-06-18 | International Business Machines Corporation | Workflow coordination in coordination namespace |
US11635995B2 (en) * | 2019-07-16 | 2023-04-25 | Cisco Technology, Inc. | Systems and methods for orchestrating microservice containers interconnected via a service mesh in a multi-cloud environment based on a reinforcement learning policy |
CN111277672B (zh) * | 2020-03-31 | 2022-03-11 | 上海积成能源科技有限公司 | 一种基于非阻塞输入输出模型的能源物联网数据采集方法 |
CN112162840B (zh) * | 2020-09-29 | 2024-03-08 | 曹蕤 | 一种基于中断重入机制的协程处理及管理方法 |
-
2022
- 2022-06-14 CN CN202210668844.8A patent/CN114756357B/zh active Active
- 2022-06-14 CN CN202211139267.XA patent/CN116089027A/zh active Pending
Patent Citations (1)
Publication number | Priority date | Publication date | Assignee | Title |
---|---|---|---|---|
CN110177118A (zh) * | 2019-06-13 | 2019-08-27 | 上海海事大学 | 一种基于rdma的rpc通信方法 |
Non-Patent Citations (2)
Title |
---|
Java语言中非阻塞算法的实现;朱;《电脑知识与技术》;20150715(第20期);全文 * |
如何用Java回调和线程实现异步调用;钱宇虹;《软件工程师》;20131015(第10期);全文 * |
Also Published As
Publication number | Publication date |
---|---|
CN116089027A (zh) | 2023-05-09 |
CN114756357A (zh) | 2022-07-15 |
Similar Documents
Publication | Publication Date | Title |
---|---|---|
US7159211B2 (en) | Method for executing a sequential program in parallel with automatic fault tolerance | |
US7779298B2 (en) | Distributed job manager recovery | |
CN114756357B (zh) | 一种基于jvm的非阻塞分布式计划任务调度方法 | |
JP5258019B2 (ja) | アプリケーション・プロセス実行の範囲内での非決定論的オペレーションを管理、ロギング、またはリプレイするための予測方法 | |
Hofmeister et al. | A framework for dynamic reconfiguration of distributed programs | |
US8095823B2 (en) | Server computer component | |
US8788569B2 (en) | Server computer system running versions of an application simultaneously | |
Ghormley et al. | GLUix: a global layer unix for a network of workstations | |
JP5519909B2 (ja) | アプリケーション・プロセスにおいて内部イベントをリプレイするための非侵入的方法およびこの方法を実装するシステム | |
US9176772B2 (en) | Suspending and resuming of sessions | |
US8984534B2 (en) | Interfacing between a receiving component of a server application and a remote application | |
CA2904253C (en) | Computer system using in-service software upgrade | |
Germain et al. | Concurrency oriented programming in termite scheme | |
Evrard et al. | Automatic distributed code generation from formal models of asynchronous concurrent processes | |
Fortier et al. | Dyninka: a FaaS framework for distributed dataflow applications | |
Cooper | Pilgrim: A debugger for distributed systems | |
JP2000194631A (ja) | 情報処理システムのマネ―ジャと少なくとも1つのリソ―スとの間の通信エ―ジェント | |
CN113835904A (zh) | 一种远程过程调用控制方法、装置、设备及存储介质 | |
Kusek et al. | Mobile agent based software operation and maintenance | |
Wood et al. | Triton: a domain specific language for cyber-physical systems | |
Correia et al. | Practical database replication | |
Echternkamp | Distributed Pipe-and-Filter architectures with TeeTime | |
Blümke | Planning and Execution of System Adaptations in Cloud-Based Environments | |
JP3797862B2 (ja) | 推論装置、システム及び方法並びに推論用ソフトウェアを記録した記録媒体 | |
Medvidovic et al. | Improving dependability of component-based systems via multi-versioning connectors |
Legal Events
Date | Code | Title | Description |
---|---|---|---|
PB01 | Publication | ||
PB01 | Publication | ||
SE01 | Entry into force of request for substantive examination | ||
SE01 | Entry into force of request for substantive examination | ||
GR01 | Patent grant | ||
GR01 | Patent grant |