分布式任务调度平台 XXL-JOB

参考 分布式任务调度平台xxl-job

源码下载地址 https://github.com/xuxueli/xxl-job/releases

一、需求背景

以往系统中的周期性任务执行,一般有以下几种方式

  • 引入quartz or spring-task ,每一个任务对应编写一个执行类,配置调度规则执行
  • 使用系统的执行器,比如centos的crontab,每一个任务对应编写一个执行类,并在crontab 配置调度规则

此时,又会出现以下的问题

  • 任务出现异常,需增加报警功能
  • 任务出现阻塞,比如上一次任务没执行完,下次任务会排队等待,以此累积
  • 任务超时,需自己主动中断任务
  • 单机任务调度下,系统出现异常,比如内存爆掉,磁盘空间不够用
  • 任务失败,需hardcode 指定次数重试
  • 在没有配日志可视化的前提下,查看日志需要去服务器上敲命令,面临rm -rf *的风险
  • ...

二、什么是xxl-job

XXL-JOB是一个轻量级分布式任务调度平台,支持通过 Web 页面对任务进行 CRUD 操作,支持动态修改任务状态、暂停/恢复任务,以及终止运行中任务,支持在线配置调度任务入参和在线查看调度结果。

它的特点有:

平台:

  • 调度中心式设计
  • 解耦
  • 用DB实现注册中心,实现轻量级部署
  • 弹性扩、缩容
  • 故障转移:执行器集群某台机器出现故障,能够自动切换正常的执行器
  • 分片广播:且支持动态分片,提升任务运行效率
  • 路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等;
  • 全异步化:异步调度和异步执行。

任务:

  • 任务分布式执行
  • 简单:支持通过web对任务进行crud
  • 灵活:动态修改任务状态,自定义失败重试次数等,且即时生效
  • 告警:任务失败,支持邮件告警

三、概念说明

任务

调度任务,系统角色中的最小单元

  • 可通过webide(GLUE模式) 或者ide(BEAN模式)自定义任务内容
  • 支持多种路由策略和阻塞处理策略的选择
  • 支持自定义失败重试次数和任务超时时间
  • 支持任务失败告警
  • 支持动态修改任务状态和各个配置策略的修改

调度中心

统一管理任务调度平台上的调度任务,负责触发调度执行,并提供可视化平台管理

执行器

主要负责接收“调度中心”的调度并执行

四、安装启动

下载

https://github.com/xuxueli/xxl-job

环境要求

  • Maven3+
  • JDK1.7+
  • MYSQL5.6+

入门

  • 初始化数据库

sql脚本在源码内,用navicat工具导入即可

doc/db/tables_xxl_job.sql
  • 调度数据库配置说明

- xxl_job_lock:任务调度锁表;
- xxl_job_group:执行器信息表,维护任务执行器信息;
- xxl_job_info:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
- xxl_job_log:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
- xxl_job_logglue:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
- xxl_job_registry:执行器注册表,维护在线的执行器和调度中心机器地址信息;
- xxl_job_user:系统用户表;
  • 项目结构

xxl-job-admin:调度中心
xxl-job-core:公共依赖
xxl-job-executor-samples:执行器Sample示例(选择合适的版本执行器,可直接使用,也可以参考其并将现有项目改造成执行器)
    :xxl-job-executor-sample-springboot:Springboot版本,通过Springboot管理执行器,推荐这种方式;
    :xxl-job-executor-sample-spring:Spring版本,通过Spring容器管理执行器,比较通用;
    :xxl-job-executor-sample-frameless:无框架版本;
    :xxl-job-executor-sample-jfinal:JFinal版本,通过JFinal管理执行器;
    :xxl-job-executor-sample-nutz:Nutz版本,通过Nutz管理执行器;
  • 配置部署“调度中心”(xxl-job-admin)

    更改配置

    ### web
    server.port=8080
    server.context-path=/xxl-job-admin
    
    ### resources
    spring.mvc.static-path-pattern=/static/**
    spring.resources.static-locations=classpath:/static/
    
    ### freemarker
    spring.freemarker.templateLoaderPath=classpath:/templates/
    spring.freemarker.suffix=.ftl
    spring.freemarker.charset=UTF-8
    spring.freemarker.request-context-attribute=request
    spring.freemarker.settings.number_format=0.##########
    
    ### mybatis
    mybatis.mapper-locations=classpath:/mybatis-mapper/*Mapper.xml
    
    ### xxl-job, datasource
    spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?Unicode=true&characterEncoding=UTF-8
    spring.datasource.username=root
    spring.datasource.password=123456
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    
    spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource
    spring.datasource.tomcat.max-wait=10000
    spring.datasource.tomcat.max-active=30
    spring.datasource.tomcat.test-on-borrow=true
    spring.datasource.tomcat.validation-query=SELECT 1
    spring.datasource.tomcat.validation-interval=30000
    
    ### xxl-job email
    spring.mail.host=smtp.qq.com
    spring.mail.port=25
    spring.mail.username=xxx@qq.com
    spring.mail.password=xxx
    spring.mail.properties.mail.smtp.auth=true
    spring.mail.properties.mail.smtp.starttls.enable=true
    spring.mail.properties.mail.smtp.starttls.required=true
    spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
    
    ### xxl-job, access token
    xxl.job.accessToken=
    
    ### xxl-job, i18n (default empty as chinese, "en" as english)
    xxl.job.i18n=
    

    部署项目

    方式一:项目打包部署
    • 打包xxl-job-admin

    • 调度中心访问地址:http://localhost:8080/xxl-job-admin (该地址执行器将会使用到,作为回调地址)

      默认登录账号:admin 默认登录密码:123456

    center.jpg
    方式二:Docker镜像搭建
    • 下载镜像
    docker pull xuxueli/xxl-job-admin
    
    • 创建容器

      由于官方镜像的配置文件(application.properties)是默认的,这里需要通过 "PARAMS" 指定,参数格式 RAMS="--key=value --key2=value2" 去修改;

      docker run -e PARAMS="--spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?  
      Unicode=true&characterEncoding=UTF-8" -p 8082:8080 -v /tmp:/data/applogs --name xxl-job-admin  -d     
      xuxueli/xxl-job-admin
      
    • 运行

      mvn clean package
      docker build -t xuxueli/xxl-job-admin ./xxl-job-admin
      docker run --name xxl-job-admin -p 8080:8080 -d xuxueli/xxl-job-admin
      
    调度中心集群(可选 )

    调度中心支持集群部署,提升调度系统容灾和可用性。

    调度中心集群部署时,几点要求和建议:

    • DB配置保持一致;
    • 登陆账号配置保持一致;
    • 集群机器时钟保持一致(单机集群忽视);
    • 建议:推荐通过nginx为调度中心集群做负载均衡,分配域名。调度中心访问、执行器回调配置、调用API服务等操作均通过该域名进行。
  • 配置部署“执行器项目”(xxl-job-executor-sample)

    执行器配置application.properties

    # web port
    server.port=8081
    
    # log config
    logging.config=classpath:logback.xml
    
    
    ### 调度中心部署跟地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册
    xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
    
    ### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
    xxl.job.executor.appname=xxl-job-executor-sample
    ### 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务"
    xxl.job.executor.ip=
    ### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口
    xxl.job.executor.port=9999
    
    ### 执行器通讯TOKEN [选填]:非空时启用
    xxl.job.accessToken=
    
    ### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径
    xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
    ### 执行器日志保存天数 [选填] :值大于3时生效,启用执行器Log文件定期清理功能,否则不生效
    xxl.job.executor.logretentiondays=-1
    

    部署项目

    官方提供了多种执行器的示例项目,这里默认使用springboot。

    执行器集群(可选)

    执行器支持集群部署,提升调度系统可用性,同时提升任务处理能力。

    执行器集群部署时,几点要求和建议:

    • 执行器回调地址(xxl.job.admin.addresses)需要保持一致;执行器根据该配置进行执行器自动注册等操作。
    • 同一个执行器集群内AppName(xxl.job.executor.appname)需要保持一致;调度中心根据该配置动态发现不同集群的在线执行器列表。

五、开发

任务运行模式主要有Bean模式和Gule模式(理解为脚本)

BEAN模式

在该模式下,任务的具体实现逻辑是 以JobHandler的形式存在于“执行器项目”中。

  • 步骤一:新建执行器项目

这里只要copy 项目xxl-job-executor-sample-springboot 即可

  • 步骤二:修改执行器appname
logging:
  config: "classpath:logback.xml"
server:
  port: 8081
xxl:
  job:
    accessToken: ''
    admin:
      addresses: http://127.0.0.1:8080/xxl-job-admin
    executor:
      appname: xxl-job-executor-test
      ip: ''
      logpath: /data/applogs/xxl-job/jobhandler
      logretentiondays: -1
      port: 9999


  • 步骤三:新建一个任务Handler:TestJobHandler
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.annotation.JobHandler;
import com.xxl.job.core.log.XxlJobLogger;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * <p>Title:lyy-modular-saas-api</p>
 * <p>Desc: 运行模式为Bean模式</p>
 * 开发步骤:
 * 1、继承"IJobHandler":“com.xxl.job.core.handler.IJobHandler”;
 * 2、注册到Spring容器:添加“@Component”注解,被Spring容器扫描为Bean实例;
 * 3、注册到执行器工厂:添加“@JobHandler(value="自定义jobhandler名称")”注解,注解value值对应的是调度中心新建任务的JobHandler属性的值。
 * 4、执行日志:需要通过 "XxlJobLogger.log" 打印执行日志;
 * @author Jerry
 * @version 1.0
 * @since 2019/8/8
 */
@JobHandler(value = "testJobHandler")
@Component
public class TestJobHandler extends IJobHandler {

    @Override
    public ReturnT<String> execute(String s) throws Exception {
        XxlJobLogger.log("################# start to job test");

        for (int i = 0; i < 5; i++) {
            XxlJobLogger.log("beat at:" + i);
            TimeUnit.SECONDS.sleep(2);
        }
        XxlJobLogger.log("################# end to job test");
        return SUCCESS;
    }
}

  • 步骤四:执行器管理,新增执行器
actuator.jpg
  • 步骤五:任务管理,新建调度任务
task.jpg
  • 步骤六:执行任务,并查看日志
console_log.jpg

可以看到,这里成功执行了先前自定义的调度任务,并输出了log,结合代码分析,任务执行的log通过XxlJobLogger.log记录并写到日志表内,调度中心后台读取日志表得到详细的log

GLUE模式

任务以源码方式维护在调度中心,支持通过Web IDE在线更新,实时编译和生效,因此不需要指定JobHandler

拿GULE模式(JAVA)来解释,每一个GLUE模式的代码,在“执行器”接受到调度请求时,会通过Groovy类加载器加载出代码,并实例化成Java对象,同时注入此代码中声明的Spring服务(这里要确保代码中所引用的类或服务在“执行器项目中存在”),接着调用该对象的execute方法,执行具体的任务逻辑。

  • 步骤一:任务管理,新建调度任务

task_glue.jpg
  • 步骤二:点击操作,选择GLUE IDE,进入Web Ide界面

web_ide.jpg

任务执行失败-邮件告警

  • 步骤一:在之前 3.3.1 更改配置中,有相关邮件报警的发送方配置,配置即可
### 这里是用腾讯企业邮箱测试
spring.mail.host=smtp.exmail.qq.com
spring.mail.port=465
spring.mail.username=error@leyaoyao.com
spring.mail.password=123456

  • 步骤二:在 任务管理,新建调度任务中,配置接收告警邮件的邮箱,多个用“,”隔开即可

相关的处理逻辑在调度中心的JobFailMonitorHelper类中

分片广播&动态分片

执行器集群部署时,任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发对应集群中所有执行器执行一次任务,同时系统自动传递分片参数;可根据分片参数开发分片任务。

使用场景:

  1. 分片任务场景:10个执行器的集群来处理10w条数据,每台机器只需要处理1w条数据,耗时降低10倍;

  2. 广播任务场景:广播执行器机器运行shell脚本、广播集群节点进行缓存更新等

  • 步骤一:执行器集群部署,暂设置为2个节点

    执行器管理可以看到:一个APP有两个机器地址

    actuators.jpg
  • 步骤二:新增or更新任务,更改路由策略为分片广播

    broadcast.jpg
  • 步骤三:编写业务代码

    // 分片参数
    ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo();
    log.info("分片参数:当前分片序号 = {}, 总分片数 = {}", shardingVO.getIndex(), shardingVO.getTotal());
    XxlJobLogger.log("分片参数1:当前分片序号 = {}, 总分片数 = {}", shardingVO.getIndex(), shardingVO.getTotal());
    
    // 业务逻辑
    // ...
    
    
  • 步骤四:执行任务,查看运行日志

    # 节点1
    2019-08-09 09:21:38 [com.xxl.job.executor.service.jobhandler.ShardingJobHandler#execute]-[30]-[Thread-14] 分片参数:当前分片序号 = 0, 总分片数 = 2
    2019-08-09 09:21:38 [com.xxl.job.executor.service.jobhandler.ShardingJobHandler#execute]-[35]-[Thread-14] 第 0 片, 命中分片开始处理
    2019-08-09 09:21:38 [com.xxl.job.executor.service.jobhandler.ShardingJobHandler#execute]-[37]-[Thread-14] 第 1 片, 忽略
    
    # 节点2
    2019-08-09 09:21:38 [com.lyyopen.saas.service.jobhandler.ShardingJobHandler#execute]-[29]-[Thread-16] 分片参数1:当前分片序号 = 1, 总分片数 = 2
    2019-08-09 09:21:38 [com.lyyopen.saas.service.jobhandler.ShardingJobHandler#execute]-[36]-[Thread-16] 第 0 片, 忽略
    2019-08-09 09:21:38 [com.lyyopen.saas.service.jobhandler.ShardingJobHandler#execute]-[34]-[Thread-16] 第 1 片, 命中分片开始处理
    
    

    可以看到一个集群的两个节点都收到了分片调度请求,业务逻辑即可针对对应的分片序号进行业务逻辑分片处理!

六、架构设计

设计思想

  • 实现 调度+任务 两者解耦

  • 调度行为在调度中心(admin后台),负责发起调度请求

  • 任务抽象化为一个个JobHandler,交由“执行器”统一管理,“执行器”负责接收调度请求并找到对应的JobHandler,执行具体的业务逻辑(execute)

系统组成

  • 调度模块(调度中心): 负责管理调度信息,按照调度配置发出调度请求,自身不承担业务代码。调度系统与任务解耦,提高了系统可用性和稳定性,同时调度系统性能不再受限于任务模块; 支持可视化、简单且动态的管理调度信息,包括任务新建,更新,删除,GLUE开发和任务报警等,所有上述操作都会实时生效,同时支持监控调度结果以及执行日志,支持执行器Failover。
  • 执行模块(执行器): 负责接收调度请求并执行任务逻辑。任务模块专注于任务的执行等操作,开发和维护更加简单和高效; 接收“调度中心”的执行请求、终止请求和日志请求等。

架构图

architecture.png

架构特性

  • 解耦

    调度中心 通过类rpc的调用模式,调用执行器暴露的远程服务;

    详见日志远程调用源码:

    com.xxl.rpc.remoting.invoker.reference.XxlRpcReferenceBean#getObject
    
    
  • 线程池

    调度采用线程池方式实现,避免用单线程出现阻塞而导致任务调度延迟;

  • 并行调度

    调度模块采用并行机制,而调度传递到执行器则是串行执行

  • 多种路由策略

  • 过期处理策略

    过期5s内立即触发一次,过期超5s则忽略

  • 日志回调服务

    “执行器”在收到调度请求并执行任务后,会将任务的执行结果回调通知给调度中心,调度中心再对应更新日志表。

    执行器触发回调线程处理源码:

    com.xxl.job.core.thread.TriggerCallbackThread#doCallback
    
    

    调度中心接受回调源码:

    com.xxl.job.admin.service.impl.AdminBizImpl#callback(com.xxl.job.core.biz.model.HandleCallbackParam)
    
    
  • 任务集群

    执行器如若集群部署,调度中心将会感知到在线的所有执行器

  • 全异步化

    • 异步调度:调度中心每次任务触发时仅发送一次调度请求,该调度请求首先推送“异步调度队列”,然后异步推送给远程执行器
    • 异步执行:执行器会将请求存入“异步执行队列”并且立即响应调度中心,异步运行。
  • 轻量级设计

    XXL-JOB调度中心中每个JOB逻辑非常 “轻”,在全异步化的基础上,单个JOB一次运行平均耗时基本在 "10ms" 之内(基本为一次请求的网络开销);因此,可以保证使用有限的线程支撑大量的JOB并发运行

通讯流程分析

  • 调度中心执行器发送http调度请求: “执行器”中接收请求的服务,实际上是一台内嵌Server,默认端口9999;
  • 执行器执行任务逻辑
  • 执行器http回调调度中心调度结果: 调度中心中接收回调的服务,是针对执行器开放一套API服务;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容