A non-blocking distributed scheduled task scheduling method based on JVM

By employing the Kotlin and coroutines libraries in a Java environment and encapsulating the non-blocking asynchronous callback interfaces of ZooKeeper or Etcd components, non-blocking programming for distributed task scheduling is achieved, solving the problems of thread resource waste and performance bottlenecks, and significantly improving task execution efficiency.

CN116089027BActive Publication Date: 2026-01-09ZHEJIANG FINGARD TECH CO LTD
View PDF 5 Cites 0 Cited by

Patent Information

Application Number
CN202211139267.X
Authority / Receiving Office
CN · China
Patent Type
Patents(China)
Current Assignee / Owner
Filing Date
2022-06-14
Publication Date
2026-01-09
Estimated Expiration
2042-06-14

AI Technical Summary

Technical Problem

In the Java environment, existing distributed task scheduling systems suffer from problems such as wasted thread resources, limited performance, reduced scalability, and blocking code, resulting in high CPU and memory usage and making it difficult to implement non-blocking programming.

Method used

A non-blocking distributed task scheduling method based on the Kotlin language and the coroutines library is adopted. By encapsulating the non-blocking asynchronous callback interface of ZooKeeper or Etcd components, the coroutine library is used to implement non-blocking listening and task scheduling of distributed coordination components, replacing the traditional multi-threading technology and optimizing task sharding and sharded execution.

Benefits of technology

It significantly reduced CPU and memory usage, decreased the number of threads, and improved task execution efficiency. CPU usage dropped from 99% to 23%, memory usage stabilized at around 210MB, and the number of threads decreased from 3000 to around 20.

✦ Generated by Eureka AI based on patent content.

Smart Images

  • Figure CN116089027B_ABST
    Figure CN116089027B_ABST
Patent Text Reader

Abstract

The application provides a non-blocking distributed scheduled task scheduling method based on JVM, which solves the problems of too many threads and idle threads in the distributed mode on the basis of realizing the original functions completely. In order to achieve the above-mentioned purpose, the application adopts the following technical scheme: a non-blocking distributed scheduled task scheduling method based on JVM is applied to a Jvm virtual machine configured with a Kotlin language library and a coroutines coroutine library, and comprises a distributed scheduled task framework and a distributed coordination component; a general non-blocking add-delete-update-retrieve interface is arranged in the distributed coordination component as a non-blocking asynchronous callback interface of a client where the distributed coordination component is located; the non-blocking asynchronous callback interface of the distributed coordination component is encapsulated through a coroutine, and a non-blocking loop listener is arranged to realize continuous listening; and finally, a local node cache is realized based on the encapsulated coroutine non-blocking API. The performance is significantly improved through the non-blocking modification.
Need to check novelty before this filing date? Find Prior Art

Description

TECHNICAL FIELD

[0001] The application belongs to the technical field of distributed task scheduling, and particularly relates to a non-blocking distributed scheduled task scheduling method based on JVM. BACKGROUND

[0002] A scheduled task refers to a program that is scheduled to run at a fixed time or periodically, and the most common scheduled tasks include a Linux-based "crontab" and a Windows-based "scheduled task program". However, as new projects become larger and larger and systems become more and more complex, the existing scheduled tasks expose many problems. First, the high-availability (HA) requirement, when the server running the scheduled task fails, all scheduled tasks will stop working. Second, the performance problem, more and more large scheduled task programs appear, and CPU / IO intensive operations cannot be met by a single node. Therefore, it is necessary to design a distributed scheduled task to execute a large number of tasks by coordinating multiple hosts to solve the problem of high availability.

[0003] This problem is relatively easy to solve in the C language environment, for example, the patent No. CN201811615533.5 discloses a data synchronization system, method, device and computer storage medium. The system comprises a data monitoring module, a message distribution module, a data synchronization module and a configuration management module; the data monitoring module is used to monitor the data source to obtain the data change record of the data source; the message distribution module is used to create a message queue corresponding to the data source, and add the data change record into the message queue; the configuration management module is used to manage the synchronization configuration parameters of the data source; the data synchronization module is used to obtain the synchronization configuration parameters, create a consumption coroutine according to the synchronization configuration parameters; the consumption coroutine listens to the message queue, and updates the data change record into the target storage according to the synchronization configuration parameters. Such a technical solution is implemented in programming languages such as Smalltalk, C++ and the like, but cannot be implemented in the java environment.

[0004] In the java environment, the prior art usually adopts the XXL-JOB distributed task scheduling system to realize the distributed scheduled task. For example, the patent application No. 202111626396.7 "Routing strategy based on XXL-JOB distributed task scheduling system" discloses a routing strategy based on XXL-JOB distributed task scheduling system, which includes the following steps: each executor itself judges whether it can participate in a new task according to the task flow control rule; the executor that can execute the new task confirms the system identifier between the executor and the scheduling center, and the scheduling center selects one of the executors as the task scheduling machine; the task scheduling machine receives the task parameters and executes; the advantage is that: based on the 10th shard broadcast routing strategy of XXL-JOB distributed task scheduling platform, the task is broadcasted through the broadcast mode, all executor machines that pass the task flow control rule verification will try to pull the task, and according to the business logic, only one machine can complete the system marker identity confirmation and task scheduling confirmation, reducing the load risk when the "preemptive" business execution logic appears. As can be seen, in order to solve the problem of thread resource waste, the prior art puts all tasks in the task scheduling center, the scheduling center connects a relational database, all data is placed in the database, and the concurrency is controlled and the consistency of scheduling is ensured by database lock. This scheme alleviates the problem of excessive consumption of thread resources to a certain extent, the task scheduling center uses a limited number of threads to schedule all tasks, the main logic is placed in an infinite loop, the task trigger is distributed to the worker node, and the worker node only needs one task and one thread to run continuously, without more listening threads. But the centralized task scheduling center also has the disadvantages of limited performance, reduced scalability, and weak elastic scheduling, and the worker node also needs to execute a thread for a long time, which is a blocking code.

[0005] Threads are relatively heavy resources, and blocking code blocks threads, stops running, and waits for a long time in one place, consuming more resources and wasting performance. But this is the programming way of most codes in the current java environment. Currently, the only callback method for non-blocking in java is, such as Project Reactor, Vert.x, which requires high code and is very difficult to implement, code, debug, learn, troubleshoot, and read, which also leads to the fact that non-blocking code with slightly complex functions cannot be implemented in the current java environment. SUMMARY

[0006] In order to overcome the deficiencies and problems of the prior art, the application provides a non-blocking distributed scheduled task scheduling method based on JVM, which replaces the java multithreading technology with the non-blocking kotlin coroutine technology, re-implements the existing distributed scheduled task framework elastic-job, and solves the problems of too many thread numbers and idle threads in the distributed nodes on the basis of realizing the original functions.

[0007] In order to achieve the above-mentioned purpose, the application adopts the following technical solutions:

[0008] A non-blocking distributed scheduled task scheduling method based on JVM is applied to a JVM virtual machine configured with a kotlin language library and a coroutines coroutine library, and includes a distributed scheduled task framework, wherein the distributed scheduled task framework contains:

[0009] A concurrent task executor: a package for calling application logic to actually execute;

[0010] A timing task scheduler: a life cycle operation including configuration, start, pause, stop and deletion of a task;

[0011] Further comprising a distributed coordination component; the distributed coordination component is provided with a general non-blocking add-delete-change-query interface as a non-blocking asynchronous callback interface of a client where the distributed coordination component is located;

[0012] The coroutines coroutine library interfaces with the non-blocking asynchronous callback interface of the client of the distributed coordination component;

[0013] The non-blocking asynchronous callback interface of the distributed coordination component is encapsulated by a coroutine, and a non-blocking loop listener is set to realize continuous listening; finally, a local node cache is re-implemented based on the encapsulated coroutine non-blocking API;

[0014] The timing task scheduler registers its task instance information to the distributed coordination component and obtains the existing current task other instance information when a new scheduled task instance is started, determines the current task master node through a master selection operation; the non-blocking asynchronous callback interface sets the shards when creating a task, and the master node distributes the shard execution node information according to the task instance IP information by using an average and polling strategy and writes into the distributed coordination component;

[0015] Further comprising a java client for executing a task through the java client.

[0016] Preferably, the distributed coordination component is a ZooKeeper component or an Etcd component. The ZooKeeper component is usually used as the distributed coordination component, but the Etcd component is used as the distributed coordination component when the ZooKeeper component cannot be started or is disabled.

[0017] Preferably, when the distributed coordination component is a ZooKeeper component, a two-level dynamic encapsulation is used; wherein the first-level encapsulation calls the suspendCancellableCoroutine method, and a callback method argument generates a CancellableContinuation instance; the second-level encapsulation is performed by using the instance; in the second-level encapsulation, each ZooKeeper method that needs to be converted into a coroutine generates an anonymous inner class of the AsyncCallback sub-interface, and the anonymous inner class internally calls the resume and resumeWithException methods of the CancellableContinuation instance passed in to interface with the coroutine. In this way, the original asynchronous callback method of ZooKeeper is converted into a coroutine method. Preferably, in the encapsulation process, add, delete, modify and query operations are added to the ZooKeeper node tree, and when the number of simultaneously executed operations is greater than 2, the asynchronous transaction submission interface is called in the form of coroutine encapsulation.

[0018] Preferably, a wrapper class loop listener LoopWatcher is encapsulated, and after receiving an event each time, the wrapper class loop listener is automatically added to the ZooKeeper component, and then the logic of the wrapper class loop listener is executed, so as to realize continuous listening. The original node watcher of ZooKeeper is removed after being listened to once, and cannot be continuously listened to. Therefore, the present application establishes a wrapper class loop listener LoopWatcher, which is automatically added each time an event is received, and then the logic of the wrapper watcher is executed.

[0019] Preferably, a non-blocking local cache and cache listening are also implemented, and the encapsulated coroutine non-blocking api re-implements a ZooKeeper local node cache, which is named ZooKeeperCacheImpl in the present application; the specific implementation uses a hash table HashMap to hold data, a non-blocking lock to control concurrency, and a LoopWatcher loop listener to real-time listen to the state of the remote ZooKeeper server node subtree change.

[0020] Further, in the case that the ZooKeeper component cannot be used, an etcd component is used as a distributed coordination component; specifically, a registration coordination service package is separated by extracting an interface; all codes related to the ZooKeeper component are extracted from the interface, the ZooKeeper component only interfaces with the interface, the implementation of the reactive-job also only interfaces with the interface, and the global search does not appear any words related to the ZooKeeper, then the extracted interface and the ZooKeeper component implementation code are divided into two jar packages; a new etcd jar package is created, and all interface methods and data structures used by the interface package are implemented. The whole process only depends on the interface jar package and does not depend on the ZooKeeper component implementation jar package.

[0021] Preferably, when the etcd component is used as the distributed coordination component, a job root directory prefix is added for listening, and then all remote data change events are distributed to the interface to simulate the implementation of the cache listener.

[0022] Preferably, the concurrent task executor calls the package actually executed by the application logic, uses the async+awaitAll method of the coroutine to structure the concurrent implementation, which is equivalent to submitting multiple tasks by the thread pool under the multi-threading, and obtaining an asynchronous execution list List <future>Then iterate over all the asynchronous execution futures waiting for their end.

[0023] Preferably, the timing task scheduling logic is placed in the timing task list AsyncJobScheduler, the core logic is to implement an infinite loop in a coroutine, calculate the next execution time using the library CronExpression in the Quartz framework, delay the interval from the current time, and then call the concurrent task executor of this task; the concurrent task executor receives the task scheduling, judges whether the current node is the execution node of the current shard of this task, and if so, executes the task, otherwise, skips.

[0024] The application is implemented in a JVM virtual machine configured with a kotlin language library and a coroutines coroutine library, finally creates a class library (Class Library), which can be directly configured in various java environments, and can also be packaged into a specific module, such as a scheduled task module of a micro service, uniformly manages all tasks that need to be repeatedly executed at a given time in the entire micro service system.

[0025] However, in either case, even in a pure java environment, the expected beneficial effect can be achieved. The application and the prior art have very obvious beneficial effects. For example, the technical solution in the application No. 201510561242.2 patent for a method and device for realizing distributed scheduling based on ZooKeeper is relatively close to the actual business, which uses the ZooKeeper component as a coordination component and quartz as a timing task scheduling, but the technical solution still adopts a blocking scheme.

[0026] Compared with the prior art, the application has the following advantages:

[0027] 1. CPU occupancy rate is reduced. In the prior art, the full-load CPU occupancy rate is close to 99%. After adopting the technical solution of the application, the CPU occupancy rate under the same load is reduced to 23%.

[0028] 2. Memory occupancy rate is reduced. In the prior art, the runtime heap memory gradually expands to 1.7G, and after running stably, it oscillates between 0.4G and 1.2G every 5 seconds. After adopting the technical solution of the application, the heap memory is about 210MB after running stably under the same load, and the actual memory usage oscillates between 100MB and 200MB.

[0029] 3. The number of threads is significantly reduced, while the efficiency of synchronous tasks is improved. Taking 1000 tasks running simultaneously as an example, in existing technologies, the number of threads continuously increases during the application process, reaching 2000 threads after about 5 minutes. The growth rate slows down significantly after the first memory expansion, until the number of threads stabilizes at around 3000 after 18 minutes, roughly one task supporting three threads. However, a large number of these threads are running idly. With the technical solution described in this invention, the number of threads remains stable at around 20 throughout the entire application process, and 1000 tasks can be started and completed within 10 seconds.

[0030] It is evident that this invention significantly improves task performance through non-blocking modifications. Attached Figure Description

[0031] Figure 1 This is the overall system architecture diagram of the non-blocking distributed scheduled task method based on JVM of this invention;

[0032] Figure 2 This is a schematic diagram of the ZooKeeper component coroutine encapsulation process of the non-blocking distributed scheduled task method based on JVM of this invention;

[0033] Figure 3 This is a sequence diagram of the ZooKeeper component coroutine of the non-blocking distributed scheduled task method based on JVM in this invention;

[0034] Figure 4 This is a schematic diagram of the ZooKeeper module after the non-blocking distributed scheduled task method based on JVM of this invention has been encapsulated.

[0035] Figure 5 This is a schematic diagram of the etcd component after the encapsulation of the non-blocking distributed scheduled task method based on JVM of this invention.

[0036] Figure 6 This is a device deployment diagram of the non-blocking distributed scheduled task method based on JVM according to the present invention;

[0037] Figure 7 This is a performance diagram of the existing technical solution used in Embodiment 1 of the non-blocking distributed scheduled task method based on JVM of the present invention;

[0038] Figure 8 This is a performance diagram of the technical solution of the present invention in Embodiment 1 of the non-blocking distributed scheduled task method based on JVM;

[0039] Figure 9 This is a performance diagram of the existing technical solution used in Embodiment 2 of the non-blocking distributed scheduled task method based on JVM of the present invention;

[0040] Figure 10 This is a performance diagram of the technical solution of the present invention in Embodiment 2 of the non-blocking distributed scheduled task method based on JVM. Detailed Implementation

[0041] To facilitate understanding by those skilled in the art, the present invention will be further described below with reference to the accompanying drawings and specific embodiments.

[0042] First, it's important to reiterate: as mentioned in the background, Java currently lacks coroutines. It's impossible for a thread to jump to another line of code after executing a certain line, then return to execute the next line after a certain time. The only way is to use lambda expressions to write callbacks, placing the subsequent logic inside the callback, allowing the thread to execute other logic only after the current method ends. In other words, if you simply want to achieve non-blocking statements, you can only use callbacks. In the Java environment, when encountering logic such as branching, loops, and exception handling, frameworks like Project Reactor, Vert.x, and RxJava are typically used, leveraging numerous method operators to handle this logic. However, once callbacks are used, they cannot handle more complex logic problems.

[0043] Here, we'll take Project Reactor as an example. In existing technologies, to achieve a non-blocking structure, we can indeed use methods like `just` and `defer` to create it, methods like `map` and `flatMap` to transform it, methods like `doOnNext` and `doOnSubscribe` to listen for changes, methods like `filter` and `ignoreElements` to filter, methods like `error` and `onErrorReturn` to handle exceptions, and methods like `timed` and `delaySubscription` to handle time-related issues. However, this results in completely replacing the execution logic of ordinary code like `if`, `for`, `try`, `catch`, `finally`, and `sleep` with a bunch of custom-created methods. Furthermore, most of these operators and methods can only receive a single callback at a time, and the returned callback is also a single callback, causing cognitive difficulties. A large number of callbacks can also create a callback hell for complex business logic. This makes non-blocking callback-based code extremely demanding, and in practice, for even slightly complex functionalities, the business logic becomes virtually impossible to implement. Even if the code is managed to be written, it's difficult to code, debug, and learn; the resulting code is also difficult to understand, maintain, and debug later on.

[0044] It is this problem that leads to the current java under non-blocking code basically not feasible. Although the present application can realize non-blocking code in the existing JVM environment, it can only be based on the environment of the kotlin language library and the coroutines coroutine library. JVM (Java Virtual Machine) is a specification for computing devices, which is a fictitious computer, and is realized by simulating various computer functions on actual computers. After introducing the java language virtual machine, the java language does not need to be recompiled when running on different platforms.

[0045] The coroutine library under the existing technology JVM has kotlin-coroutines, Quasar, Loom, etc., but only the coroutines of the kotlin language are maintained actively, which is easy to interface with the asynchronous library such as project reactor under java, and has no additional impact on other business code logic. And other coroutine libraries, such as java coroutine libraries like Quasar, use the way of modifying bytecode through javaagent, modifying the logic of existing java code to run coroutines, which also affects other business logic. The overall implementation is not mature enough, the maintenance is not active enough, and it is still impossible to achieve. The openjdk loom project has not yet released the latest jdk version 18, which obviously cannot be used in production. Overall, the kotlin-coroutines is the only mature coroutine library on JVM that can be used in production, so the final choice of kotlin-coroutine is made as the coroutine library for non-blocking implementation.

[0046] As shown in Figure 1 Existing components: JVM virtual machine; kotlin and coroutine library; distributed coordination component: ZooKeeper or etcd, and its java client; scheduled task framework itself, based on the open source framework elastic-job, based on the kotlin coroutine to realize all its logic, including: ZooKeeper non-blocking call; etcd non-blocking call; concurrent task executor; timing task scheduler.

[0047] As shown in Figure 2 The specific process of ZooKeeper non-blocking call is shown in the figure:

[0048] The internal implementation of the ZooKeeper coroutine client itself is asynchronous and non-blocking, and provides a complete callback asynchronous API, similar to the native netty, and each asynchronous callback API also has a corresponding synchronous blocking encapsulation implementation. The native ZooKeeper component only provides the simplest data storage API, and the traditional implementation of the distributed scheduling solution elastic-job. Elastic-job uses the open source component curator to call the ZooKeeper component, and curator is an encapsulation of the business logic layer of the ZooKeeper component, which is easier to use, but all synchronous blocking APIs are called, and curator internally uses a thread pool to handle asynchronous, parallel, and listening, while the distributed coordination of elastic-job relies heavily on this listener, which causes each task to require at least one thread for remote listening on each server node.

[0049] Based on this problem, we made improvements. Non-blocking asynchronous callback interfaces were added to interface the native ZooKeeper client. Since the interfaces and APIs provided by the ZooKeeper component are all implemented using callbacks to achieve non-blocking, the two need to be interfaced to simplify the call using coroutines. The callbacks of the ZooKeeper component asynchronous API are all subinterfaces of the empty interface AsyncCallback, and different callback argument types and numbers have different, incompatible interface methods, and the same callback interface can be called by different external methods, which causes a variety of difficulties in interfacing. Here, two levels of encapsulation are used to solve this problem. For example Figure 3 The suspendCancellableCoroutine method is called inside the first-level packaging method. The suspendCancellableCoroutine method can convert the callback function into a coroutine, and the cancellation of the coroutine is controllable. The callback parameter CancellableContinuation instance is called inside the callback of this method. The anonymous inner class of the sub-interface of the AsyncCallback generated in the second level is called inside the callback of this method. The anonymous inner class of the sub-interface of the AsyncCallback generated in the second level is called inside the callback of this method. The implementation inside this implementation calls the resume and resumeWithException methods of the CancellableContinuation instance passed in to interface with the coroutine. Normally, the result is returned directly, and the exception is ended abnormally, and an exception is thrown. The specific implementation is in the ZooKeeperAsyncClient class, which encapsulates the callback-based asynchronous API by using coroutines. The external performance logic is basically consistent with the synchronous API of ZooKeeper itself, which is convenient for subsequent calls. For example Figure 4 The module after packaging is shown in the above manner. The asynchronous callback of the ZooKeeper node is added, deleted, modified, and inquired, and the asynchronous transaction is encapsulated. The encapsulation effect is coroutine add, delete, modify, and inquiry, and coroutine transaction / batch processing. In the coroutine state, the calling method of the original blocking code is almost the same, reducing the amount of subsequent changes. Based on these coroutine-encapsulated methods, the general interface for calling the coordination component, the state monitoring watcher, the local synchronization node cache, and the non-blocking distributed lock are implemented.

[0050] When the non-blocking transaction is performed using the module, the original ZooKeeper transaction assembly method is directly called, and a series of add, delete, modify, and inquiry operations are added. Only when the operation is performed, the asynchronous transaction submission interface is called through the coroutine encapsulation form, which becomes a transaction that can be called in the coroutine and is used for batch processing.

[0051] The coordination component general non-blocking add, delete, modify, and inquiry interface. The specific task state add, delete, modify, and inquiry logic in the task framework calls the CoordinatorRegistryCenter interface, which is used for the logical interface of the plan task to call the logical interface of the coordination component. First, it is rewritten using Kotlin, and then it is re-implemented using coroutines, and the ZooKeeperAsyncClient encapsulated by coroutines is called.

[0052] In this process, the non-blocking loop listener needs to be applied. The watcher in the native ZooKeeper component is asynchronous and non-blocking itself, but the ZooKeeper native node watcher is removed after listening once and cannot be continuously monitored. In order to solve this problem, a wrapper class LoopWatcher is encapsulated. After receiving an event, the wrapper class LoopWatcher automatically adds itself to ZooKeeper, and then executes the logic of the wrapped listener. In this way, the LoopWatcher itself can realize continuous monitoring.

[0053] Non-blocking local cache and cache listener. The traditional distributed coordination implementation is based on the CuratorCacheListener interface of the curator framework. CuratorCacheListener is the core interface of distributed scheduling, which is used to monitor the state changes of the distributed task cluster at each time point. Each business implementation interface of the distributed task is used to manage the start and stop of its own task. However, the non-blocking transformation cannot use the blocking curator, and ZooKeeper itself does not have a local cache implementation. In order to solve this problem, a ZooKeeper local node cache is re-implemented based on the encapsulated coroutine non-blocking api, and it is named ZooKeeperCacheImpl. The specific implementation uses HashMap to hold data, non-blocking locks to control concurrency, and LoopWatcher listeners to monitor the state of remote ZooKeeper server node subtree changes in real time. If there is new data change, the corresponding event is changed to the local cache state according to the remote change, and then the event is sent to the coroutine-based listener interface CacheListener.

[0054] The module also includes a non-blocking distributed lock, which is implemented by referring to the ZooKeeper official open source blocking distributed lock implementation ZooKeeper-recipes-lock, which is re-implemented in Kotlin. All methods that call ZooKeeper blocking api are replaced with the above coroutine-encapsulated non-blocking call, and the corresponding class is WriteLock.

[0055] As Figure 5 shown, in the case where ZooKeeper cannot be used, a backup solution is provided, which uses the etcd component as a distributed coordination component. Etcd is an open source component implemented by golang, which provides similar functions to ZooKeeper components. However, the api is not as perfect and functional as ZooKeeper components. The specific implementation is as follows.

[0056] Extract the interface, separate the registration coordination service package. Extract the interface related to the code of the zookeepe component, and the ZooKeeper only interfaces with this interface. The implementation of reactive-job is only connected with the interface, and the global search does not appear any word related to ZooKeeper. Then, the interface and the ZooKeeper implementation code are divided into two jar packages, and the reactive-job logic only depends on the interface jar package and does not depend on the ZooKeeper implementation jar package. A new etcd jar package is created, and the interface method and data structure used by the interface package are implemented.

[0057] Implement a sub-node tree similar to the ZooKeeper component. The Etcd component does not have a tree structure, which is a key-value pair kv structure similar to redis. The symbol ` / ` is used to separate the keys by level to simulate a tree structure. The add, delete, modify and query operations of the normal node directly call the interface provided by the Etcd client which returns CompletableFuture, and then use the CompletableFuture.await method provided by the kotlinx-coroutines-jdk8 package to connect, without the complex non-generic asynchronous callback encapsulation of the ZooKeeper component.

[0058] The etcd component does not have the concept of temporary node in the ZooKeeper component, that is, the node is deleted when the client disconnects. Almost all the logic of reactive-job is based on this mechanism; here, the lease mechanism of the etcd component is used as a replacement scheme, and a client instance is associated with a lease leaseId. The lease is invalid within 3 seconds after being closed, and the key managing the lease is automatically deleted.

[0059] All the logic of reactive-job is based on the local cache listener of the ZooKeeper component. The etcd component here is no longer so troublesome, and the cache needs to maintain a large amount of local state; all cache-related operations go through the remote, and only a job root directory prefix listener is added, and then all remote data changes are distributed through the interface.

[0060] The etcd component has a non-blocking distributed lock, and it does not need to be implemented like the ZooKeeper component; the distributed lock call transmits the path path and returns the lock key lockKey, which are two different things. The release lock needs to call the unlock lock key lockKey.

[0061] No matter which component is selected as a distributed coordination component, the scheme described in the present application still needs a concurrent task executor to call the application logic actual execution package.

[0062] The concurrent task executor is configured in the reactive-job-executor-kernel package, which is a package for actually executing application logic. The original version is implemented in multithreading, and a new thread pool is also opened in the case of task sharding, further increasing thread resource consumption. In the present application, the async+awaitAll method structure of the coroutine is used for concurrent implementation, which is equivalent to submitting several tasks to the thread pool under multithreading, and obtaining List <future>Then traverse to wait for all futures to end.

[0063] The task execution interface is currently provided in the form of a java SPI, facilitating subsequent extension, and internally implementing a SimpleJob, a javaMonoJob, and a javaBlockJob by default. The SimpleJob directly uses a kotlin coroutine interface and is directly run in an executor. Considering that the current scheduled task framework is under java, java needs to be compatible and adapted, and two java interfaces are provided. The javaMonoJob interface uses a Mono return value and is executed in the form of an asynchronous callback to a coroutine. The other javaBlockJob is additionally provided to consider that asynchronous callback code under java is prone to callback hell, and most java code logic is blocking, so a blocking interface is provided, which uses the logic of executing a task in a thread pool. Compared with the original implementation of scheduling by using a thread to constantly run to implement scheduling, a considerable amount of thread resources can be saved, but this use is still not recommended, and the two preceding interfaces are preferably used.

[0064] At this time, a unit test framework can be used for testing. The unit test framework uses junit, and the original version of multi-threading concurrency uses an open source framework Mockito to simulate interface returns for unit testing, but Mockito does not support Kotlin null safety and coroutines, and direct use will report various errors. Here, an open source framework MockK is used, which is basically a 1:1 implementation of Mockito under Kotlin, fully supports Kotlin, supports null safety, and supports coroutine testing.

[0065] Finally, a timing task scheduler is needed at runtime, and the timing task scheduler in the application is implemented based on a non-blocking coroutine. When a new scheduled task instance is started, the task instance information of the instance is registered with the coordination component, and the information of all existing current task instances is obtained, the current task master node is determined through a master selection operation; the task provides a sharding mode, and the default is one shard. The master node distributes the execution node information of the shard according to the task instance IP and other information, and writes the information into a distributed coordination component; the timing task scheduling logic is placed in AsyncJobScheduler, and the core logic is an infinite loop in a coroutine. The hutool-cron toolkit is used to parse the CRON expression, obtain the next execution time, delay the interval from the current time, and then call the executor of the task; the executor receives the task scheduling, judges whether the current node is the execution node of the current shard of the task, and if so, executes the task, otherwise, skips; for the control of the scheduler, pause and shutdown directly kill the coroutine, and resume directly calls the start of the coroutine to start running.

[0066] The task distribution listener in the timing task scheduler uses the wait, notifyAll semantics of java.lang.Object, and the koltin-coroutine does not have an implementation of similar semantics, so a slightly complex tool class ObjectWaitAsync is created to implement similar semantics. The specific implementation is as follows: hold a lock mutex, hold a CompletableDeferred list, every time waitAsync() is called, a CompletableDeferred is added, and then the CompletableDeferred is made to await; notifyAllAsync makes all the CompletableDeferred in the list call complete, and then clears the list; all operations use the non-blocking lock mutex to limit concurrency.

[0067] As for other logics of the timing task scheduler, only the non-blocking implementation is rewritten by using koltin-coroutine, including the life cycle operations of the task such as configuration, start, pause, stop, and delete, the implementation of the distributed characteristics such as publishing own information to a distributed coordination component, listening to the state information of all nodes in a task cluster, and high availability, elasticity, and fault transfer.

[0068] As shown in Figures 7-10 , we use a computer with hardware of 4 cores, 8 threads, and 24g memory, and an application of 1000 tasks, and each scheduled task prints the task name every 5s. The specific implementation is shown below, and the performance comparison of the two is shown.

[0069] The traditional elastic-job gradually expands the heap memory to 1.7G and stabilizes, and the actual memory usage fluctuates between 0.4G and 1.2G every 5s; about 5 minutes, the number of threads increases to 2000, which is about the first time the memory expands, and the growth rate slows down obviously, and at 18 minutes, the number of threads stabilizes at 3014, which is roughly 3 threads consumed by 1 task;

[0070] And the application can be stabilized within a few seconds, and after stabilization, the heap memory is about 210MB, and the actual memory usage fluctuates between 100MB and 200MB; after stabilization, about 20 threads are occupied.

[0071] It should be noted that the number of threads displayed below Figure 7 and Figure 8 is the total number of threads on the device running under the same conditions. It can be seen that the actual number of threads of the application and the prior art differs by more than 3000, which meets our expectations.

[0072] Embodiment 2: execute scheduled tasks with certain execution duration. Task execution interval is 1 minute, single task http call remote rest service, rest service interface execution duration is 30 seconds, 10 tasks are executed in parallel, test result task can be executed normally, total number of threads is still about 20.

[0073] It should be noted that, in this article, relationship terms such as first and second are only used to distinguish one entity or operation from another entity or operation, and do not necessarily require or imply any such actual relationship or order between the entities or operations. Moreover, the term "includes", "contains" or any other variant thereof is intended to cover non-exclusive inclusion, so that the process, method, article or device including a series of elements not only includes those elements, but also includes other elements not explicitly listed or inherent to such process, method, article or device. Without more limitations, the element defined by the statement "includes a" does not exclude the presence of other identical elements in the process, method, article or device including the element.

[0074] The above embodiments are only used to illustrate the technical solutions of the present disclosure, rather than limit them; although the present disclosure has been described in detail with reference to the foregoing embodiments, those skilled in the art should understand that they can still modify the technical solutions recorded in the foregoing embodiments, or make equivalent replacement for part of the technical features; and these modifications or replacements do not make the essence of the corresponding technical solutions deviate from the spirit and scope of the technical solutions of the embodiments of the present disclosure.< / future> < / future>

Claims

1. A JVM-based non-blocking distributed scheduled task scheduling method applied to a Jvm virtual machine configured with a kotlin language library and a coroutines coroutine library, characterized in that, Comprise: A distributed scheduled task framework, which comprises: A concurrent task executor: a package that actually executes the application logic; And a timing task scheduler; the timing task scheduler performs life cycle operations of the task, including configuration, start, pause, stop, and deletion; Also comprising a distributed coordination component; the distributed coordination component is an Etcd component; the registration coordination service package is separated by extracting an interface; all code related to the ZooKeeper component is extracted into an interface, and then the extracted interface and the ZooKeeper component implementation code are divided into two jar packages; a new etcd jar package is created, and all interface methods and data structures used in the interface package are implemented; the distributed coordination component is provided with general non-blocking add-delete-update-query interfaces as non-blocking asynchronous callback interfaces of the client where the distributed coordination component is located; The coroutines coroutine library interfaces with the client asynchronous interface of the distributed coordination component; the non-blocking asynchronous callback interface of the distributed coordination component is encapsulated through coroutines, and a non-blocking loop listener is set to realize continuous listening; All cache-related operations of the etcd component are performed remotely, a job root directory prefix is added for listening, and then all remote data change events are distributed through the interface; Wherein the add-delete-update-query operation of the ordinary node directly calls the interface provided by the Etcd client to return CompletableFuture, and then interfaces with the CompletableFuture.await method provided by the kotlinx-coroutines-jdk8 package; the concurrent task executor is configured in the reactive-job-executor-kernel package, and the async+awaitAll method structure of the coroutine is used to realize concurrent implementation; The timing task scheduler registers its task instance information in the distributed coordination component and obtains the information of all existing current task instances when a new scheduled task instance is started, determines the current task master node through the master selection operation; the task provides a sharding method, and the master node distributes the execution node information of the shard according to the task instance ip information, average, and polling strategy, and writes it into the distributed coordination component; Also comprising a java client, which executes tasks through the java client.

2. The non-blocking distributed scheduled task scheduling method based on JVM according to claim 1, characterized in that, The etcd component uses a lease mechanism lease to replace the client's continuous lease, and a client instance is associated with a lease leaseId. The lease is invalid within 3 seconds after being closed, and the key for managing the lease is also automatically deleted.

3. The non-blocking distributed scheduled task scheduling method based on JVM according to claim 1, characterized in that, The etcd component has a non-blocking distributed lock, which is called by the distributed lock, and the path is input and the lock key is returned. The lock key needs to be called to release the lock.

4. The non-blocking distributed scheduled task scheduling method based on JVM according to claim 1, characterized in that, Task execution interface is provided in the form of java SPI, and the internal default implementation is SimpleJob, javaMonoJob, javaBlockJob; among them, SimpleJob directly uses the interface of kotlin coroutine, and directly runs in the executor.

5. The non-blocking distributed scheduled task scheduling method based on JVM according to claim 4, characterized in that, Two java interfaces are provided: the javaMonoJob interface uses Mono return value to execute in the form of asynchronous callback to coroutine; the other javaBlockJob is additionally considered that asynchronous callback code under java is easy to cause callback hell, and most of the java code logic is blocking, so a blocking interface is provided, which adopts the logic of executing the task in the thread pool.

6. The non-blocking distributed scheduled task scheduling method based on JVM according to claim 1, characterized in that, Unit testing framework is used for testing, junit is used for unit testing framework, open source framework MockK is used for multithreading concurrency, which fully supports Kotlin, supports null safety and coroutine testing.

Citation Information

Patent Citations

  • Methods and apparatus for implementing distributed scheduling based on ZooKeeper

    CN105159767B

  • Systems, methods, apparatus and computer storage media for data synchronization

    CN109815028B

  • Routing strategy based on XXL-JOB distributed task scheduling system

    CN114265681A

  • Coroutine-based server monitoring method, equipment and readable medium

    CN110445669A

  • Task distribution processing method and system, computer equipment and storage medium

    CN111182061A