xxl-job

一.xxl-job介绍

在平时的业务场景中,经常有一些场景需要使用定时任务,比如:时间驱动的场景:某个时间点发送优惠券,发送短信等等;批量处理数据:批量统计上个月的账单,统计上个月销售数据等等;固定频率的场景:每隔5分钟需要执行一次。

定时任务在开发中并不少见,对于现在快速消费的时代,每天都需要发送各种推送,消息都需要依赖定时任务去完成,应用非常广泛。在Java中,传统的定时任务实现方案,比如Timer,Quartz等都或多或少存在一些问题:不支持集群、不支持统计、没有管理平台、没有失败报警、没有监控等等。

而在现有的分布式的架构中,有一些场景需要分布式任务调度,比如:同一个服务多个实例的任务存在互斥时,需要统一的调度;任务调度需要支持高可用、监控、故障告警;需要统一管理和追踪各个服务节点任务调度的结果,需要记录保存任务属性信息等。而目前比较主流的分布式任务调度平台有elasticjob和xxl-job。

elasticjob由当当网开源,elasticjob是采用zookeeper实现分布式协调,任务高可用以及分片。

elasticjob的初衷是为了面对高并发复杂的业务,即使是在业务量大,服务器多的时候也能做好任务调度,尽可能的利用服务器的资源。使用ZooKeeper使其具有高可用、一致性的,而且还具有良好的扩展性。官网上写elasticjob是无中心化的,通过ZooKeeper的选举机制选举出主服务器,如果主服务器挂了,会重新选举新的主服务器。因此elasticjob具有良好的扩展性和可用性,但是使用和运维有一定的复杂。

xxl-job则相反,是通过一个中心式的调度平台,调度多个执行器执行任务,调度中心通过DB锁保证集群分布式调度的一致性,这样扩展执行器会增大DB的压力,但是如果实际上这里数据库只是负责任务的调度执行,如果没有大量执行器和任务的情况下,是不会造成数据库压力的。

xxl-job 目标是一种跨平台、跨语言的任务调度规范和协议。针对Java应用,可以直接通过官方提供的调度中心与执行器,方便快速的接入和使用调度中心。针对非Java应用,可借助 xxl-job 的标准 RESTful API 方便的实现多语言支持。

 xxl-job将调度行为抽象形成“调度中心”公共平台,而平台自身并不承担业务逻辑,“调度中心”负责发起调度请求。将任务抽象成分散的JobHandler,交由“执行器”统一管理,“执行器”负责接收调度请求并执行对应的JobHandler中业务逻辑。因此,“调度”和“任务”两部分可以相互解耦,提高系统整体稳定性和扩展性;

xxl-job表结构:

- xxl_job_lock:任务调度锁表;

- xxl_job_group:执行器信息表,维护任务执行器信息;

- xxl_job_info:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;

- xxl_job_log:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;

- xxl_job_log_report:调度日志报表:用户存储XXL-JOB任务调度日志的报表,调度中心报表功能页面会用到;

- xxl_job_logglue:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;

- xxl_job_registry:执行器注册表,维护在线的执行器和调度中心机器地址信息;

- xxl_job_user:系统用户表;

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

执行模块(执行器):负责接收调度请求并执行任务逻辑。任务模块专注于任务的执行等操作,开发和维护更加简单和高效;接收“调度中心”的执行请求、终止请求和日志请求等。

调度中心每次进行任务调度,都会记录一条任务日志,任务日志主要包括三部分内容:任务信息(包括“执行器地址”、“JobHandler”和“执行参数”等属性,点击任务ID按钮可查看,根据这些参数,可以精确的定位任务执行的具体机器和任务代码);调度信息(包括“调度时间”、“调度结果”和“调度日志”等,根据这些参数,可以了解“调度中心”发起调度请求时具体情况);执行信息(包括“执行时间”、“执行结果”和“执行日志”等,根据这些参数,可以了解在“执行器”端任务执行的具体情况)。

针对单次调度,调度日志的属性说明如下:

执行器地址:任务执行的机器地址;

JobHandler:Bean模式表示任务执行的JobHandler名称;

任务参数:任务执行的入参;

调度时间:调度中心,发起调度的时间;

调度结果:调度中心,发起调度的结果,SUCCESS或FAIL;

调度备注:调度中心,发起调度的备注信息,如地址心跳检测日志等;

执行时间:执行器,任务执行结束后回调的时间;

执行结果:执行器,任务执行的结果,SUCCESS或FAIL;

执行备注:执行器,任务执行的备注信息,如异常日志等;

执行日志:任务执行过程中,业务代码中打印的完整执行日志

任务执行器根据配置的调度中心的地址,自动注册到调度中心。

达到任务触发条件,调度中心下发任务。

执行器基于线程池执行任务,并把执行结果放入内存队列中、把执行日志写入日志文件中。

执行器的回调线程消费内存队列中的执行结果,主动上报给调度中心。

当用户在调度中心查看任务日志,调度中心请求任务执行器,任务执行器读取任务日志文件并返回日志详情。

任务依赖:XXL-JOB中每个任务都对应有一个任务ID,同时,每个任务支持设置属性“子任务ID”,因此,通过“任务ID”可以匹配任务依赖关系。当父任务执行结束并且执行成功时,将会根据“子任务ID”匹配子任务依赖,如果匹配到子任务,将会主动触发一次子任务的执行。

二.xxl-job使用

克隆xxl-job-master项目Git pull https://github.com/xuxueli/xxl-job.git执行sql脚本  路径:/xxl-job/doc/db/tables_xxl_job.sql

使用idea打开下载好的项目,下载资源文件,编辑 /xxl-job/xxl-job-admin/src/main/resources/路径下的application.properties

### web
server.port=8080
server.servlet.context-path=/xxl-job-admin

### actuator
management.server.servlet.context-path=/actuator
management.health.mail.enabled=false

### resources
spring.mvc.servlet.load-on-startup=0
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
#mybatis.type-aliases-package=com.xxl.job.admin.core.model

### xxl-job, datasource
spring.datasource.url=jdbc:mysql://192.168.0.215:33306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=cserver265
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

### datasource-pool
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.maximum-pool-size=30
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=HikariCP
spring.datasource.hikari.max-lifetime=900000
spring.datasource.hikari.connection-timeout=10000
spring.datasource.hikari.connection-test-query=SELECT 1
spring.datasource.hikari.validation-timeout=1000

### xxl-job, email
spring.mail.host=smtp.163.com
spring.mail.port=25
spring.mail.username=xxxxxx@163.com
spring.mail.from=xxxxxxxxx@163.com
spring.mail.password=xxxxxxxxxxxxx
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 is zh_CN, and you can choose "zh_CN", "zh_TC" and "en")
xxl.job.i18n=zh_CN

## xxl-job, triggerpool max size
xxl.job.triggerpool.fast.max=200
xxl.job.triggerpool.slow.max=100

### xxl-job, log retention days
xxl.job.logretentiondays=30

启动XxlJobAdminApplication

http://ip: 端口号/xxl-job-admin/

账号:admin     密码:123456

创建一个springboot项目作为执行器,添加xxl-job core依赖

修改资源文件

# web port

server.port=8888

# log config

logging.config=classpath:logback.xml

spring.application.name=xxl-cserver-test

### 调度中心部署跟地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;

xxl.job.admin.addresses=https://192.168.0.156:9999/xxl-job-admin

### 执行器通讯TOKEN [选填]:非空时启用;

xxl.job.accessToken=

### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册

xxl.job.executor.appname=xxl-cserver-test

### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。

xxl.job.executor.address=

### 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";

xxl.job.executor.ip=

### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;

xxl.job.executor.port=9998

### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;

xxl.job.executor.logpath=E:/project/xxl-cserver-test/applogs/jobhandler

### 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;

xxl.job.executor.logretentiondays=10

配置类XxlJobConfig

@Configuration
public class XxlJobConfig {
    private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;
    @Value("${xxl.job.accessToken}")
    private String accessToken;
    @Value("${xxl.job.executor.appname}")
    private String appname;
    @Value("${xxl.job.executor.address}")
    private String address;
    @Value("${xxl.job.executor.ip}")
    private String ip;
    @Value("${xxl.job.executor.port}")
    private int port;
    @Value("${xxl.job.executor.logpath}")
    private String logPath;
    @Value("${xxl.job.executor.logretentiondays}")
    private int logRetentionDays;

    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        logger.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        xxlJobSpringExecutor.setAppname(appname);
        xxlJobSpringExecutor.setAddress(address);
        xxlJobSpringExecutor.setIp(ip);
        xxlJobSpringExecutor.setPort(port);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
        return xxlJobSpringExecutor;
    }
}

任务类XxlJobDemoHandler,使用Bean模式

@Component
public class XxlJobDemoHandler {
    /**
     * Bean模式,一个方法为一个任务
     * 1、在Spring Bean实例中,开发Job方法,方式格式要求为 "public ReturnT<String> execute(String param)"
     * 2、为Job方法添加注解 "@XxlJob(value="自定义jobhandler名称", init = "JobHandler初始化方法", destroy = "JobHandler销毁方法")",注解value值对应的是调度中心新建任务的JobHandler属性的值。
     * 3、执行日志:需要通过 "XxlJobLogger.log" 打印执行日志;
     */
    private Logger logger = LoggerFactory.getLogger(XxlJobDemoHandler.class);
    @XxlJob("demoJobHandler")
    public ReturnT<String> demoJobHandler(String param) throws Exception {
        logger.info("java, Hello World~~~");
        logger.info("param:" + param);
        return ReturnT.SUCCESS;
    }
}

在resources目录下,添加logback.xml文件。

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="true" scanPeriod="1 seconds">
    <contextName>logback</contextName>
    <property name="log.path" value="E:/project/xxl-cserver-test/applogs/jobhandler/xxl-job-executor-sample-springboot.log"/>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}.%d{yyyy-MM-dd}.zip</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n
            </pattern>
        </encoder>
    </appender>
    <root level="info">
        <appender-ref ref="console"/>
        <appender-ref ref="file"/>
    </root>
</configuration>

启动服务后,在http://192.168.0.156:9999/xxl-job-admin/jobgroup,执行器管理模块中新建执行器。

打开任务管理,点击新建

点击操作之后启动即可实现定时任务

查看任务日志,定时任务稳定执行。

可在运行报表中查看成功与失败趋势

参考资源:分布式任务调度平台XXL-JOB

3千字带你搞懂XXL-JOB任务调度平台 - 知乎

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐