阅读收获
1. 了解常用的分布式应用定时任务框架
2. 掌握xxl-job定时任务框架搭建及使用
常用的分布式任务调度系统
-
xxl-job
: 是大众点评员工徐雪里于2015年发布的分布式任务调度平台,是一个轻量级分布式任务调度框架,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。 -
Quartz
:Java事实上的定时任务标准。但Quartz关注点在于定时任务而非数据,并无一套根据数据处理而定制化的流程。虽然Quartz可以基于数据库实现作业的高可用,但缺少分布式并行调度的功能 -
TBSchedule
:阿里早期开源的分布式任务调度系统。代码略陈旧,使用timer而非线程池执行任务调度。众所周知,timer在处理异常状况时是有缺陷的。而且TBSchedule作业类型较为单一,只能是获取/处理数据一种模式。还有就是文档缺失比较严重 -
elastic-job(E-Job)
:当当开发的弹性分布式任务调度系统,功能丰富强大,采用zookeeper实现分布式协调,实现任务高可用以及分片,目前是版本2.15,并且可以支持云开发 -
Saturn
:是唯品会自主研发的分布式的定时任务的调度平台,基于当当的elastic-job 版本1开发,并且可以很好的部署到docker容器上。
- 共同点:
- E-Job和X-job都有广泛的用户基础和完整的技术文档,都能满足定时任务的基本功能需求。
- 不同点
- X-Job 侧重的业务实现的简单和管理的方便,学习成本简单,失败策略和路由策略丰富。推荐使用在“用户基数相对少,服务器数量在一定范围内”的情景下使用
- E-Job 关注的是数据,增加了弹性扩容和数据分片的思路,以便于更大限度的利用分布式服务器的资源。但是学习成本相对高些,推荐在“数据量庞大,且部署服务器数量较多”时使用
xxl-job
设计思想:
- 将调度行为抽象形成“调度中心”公共平台,而平台自身并不承担业务逻辑,“调度中心”负责发起调度请求。
- 将任务抽象成分散的JobHandler,交由“执行器”统一管理,“执行器”负责接收调度请求并执行对应的JobHandler中业务逻辑。
- 因此,“调度”和“任务”两部分可以相互解耦,提高系统整体稳定性和扩展性;
本文使用版本为2.3.0
1.下载 源码
2. 初始化 数据库
- 执行tables_xxl_job.sql文件初始化
- 调度中心支持集群部署,集群情况下各节点务必连接同一个mysql实例;
- 如果mysql做主从,调度中心集群节点务必强制走主库;
- 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:系统用户表;
3. 配置部署 调度中心
- 调度中心项目:xxl-job-admin
- 作用: 统一管理任务调度平台上调度任务,负责触发调度执行,并且提供任务管理平台。
3.1 主要修改配置:
- 修改数据源
- 修改报警邮箱
- 调度中心通讯TOKEN
### xxl-job, datasource
spring.datasource.url=jdbc:mysql://localhost:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
### xxl-job, email
spring.mail.host=smtp.qq.com
spring.mail.port=25
spring.mail.username=xxx@qq.com
spring.mail.from=xxx@qq.com
spring.mail.password=你的mail token
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
### 调度中心通讯TOKEN [选填]:非空时启用;
### xxl-job, access token
xxl.job.accessToken=ljwtoken
3.2. 打包部署“调度中心”
- mvn clean package -Dmaven.test.skip=true
3.3. 访问调度中心
- 调度中心访问地址:
http://localhost:8080/xxl-job-admin
(该地址执行器配置文件xxl.job.admin.addresses将会使用到,作为回调地址
) - 默认登录账号 “admin/123456”, 登录后运行界面如下图所示。
- 调度中心集群部署参考
4. 配置部署 执行器项目
- “执行器”项目:xxl-job-executor-sample-springboot (提供多种版本执行器供选择,现以 springboot 版本为例,
可直接使用,也可以参考其并将现有项目改造成执行器
) - 作用: 负责接收“调度中心”的调度并执行;可直接部署执行器,也可以将执行器集成到现有业务项目中。
- 原理:
- 执行器实际上是一个内嵌的Server,默认端口9999(配置项:xxl.job.executor.port)。
- 在项目启动时,执行器会通过“@JobHandler”识别Spring容器中“Bean模式任务”,以注解的value属性为key管理起来。
- “执行器”接收到“调度中心”的调度请求时,如果任务类型为“Bean模式”,将会匹配Spring容器中的“Bean模式任务”,然后调用其execute方法,执行任务逻辑。如果任务类型为“GLUE模式”,将会加载GLue代码,实例化Java对象,注入依赖的Spring服务(注意:Glue代码中注入的Spring服务,必须存在与该“执行器”项目的Spring容器中),然后调用execute方法,执行任务逻辑。
4.1 maven依赖
- 执行器项目需要引入 “xxl-job-core” 的maven依赖,版本要和注册中心版本一致
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
4.2 修改配置文件:
- xxl-job-executor-sample-springboot/src/main/resources/application.properties
- 修改调度中心部署跟地址
- 需修改或自定义
- xxl.job.admin.addresses 地址
- xxl.job.executor.appname 自定义名称,后台配置必须对应
- xxl.job.executor.ip 当前电脑Ip,或部署项目的电脑Ip
- xxl.job.executor.port 端口
- xxl.job.accessToken 自定义的token,要和admin模块配置的一致
### 调度中心部署跟地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
### xxl-job, access token 自定义的token,要和admin模块配置的一致
xxl.job.accessToken=ljwtoken
### xxl-job executor appname 自定义名称,后台配置必须对应
xxl.job.executor.appname=xxl-job-executor-sample
### xxl-job executor registry-address: default use address to registry , otherwise use ip:port if address is null
xxl.job.executor.address=
### xxl-job executor server-info
xxl.job.executor.ip=127.0.0.1
xxl.job.executor.port=9999
4.3 执行器组件配置 要配置执行器组件,配置文件参考地址: /xxl-job/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/core/config/XxlJobConfig.java
@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;
}
4.4 部署执行器项目
- 如果已经正确进行上述配置,将执行器项目编译打部署,我们使用springboot项目,打包运行xxl-job-executor-sample-springboot即可
- mvn clean package -Dmaven.test.skip=true
- 执行器项目集群部署参考
5. GLUE模式(Java)开发一个任务
- GLUE模式(Java)原理:任务以源码方式维护在调度中心;该模式的任务实际上是一段继承自IJobHandler的Java类代码并 “groovy” 源码方式维护,它在执行器项目中运行,可使用@Resource/@Autowire注入执行器里中的其他服务(请确保Glue代码中的服务和类引用在“执行器”项目中存在),然后调用该对象的execute方法,执行任务逻辑。
- GLUE模式(Java)就是在界面编写代码即可,不用在执行器项目中编写代码
5.1 新建任务
- 登录调度中心,点击下图所示“新增”按钮,新建示例任务。然后,参考下面截图中任务的参数配置,点击保存。
5.2 “GLUE模式(Java)” 任务开发
- 请点击任务右侧 “GLUE IDE” 按钮,进入 “GLUE编辑器开发界面” ,见下图。“GLUE模式(Java)” 运行模式的任务默认已经初始化了示例任务代码,即打印Hello World。
5.3 触发执行
- 请点击任务右侧 “执行一次” 按钮,可手动触发一次任务执行(通常情况下,通过配置Cron表达式进行任务调度触发)。
5.4 查看日志:
- 请点击任务右侧 “调度日志” 按钮,可前往任务日志界面查看任务日志。
- 在任务日志界面中,可查看该任务的历史调度记录以及每一次调度的任务调度信息、执行参数和执行信息。运行中的任务点击右侧的“执行日志”按钮,可进入日志控制台查看实时执行日志。
6. BEAN模式开发一个任务
- BEAN模式原理:任务以JobHandler方式维护在执行器端;需要结合 “JobHandler” ,任务类需要加“@JobHandler(value=“名称”)”注解,因为“执行器”会根据该注解识别Spring容器中的任务。任务类需要继承统一接口“IJobHandler”,任务逻辑在execute方法中开发,因为“执行器”在接收到调度中心的调度请求时,将会调用“IJobHandler”的execute方法,执行任务逻辑。
6.1 类形式
6.1.1 优缺点
- Bean模式任务,支持基于类的开发方式,每个任务对应一个Java类。
- 优点:
- 不限制项目环境,兼容性好。即使是无框架项目,如main方法直接启动的项目也可以提供支持
- 缺点:
- 每个任务需要占用一个Java类,造成类的浪费;
- 不支持自动扫描任务并注入到执行器容器,需要手动注入。
- 优点:
6.1.2 执行器项目中,开发Job类
- 1、开发一个继承自"com.xxl.job.core.handler.IJobHandler"的JobHandler类,实现其中任务方法。
- 2、手动通过如下方式注入到执行器容器。
- :XxlJobExecutor.registJobHandler(“demoJobHandler”, new DemoJobHandler());
注: 版本v2.2.0 移除旧类注解@JobHandler,推荐使用基于方法注解@XxlJob的方式进行任务开发;(如需保留类注解JobHandler使用方式,可以参考旧版逻辑定制开发);
public class MyJobHandler extends IJobHandler {
@Override
public void execute() throws Exception {
XxlJobHelper.log("11==============MyJobHandler=================");
System.out.println("22==============MyJobHandler=================");
}
}
注入myJobHandler:
@SpringBootApplication
public class XxlJobExecutorApplication {
public static void main(String[] args) {
SpringApplication.run(XxlJobExecutorApplication.class, args);
XxlJobExecutor.registJobHandler("myJobHandler", new MyJobHandler());
}
}
6.1.3 调度中心,新建调度任务
- JobHandler填写@JobHandler的value值
- 新建后可以测试运行一次查看日志是否成功
6.2 方法形式
6.2.1 优缺点
- 基于方法开发的任务,底层会生成JobHandler代理,和基于类的方式一样,任务也会以JobHandler的形式存在于执行器任务容器中。
- 优点:
- 每个任务只需要开发一个方法,并添加@XxlJob注解即可,更加方便、快速。
- 支持自动扫描任务并注入到执行器容器。
- 缺点:要求Spring容器环境
- 优点:
6.2.2 执行器项目中,开发Job方法
-
- 任务开发:在Spring Bean实例中,开发Job方法
-
- 注解配置:为Job方法添加注解 “@XxlJob(value=“自定义jobhandler名称”, init = “JobHandler初始化方法”, destroy = “JobHandler销毁方法”)”,注解value值对应的是调度中心新建任务的JobHandler属性的值。
-
- 执行日志:需要通过 “XxlJobHelper.log” 打印执行日志
-
- 任务结果: 默认任务结果为 “成功” 状态,不需要主动设置;如有诉求,比如设置任务结果为失败,可以通过 “XxlJobHelper.handleFail/handleSuccess” 自主设置任务结果
@Configuration
public class MyXxlJobConfig {
@XxlJob("myXxlJobConfig")
public boolean demoJobHandler() throws Exception {
XxlJobHelper.log("========myXxlJobConfig============");
System.out.println("========myXxlJobConfig============");
//ReturnT无作用
//return new ReturnT(200, "hahahahah");
return XxlJobHelper.handleSuccess("myXxlJobConfig hello world");
}
@XxlJob("myfaile")
public boolean myfaile() throws Exception {
XxlJobHelper.log("========myfaile============");
System.out.println("========myfaile============");
return XxlJobHelper.handleFail("myfaile hello world");
}
/**
* 5、生命周期任务示例:任务初始化与销毁时,支持自定义相关逻辑;init只在第一次调度时执行一次。destroy每次调度都执行
*/
@XxlJob(value = "demoJobHandler2", init = "init", destroy = "destroy")
public void demoJobHandler2() throws Exception {
System.out.println("========demoJobHandler2============");
XxlJobHelper.log("XXL-JOB, Hello World.");
}
public void init() {
System.out.println("========init============");
logger.info("init");
}
public void destroy() {
System.out.println("========destory============");
logger.info("destory");
}
}
6.2.3 调度中心,新建调度任务
- 修改JobHandler为@XxlJob(“myfaile”)名称即可,其他照样新建任务就行
- 新建后可以测试运行一次查看日志是否成功
- myfaile方法返回为return XxlJobHelper.handleFail(“myfaile hello world”);是错误的,但是任务是调度成功的
注:
任务执行结果说明
系统根据以下标准判断任务执行结果
- 任务执行成功:XxlJobHelper.handleSuccess()
- 任务执行失败:XxlJobHelper.handleFail()
- 注:任务调度室成功的,这里只是业务返回结果的判断
终止运行中的任务
仅针对执行中的任务。 在任务日志界面,点击右侧的“终止任务”按钮,将会向本次任务对应的执行器发送任务终止请求,将会终止掉本次任务,同时会清空掉整个任务执行队列。
任务终止时通过 “interrupt” 执行线程的方式实现, 将会触发 “InterruptedException” 异常。因此如果JobHandler内部catch到了该异常并消化掉的话, 任务终止功能将不可用。
因此, 如果遇到上述任务终止不可用的情况, 需要在JobHandler中应该针对 “InterruptedException” 异常进行特殊处理 (向上抛出) , 正确逻辑如下:
try{
// do something
} catch (Exception e) {
if (e instanceof InterruptedException) {
throw e;
}
logger.warn("{}", e);
}
而且,在JobHandler中开启子线程时,子线程也不可catch处理"InterruptedException",应该主动向上抛出。
任务终止时会执行对应JobHandler的"destroy()"方法,可以借助该方法处理一些资源回收的逻辑。
任务超时控制
- 支持设置任务超时时间,任务运行超时的情况下,将会主动中断任务;
- 需要注意的是,任务超时中断时与任务终止机制(可查看章节“终止运行中的任务”)类似,也是通过 “interrupt” 中断任务,因此业务代码需要将 “InterruptedException” 外抛,否则功能不可用。
最后
- :有收获的,点赞鼓励!
- :收藏文章,方便回看!
- :评论交流,互相进步!
作者:小伙子vae
链接:一文搞懂⭐java中的定时任务框架-分布式(xxl-job) - 掘金