xxl-job 原理讲解(一)

本来想分三篇文章来详细讲解和分析 xxl-job ,但最近较忙,综合写在一篇里面了,所以本文不再讲述定时任务、分布式调度中心、quartz、Cron 等这些基本概念,想更多了解 xxl-job 的设计和思想,请访问 xxl-job官网 ,本文主要分析 xxl-job 的源码 。

xxl-job 的架构设计主要包括两部分:执行器调度中心

执行器

你项目中的一段业务代码,想被 xxl-job 调度,就必须引入 xxl-job 的 core 包:

并在配置文件中指定调度中心的地址(支持 .properties 或 .yaml 格式的配置文件):

然后初始化 XxlJobSpringExecutor 对象:

至此,你的项目已经成为一个执行器,可以跟调度中心通信了。

调度中心

在 xxl-job 官方提供的源码中,有一个 admin 项目,该项目就是调度中心,调度中心支持集群部署,集群情况下各节点务必连接同一个 mysql 实例。先执行 xxl-job 提供的初始化 sql ,然后修改配置文件中数据库等配置信息为自己的:

然后启动 admin 项目即可看到 xxl-job 调度中心的页面:

至此,执行器我们已经配置过了,调度中心也启动成功了,然后在执行器中添加一段代码,这段代码将被调度中心调度(本文只分析运行模式为 Bean 模式的源码),这段代码如下:

然后在调度中心中配置该定时任务:

配置成功之后,将会在 xxl-job 控制台的 “任务管理” 里面看到大致如下界面:

每一个定时任务,对应 xxl_job_info 表中的一条记录,对应的对象是 XxlJobInfo 对象。然后 xxl-job 就可以为我们所用了。


下面,我们要思考的问题是:

  • xxl-job 的执行器和调度中心是怎么通信的?
  • 调度中心是如何调度不同的定时任务的?
  • 执行器是怎么把自己注册到调度中心上的?
  • ……

下面一步步分析。

调度中心是怎么知道都有哪些定时任务的呢?

还记得上面提到执行器需要配置 XxlJobSpringExecutor 吗,这个 XxlJobSpringExecutor 就是执行器的入口,在执行器项目启动之后,XxlJobSpringExecutor 中的 initJobHandlerMethodRepository 方法会扫描执行器项目中所有加了  @XxlJob 注解的方法,这里需要说一下 @XxlJob 这个注解:

上面的 value() 值是我们定义的 jobHandler 名字,这个过程具体来说,会先扫描所有被 Spring 管理的 Bean 对象,然后对于每个 Bean 对象的每个方法,都会进行扫描并判断该方法是否加了 @XxlJob 注解,如果加了,则会根据 @XxlJob 注解的值、该 Bean 对象等来创建一个 MethodJobHandler 对象:

然后调用父类(com.xxl.job.core.executor.XxlJobExecutor#registJobHandler)方法将 jobHandler(即我们的定时任务)注册到调度中心上:

到这一步其实只是将 jobHandler 放进一个 Map 中:

放进 Map 中的目的就是为了方便统一管理(如一次 load 全部 MethodJobHandler 、clear 掉全部 MethodJobHandler ,该 Map 在 com.xxl.job.core.biz.impl.ExecutorBizImpl#run 方法中会用到)。

然后 XxlJobSpringExecutor 会执行 super.start() 方法即调用父类的 start() 方法:

主要关注上面的:

和:

逐个分析上面两个方法。

initAdminBizList(…)方法:

这个方法是将一个或多个调度中心地址封装成 AdminBizClient 集合,AdminBizClient 源码如下:

作用AdminBizClient 中的 addressUrl 是调度中心的地址,registry、callback、registryRemove 方法分别调用调度中心的相关方法,即可以通过 AdminBizClient 往调度中心发起 registry、callback、registryRemove 请求。

initEmbedServer(…) 方法:

initEmbedServer(…) 方法的作用其注释上已经说的很清楚了,它会找到本地(执行器)的 IP 地址和一个可用端口(默认是9999),然后用这个 IP:port 和调度中心进行通信,同时这个 IP:port 也会记录在 xxl_job_registry 表里面。我们看看 embedServer.start(address, port, appname, accessToken); 的具体实现,这个方法在 EmbedServer 中:

主要关注上面这个方法里三个地方的代码:

01 处:

关于 ExecutorBizImpl 类的分析见:xxl-job源码主要类 – 码先生的博客 (codermr.com)

02 处:

主要是创建了一个 EmbedHttpServerHandler 对象,入参是 executorBiz(ExecutorBizImpl), accessToken, bizThreadPool :

EmbedHttpServerHandler 是 EmbedServer 的一个静态内部类,由于 xxl-job 使用的 netty 版本为 4.x,所以它需要实现的抽象方法为:

在重写的该方法里面主要代码是:

主要是上面的 process 方法,主要作用是根据不同的 uri 调用 executorBiz(实现类是 ExecutorBizImpl ) 的不同方法:

ExecutorBizImpl 是在执行器中被使用的,上面使用了 Netty 和调度中心进行通信,在接受到调度中心发送过来的网络请求后(调度中心使用 ExecutorBizClient 发送),创建了一个 EmbedHttpServerHandler 处理该网络请求,而 EmbedHttpServerHandler 中就是用 ExecutorBizImpl 来处理网络请求的。关于 ExecutorBizImpl 类的分析见:xxl-job源码主要类 – 码先生的博客 (codermr.com)

03 处:

startRegistry(appname, address); 方法:

具体实现在 ExecutorRegistryThread 中:

上图 46 行的 adminBiz(AdminBizClient) 就是上面讲的 initAdminBizList(…)方法里面生成的,有几个调度中心,就会有几个 adminBiz 实例,执行器会往这几个调度中心上分别注册自己。往调度中心上注册完之后会有一个 30s 的休眠时间(上图第 70 行代码),即执行器每隔 30s 往调度中心注册一次自己。这个时间间隔在这里配置:

所以,embedServer.start(address, port, appname, accessToken); 方法的作用是

  • 使用 Netty 和调度中心保持心跳和通信,调度中心会通过 Netty Http 请求检测执行器是否可用,并通过 Netty Http 请求通知执行器执行一个定时任务。
  • 周期性的(每隔 30s)往调度中心注册自己(使用 Http 请求)。
小结:
  • 执行器通过 Netty 和调度中心保持通信的接口只有这几个:
  • 执行器每隔 30s 往调度中心注册自己,是在 ExecutorRegistryThread 中,是通过 Http 请求进行的。
  • 调度中心通知执行器执行定时任务,是在 com.xxl.job.admin.core.trigger.XxlJobTrigger#runExecutor 这里是通过 Http 请求的方式。而具体调度中心是怎么通知执行器执行定时任务的见:xxl-job原理讲解(三):调度中心 – 码先生的博客 (codermr.com)


调度中心通知执行器执行任务
  1. 调度中心向客户端发起 post 请求
  2. client 通过内嵌服务 netty 接收,异步线程处理
  3. 找到 job 绑定的线程,将任务丢到阻塞队列中。然后返回结果给调度中心。
  4. 调度中心更改任务状态。
  5. 客户端执行任务后,将执行结果丢到回调线程的阻塞队列处理。
  6. 回调线程通过 post 请求访问调度中心,调度中心更改 job 最终结果。
  7. 倘若超过 10 分钟调度中心没收到回调线程的请求,则设置 job 最终结果失败。


“调度成功”和”执行成功”的含义及区别?

在 xxl-job 的调度日志中,有一个”调度结果”和”执行结果”的字段,那么这两者是什么含义、有什么区别呢?

  • “调度结果”,是调度中心调度某一个定时任务,是否调度成功了,这个状态是调度中心维护的。
  • “执行结果”,是执行器执行定时任务具体是成功还是失败了,执行器会回调调度中心的 callback 方法,具体来说是执行器中的 triggerCallbackThread 线程会将定时任务执行的结果,通过回调调度中心的 callback 方法将执行结果保存在调度中心的数据库中。

后续内容请访问:

xxl-job原理讲解(二):主要类 – 码先生的博客 (codermr.com)

xxl-job原理讲解(三):调度中心 – 码先生的博客 (codermr.com)

码先生
Author: 码先生

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注