通过前面两篇文章《调度中心集群配置》《执行器配置及定时任务的创建》,我们已经获取到了一个XXL-JOB的集群,以及一个可以执行任务的调度器,在实际的项目中可以参照这个流程,引入定时任务。
接下来,我们就可以来探索一下执行器的注册与发现的原理了,本篇主要是描述执行器的注册与发现,主要包括以下几点内容:
这几个机制共同构成了一个稳定运行的定时任务流程。
一个定时任务的方法是如何被调用的呢?
XXL-JOB的调度关系分为了3层,每层向下进行调度,最上层是调度中心,最下层是定时任务方法,调度中心可以调度不同的执行器,执行器再调用归属于自己的定时任务,如下图所示:
调度中心在调度执行器时,需要知道执行器的ip和端口号,以此来找到对应的执行器节点来进行调度。而调度中心获取到执行器ip的方式有两种,分别是:自动注册、手动录入。
一般不会使用手动录入的方式,可以想象一下,新增、减少了执行器实例,执行器宕机需要下线都需要手动修改机器地址,意味着需要有人24小时盯着,这是一件很可怕的事。
所以,正常情况下我们都会选择使用自动注册的方式来创建,选择这种方式的话,就需要调度中心与执行器之间建立通信机制,通过网络请求传输注册信息。
当前版本(2.3.1)的XXL-JOB采用的是Http通信,而调度中心是通过SpringBoot来实现的。
因此可以猜测,调度中心就是启动了一个Tomcat,并提供了执行器注册接口,执行器在启动的时候,将自己的ip,端口等信息传输到调度中心,由调度中心存起来,这样就完成了执行器注册。
首先,需要调度中心向外暴露注册接口才能收到执行器的注册请求。
在调度中心新的服务xxl-job-admin
中,我们可以查看contoller包
中是否提供了注册的接口,XXL-JOB的命名比较规范,我们可以很容易的找到一个api相关的Controller接口JobApiController
。
果然,在这个类里面有一个api
相关的方法,如下:
一路顺藤摸瓜,就找到了实际做注册动作的方法:
注意截图中的3个红框,这里包含了两个关键点:
xxl_job_registry
这张表。简单的说,就是调度中心会接收执行器的registry
请求,然后将请求中传入的参数保存到xxl_job_registy
表中。这就是调度中心运行的执行器注册主流程,一个非常简单的CRUD。
看完了主流程之后,我们再来看一下细节,可以发现这里的注册代码并不是同步执行的,而是通过一个线程池registryOrRemoveThreadPool
来进行的异步操作。这里也提现了XXL-JOB的一个设计思想,即全异步化调用,我们在研究后续原理的时候,还会经常看到这样的用法。
注册或注销线程池的创建
registryOrRemoveThreadPool
这个线程池在这里可以直接使用,那一定是提前创建好的,有两种方式可以找到创建这个线程池的位置。
usages
可以找到new ThreadPoolExecutor()
的地方,这就是线程池的创建。properties
配置的key,查看是哪个位置使用了properties
的值,再去找afterPropertiesSet
方法,一般的初始化都会在这个方法中。init()
,因为XXL-JOB的命名是比较规范的,我们很容易就可以找到线程池初始化的位置。start()
方法中除了初始化registryOrRemoveThreadPool
之外,还启动了一个线程registryMonitorThread
这个线程就是调度中心的探活线程,下面的注销原理中会提到。上面详细的描述了如何在源码中找到对应的方法位置,以及调度中心处理注册请求的过程,忽略探活线程的流程如下:
我们所说的执行器,实际上就是一个普通的Spring-Web项目,在其中引入了xxl-job-core
包,按照上面描述的源代码查询流程,通过properties
文件可以定位到执行器对象XxlJobSpringExecutor
,在这个对象中对执行器做了初始化的操作。
由于篇幅的关系,下面就不再贴代码了,而是描述整个流程。
我们可以通过注册信息,倒推一下执行器做了什么,下图是调度中心保存的一个执行器的注册请求数据。
properties
文件中的。同时,在数据行中的registry_key
字段值也可以通过properties
配置文件中配置直接获取。registry_value
中的ip和端口信息,也可以来自配置文件,只是还需要做一点额外的操作。既然是将ip和端口号信息传输到了注册中心,其目的肯定是希望调度中心通过这个ip和端口号来调度执行器执行定时任务,也就是说,执行器在启动时,也会向外暴露一个Http接口,这个接口会用于后面的定时任务调度。执行器的注销分为主动注销和被动注销两种。
主动注销的发起时机是在Spring容器正常关闭时,XXL-JOB的执行器类实现了DisposableBean
接口,这个接口会在Spring的停止流程中被调用,此时会调用registryRemove
接口。
调度中心收到请求后,也会通过registryOrRemoveThreadPool
线程池进行异步处理,最终将xxl_job_registry
中对应的执行器信息删除掉。
这个过程与注册的过程类似,这里就不过多的赘述了。
前面我们看到了在调度中心初始化时,会启动一个探活线程registryMonitorThread
,这个线程每30秒会执行一次,执行时会查询xxl_job_registry
中update_time
与当前时间的差值大于90s的数据,将这部分数据给删除掉。
也就是说,某个执行器在90秒至120秒内都没有发送新的注册请求来维持心跳的话,这个执行器就会被调度中心干掉。
心跳是怎么维持的呢?
在上面3.1中的代码截图中,我们可以看到,注册接口实际上是做了保存和更新两种处理,这里的更新方法其实就是在更新update-time
的值,目的就是为了维持心跳,不被探活线程给干掉。
在执行器所在的服务中,也有一个线程在不断的请求注册接口来更新注册信息,具体位置在ExecutorRegistryThread
类中,创建了一个registryThread
线程,每30秒会调用一次registry
接口,直到服务停止。
经过上面的探索,我们已经了解了执行器的注册与注销的流程,下面是这整个流程的流程图。
本篇内容主要是在探索执行器注册到调度中心的流程及实现原理。
xxl_job_registry
表,新增或删除执行器的注册信息。注:本篇只是在探索注册于发现的流程,所以忽略在这个流程中还涉及到的任务调度与回调相关的逻辑。