从xxl-job看分布式调度中心的设计

xxl-job是xuxueli同学开源的XXL系列中的一员,是一个分布式任务调度平台。我在撸完了TCP/IP协议栈源码(netX, 非开源,讲真,里面关于TCP拥塞控制那部分没看明白)、Tomcat源码(JSP/Servlet容器)、Motan源码(RPC框架)之后。想研究一下调度中心的实现, 也没有仔细比较过就选择了XXL_JOB. 撸完一遍源码后,发现是一个比较小的项目,所以就不打算搞一个系列了,就整一篇博客总结一下,还是有所收获的。

关于xxl_job的功能介绍以及使用方法,作者写的文档很详细,也很容易上手,这里就不追溯了。代码相对简单,没有复杂的算法、数据结构、线程模型等,我就不抠细节了,从大的方面来总结下。

1. 项目结构

300

  • db,建表语句的SQL
  • doc, 文档
  • xxl-job-admin,调度中心,负责任务的管理(新增、删除、更新等)、触发(手动触发、自动触发、暂停、恢复等)、监控(日志)、执行器的管理等。还有就是提供了Web管理平台,可以方便通过页面管理任务。
  • xxl-job-core, 公共依赖,定义了执行器的接口,实现了任务的执行,RPC等
  • xxl-job-executor-example、xxl-job-executor-springboot-example,执行器的样例代码,供体验和参考

2. 整体架构


图片来源,官方文档。先来看下调度中心的几个组件
1.【调度中心】数据中心
就是一些表:

  • XXL_JOB_QRTZ_TRIGGER_GROUP, JOB所属的分组,一般一个应用配置一个group,然后每个group有多个address(一个应用部署在多台服务器上)
  • XXL_JOB_QRTZ_TRIGGER_INFO,定时任务配置,每条配置会有如下属性:
    • cron表达式
    • handler, 即这个Job要出发group里的哪个方法.(一个应用为一个group,一个应用可以有多个接口,或者说多个方法)
    • group Id, 即这个job是用来触发哪个group的
    • 其他信息,ha策略、路由策略等
  • XXL_JOB_QRTZ_TRIGGER_LOG, 任务执行情况的日志
  • XXL_JOB_QRTZ_TRIGGER_REGISTRY, 保存注册服务收集的应用address, 最后会有线程收集并更新到XXL_JOB_QRTZ_TRIGGER_GROUP的addressList中去
    2.【调度中心】调度器
    用的是集群Quartz
    3.【调度中心】Rolling日志
    就是普通的rolling日志,没啥好说的。
    4.【调度中心】 回调服务
    回调接口,任务执行结束后,执行器通过该接口告知执行结果,调度中心根据结果记录一下日志。
    5.【调度中心】 注册服务
    提供一个接口,在应用启动的时候来注册一下,告知一下自己的ip地址和端口。还有一种方式是通过管理平台手动添加应用的名称和地址。

6.【执行器】执行器服务
暴露的一个RPC服务,负责接收调度中心的请求,然后把封装成任务丢到队列里
7.【执行器】JobHandler
定义的任务处理接口
8.【执行器】任务线程
负责从队列里提取任务,执行,并把执行结果通过回调的方式传给调度中心
9.【执行器】注册线程
执行器启动的时候,会通过调度中心的注册接口,将自己的name和address注册到调度中心。

3. 我的思考

  1. 如果我来设计这个项目的话,我会把RPC这部分改成使用Motan或Dubbo之类的,尽量弄成可扩展的,可配置的。这样做的好处是:不需要 【调度中心】 注册服务 和 【执行器】注册线程了。 调度中心只需要维护一个服务引用即可,其他事情 RPC框架帮你做了。而且,也不用自己去实现route策略了,RPC框架一般都自带route实现且可配置的。

  2. 本来我觉得没必要搞个执行器,直接调度中心 call 应用系统的接口就可以了。但这样有个问题,应用系统的接口不统一,调度中心需要依赖各个接口的JAR包,这显然不合理。所以执行器(我更倾向于叫它调度中心提供的client包)还是有必要的,主要负责以下工作:

    1. 定义一个统一的接口,在应用启动的时候,以服务的方法发布出去。这个接口是job的统一入口
    2. 在应用启动的时候,负责收集应用所有的job 入口,并将他们管理起来。(可以是xml配置,也可以是annotation的方式来标注方法),通过Spring的一些hook接口来做
    3. 执行队列,采用 Disruptor. 将受到的调度请求,经过解析处理,封装成任务,丢给该队列。然后从该队列中拿出来后,找到对应的方法,去执行。
    4. 回调,我倾向于用MQ的方式,应用系统执行完任务后,发个MQ出来。
  3. 多层任务分发没有实现。

以上皆是阅读源码 https://github.com/xuxueli/xxl-job (tag v1.8.0)所得,文中贴的代码或配置有所删减