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