xxl-job
XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展,其中“XXL”是主要作者,大众点评许雪里名字的缩写
官方地址:https://www.xuxueli.com/xxl-job/
# 和ElasticJob的区别
# 相同点
- E-Job和X-job都有普遍的用户基础和完整的技术文档,都能满足定时任务的基本功能需求
# 不同点
- X-Job 侧重的业务实现的简单和管理的方便,学习成本简单,失败策略和路由策略丰富,推荐使用在“用户基数相对少,服务器数量在一定范围内”的情景下使用
- E-Job 关注的是数据,增长了弹性扩容和数据分片的思路,以便于更大限度的利用分布式服务器的资源,可是学习成本相对高些,推荐在“数据量庞大,且部署服务器数量较多”时使用算法
- X-Job采用了中心化思想的架构,而E-Job采用了无中心的架构
# 功能特性
# 简单灵活
- 提供Web页面对任务进行管理,管理系统支持用户管理、权限控制;
- 支持容器部署;
- 支持通过通用HTTP提供跨平台任务调度;
# 丰富的任务管理功能
- 支持页面对任务CRUD操作;
- 支持在页面编写脚本任务、命令行任务、Java代码任务并执行;
- 支持任务级联编排,父任务执行结束后触发子任务执行;
- 支持设置任务优先级;
- 支持设置指定任务执行节点路由策略,包括轮询、随机、广播、故障转移、忙碌转移等;
- 支持Cron方式、任务依赖、调度中心API接口方式触发任务执行
# 高性能
- 调度中心基于线程池多线程触发调度任务,快任务、慢任务基于线程池隔离调度,提供系统性能和稳定性;
- 任务调度流程全异步化设计实现,如异步调度、异步运行、异步回调等,有效对密集调度进行流量削峰;
# 高可用
- 任务调度中心、任务执行节点均 集群部署,支持动态扩展、故障转移
- 支持任务配置路由故障转移策略,执行器节点不可用是自动转移到其他节点执行
- 支持任务超时控制、失败重试配置
- 支持任务处理阻塞策略:调度当任务执行节点忙碌时来不及执行任务的处理策略,包括:串行、抛弃、覆盖策略
# 易于监控运维
- 支持设置任务失败邮件告警,预留接口支持短信、钉钉告警;
- 支持实时查看任务执行运行数据统计图表、任务进度监控数据、任务完整执行日志;
# 整体架构设计
将调度行为抽象形成“调度中心”公共平台,而平台自身并不承担业务逻辑,“调度中心”负责发起调度请求;
将任务抽象成分散的JobHandler,交由“执行器”统一管理,“执行器”负责接收调度请求并执行对应的JobHandler中业务逻辑,因此,“调度”和“任务”两部分可以相互解耦,提高系统整体稳定性和扩展性;
# 调度模块(调度中心)
负责管理调度信息,按照调度配置发出调度请求,自身不承担业务代码
调度系统与任务解耦,提高了系统可用性和稳定性,同时调度系统性能不再受限于任务模块; 支持可视化、简单且动态的管理调度信息,包括任务新建,更新,删除,任务报警等,所有上述操作都会实时生效,同时支持监控调度结果以及执行日志,支持执行器Failover
# 功能
- 任务管理:对调度的任务进行触发时间等配置
- 日志管理:查看调度的日志情况
- 执行器管理:管理接入的业务模块
- 其它,比如用户权限配置和运行统计报表等功能
# 执行模块(执行器)
负责接收调度请求并执行任务逻辑。
任务模块专注于任务的执行等操作,开发和维护更加简单和高效; 接收“调度中心”的执行请求、终止请求和日志请求等
# 工作原理
- 任务执行器根据配置的调度中心的地址,自动注册到调度中心
- 达到任务触发条件,调度中心下发任务
- 执行器基于线程池执行任务,并把执行结果放入内存队列中、把执行日志写入日志文件中
- 执行器的回调线程消费内存队列中的执行结果,主动上报给调度中心
- 当用户在调度中心查看任务日志,调度中心请求任务执行器,任务执行器读取任务日志文件并返回日志详情
# 部署调度中心
# 初始化数据库
https://gitee.com/xuxueli0323/xxl-job
地址下载项目源码并解压, SQL脚本位置在
/xxl-job/doc/db/tables_xxl_job.sql
# 创建用户并授权
mysql> source /opt/tables_xxl_job.sql;
mysql> CREATE USER 'xxl'@'%' IDENTIFIED BY 'xxl';
mysql> GRANT ALL ON xxl_job.* TO 'xxl'@'%' IDENTIFIED BY 'xxl' WITH GRANT OPTION;
# 源码结构
解压源码,按照maven格式将源码导入IDE, 使用maven进行编译即可
xxl-job-admin:调度中心
xxl-job-core:公共依赖
xxl-job-executor-samples:执行器Sample示例(选择合适的版本执行器,可直接使用,也可以参考其并将现有项目改造成执行器)
:xxl-job-executor-sample-springboot:Springboot版本,通过Springboot管理执行器,推荐这种方式;
:xxl-job-executor-sample-frameless:无框架版本;
# 配置调度中心
调度中心项目是
xxl-job-admin
,作用是统计管理调度平台上面的任务,负责触发以及执行调度任务,并且提供任务管理
# 修改application.properties
调度中心配置内容说明,可以按需进行修改
server.port=8080
#项目访问路径
server.servlet.context-path=/xxl-job-admin
spring.datasource.url=jdbc:mysql://192.168.10.30:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.username=xxl
spring.datasource.password=xxl
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
### 报警邮箱
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
### 调度中心通讯TOKEN [选填]:非空时启用;
xxl.job.accessToken=
### 调度中心国际化配置 [必填]: 默认为 "zh_CN"/中文简体, 可选范围为 "zh_CN"/中文简体, "zh_TC"/中文繁体 and "en"/英文;
xxl.job.i18n=zh_CN
## 调度线程池最大线程配置【必填】
xxl.job.triggerpool.fast.max=200
xxl.job.triggerpool.slow.max=100
### 调度中心日志表数据保存天数 [必填]:过期日志自动清理;限制大于等于7时生效,否则, 如-1,关闭自动清理功能;
xxl.job.logretentiondays=30
# 部署项目
可以在本地直接运行
xxl-job
服务,或者打包后在服务器上面运行
# 启动服务
运行项目后访问 http://192.168.10.30:8080/xxl-job-admin ,该地址执行器将会使用到,作为回调地,默认登录账号
admin/123456
, 登录后运行界面如下图所示
# Docker部署
# 创建docker-comopose.yml
version: '3'
services:
xxl-job-admin:
image: xuxueli/xxl-job-admin:2.3.0
restart: always
container_name: xxl-job-admin
environment:
PARAMS: '--spring.datasource.url=jdbc:mysql://192.168.10.30:3306/xxl_job?Unicode=true&characterEncoding=UTF-8 --spring.datasource.username=root --spring.datasource.password=root'
ports:
- 8080:8080
volumes:
- ./data/applogs:/data/applogs
# 启动服务
docker-compose up -d
# 部署执行器
# 引入依赖
在项目pom文件中引入了 “xxl-job-core” 的maven依赖;
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.3.0</version>
</dependency>
# 执行器配置文件
如调度中心集群部署存在多个地址则用逗号分隔
### 调度中心部署根地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
xxl.job.admin.addresses=http://192.168.10.30:8080/xxl-job-admin
### 执行器通讯TOKEN [选填]:非空时启用;
xxl.job.accessToken=
### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
xxl.job.executor.appname=xxl-job-executor-task
### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
xxl.job.executor.address=
### 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
xxl.job.executor.ip=
### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
xxl.job.executor.port=9999
### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
xxl.job.executor.logretentiondays=30
# 配置执行器
@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;
}
}
# 启动执行器
本地执行器服务启动后,需要在调度中心配置执行器
注意AppName就是执行器项目配置的配置项,名称可以随意,机器地址采用自动注册就可以
# 作业详情
xxl-job支持很多种任务模式,下面我们挑几个常用的介绍下
# BEAN模式
Bean模式任务,支持基于方法的开发方式,每个任务对应一个方法。
# 原理
每个Bean模式任务都是一个Spring的Bean类实例,它被维护在“执行器”项目的Spring容器中。
任务类需要加“@JobHandler (opens new window)(value=”名称”)”注解,因为“执行器”会根据该注解识别Spring容器中的任务。任务类需要继承统一接口“IJobHandler”,任务逻辑在execute方法中开发,因为“执行器”在接收到调度中心的调度请求时,将会调用“IJobHandler”的execute方法,执行任务逻辑。
# 编写执行器
为Job方法添加注解 "@XxlJob(value="自定义jobhandler名称", init = "JobHandler初始化方法", destroy = "JobHandler销毁方法")",注解value值对应的是调度中心新建任务的JobHandler属性的值。
@Component
public class XxlJobBeanMethodTask {
private static final Logger logger = LoggerFactory.getLogger(XxlJobBeanMethodTask.class);
@XxlJob("methodTask")
public void methodTask() throws Exception {
logger.info("methodTask定时任务启动,总分片:{},当前分片:{},参数:{}",XxlJobHelper.getShardIndex(),XxlJobHelper.getShardIndex(),XxlJobHelper.getJobParam());
XxlJobHelper.log("XXL-METHODTASK, methodTask定时任务启动");
//执行成功标志,默认就是执行成功
XxlJobHelper.handleSuccess();
}
}
# 新建调度任务
在任务管理,选择对应的执行器新建任务
新增完成后,可以在列表点击执行一次或者点击启动启动定时任务
# GLUE模式(Java)
任务以源代码方式维护在调度中心,支持通过Web IDE在线更新,实时编译和生效,因此不需要指定JobHandler
# 原理
每个 “GLUE模式(Java)” 任务的代码,实际上是“一个继承自“IJobHandler”的实现类的类代码”,“执行器”接收到“调度中心”的调度请求时,会通过Groovy类加载器加载此代码,实例化成Java对象,同时注入此代码中声明的Spring服务(请确保Glue代码中的服务和类引用在“执行器”项目中存在),然后调用该对象的execute方法,执行任务逻辑。
# 新建调度任务
新建一个任务,注意使用使用GLUE(JAVA)模式
# 在线编辑代码
添加任务后,选择刚刚添加的GLUE(JAVA)任务,选中指定任务后,点击该任务右侧“GLUE IDE”按钮,将会前往GLUE任务的Web IDE界面
在该界面支持对任务代码进行开发(也可以在IDE中开发完成后,复制粘贴到编辑中)
# 路由策略
路由策略属于XXLJob的高级功能,可以控制执行器执行的策略
# 准备工作
启动多台XXL执行器的服务,我们启动三台服务,并查看控制台的注册情况
# 第一个
当选择该策略时,会选择执行器注册地址的第一台机器执行,如果第一台机器出现故障,则调度任务失败
# 查看客户端日志
我们查看客户端日志,发现服务器已经调度了第一个节点
查看执行器日志
# 关闭客户端
我们通过执行器的注册节点发现,第一台服务器的执行器是
8089
端口的服务器,我们将8089
服务器给关闭,然后查看执行器日志,我们发现服务执行失败
# 容错策略
但是并不会一直报错,等一段时间控制台会将已经挂掉的节点剔除出去,第一个节点已经变成了
8189
就可以继续执行了
# 最后一个
当选择该策略时,会选择执行器注册地址的最后一台机器执行,如果最后一台机器出现故障,则调度任务失败,测试方式如上
# 轮询
当选择该策略时,会按照执行器注册地址轮询分配任务,如果其中一台机器出现故障,调度任务失败,任务不会转移
# 随机
当选择该策略时,会按照执行器注册地址随机分配任务,如果其中一台机器出现故障,调度任务失败,任务不会转移
# 一致性HASH
当选择该策略时,每个任务按照Hash算法固定选择某一台机器,如果那台机器出现故障,调度任务失败,任务不会转移。
# 最不经常使用
当选择该策略时,会优先选择使用频率最低的那台机器,如果其中一台机器出现故障,调度任务失败,任务不会转移(实践表明效果和轮询策略一致)
# 最近最久未使用
当选择该策略时,会优先选择最久未使用的机器,如果其中一台机器出现故障,调度任务失败,任务不会转移(实践表明效果和轮询策略一致)
# 故障转移
当选择该策略时,按照顺序依次进行心跳检测,如果其中一台机器出现故障,则会转移到下一个执行器,若心跳检测成功,会选定为目标执行器并发起调度
# 忙碌转移
当选择该策略时,按照顺序依次进行空闲检测,如果其中一台机器出现故障,则会转移到下一个执行器,若空闲检测成功,会选定为目标执行器并发起调度
# 分片广播
当选择该策略时,广播触发对应集群中所有机器执行一次任务,同时系统自动传递分片参数,可根据分片参数开发分片任务,如果其中一台机器出现故障,则该执行器执行失败,不会影响其他执行器
XXL的分片是根据启动的客户端进行分片,
分片参数是调度中心自动传递的,不用我们手动传递,且集群中的每个index序号是固定的,即使集群中有项目宕机,也不影响其他项目的index序号,当重启宕机项目时,它的序号还是原先的
服务启动后会自动的进行服务分片,分片会根据不同的节点进行调度
# 节点1
# 节点2
# 节点3
# 阻塞策略
调度过于密集执行器来不及处理时的处理策略
# 准备工作
修改代码加入延时操作模拟定时任务执行超时
@Component
public class XxlJobBeanMethodTask {
private static final Logger logger = LoggerFactory.getLogger(XxlJobBeanMethodTask.class);
@XxlJob("methodTask")
public void methodTask() throws Exception {
logger.info("methodTask定时任务启动,总分片:{},当前分片:{},参数:{}",XxlJobHelper.getShardTotal(),XxlJobHelper.getShardIndex(),XxlJobHelper.getJobParam());
XxlJobHelper.log("XXL-METHODTASK, methodTask定时任务启动");
//休眠10S模拟服务执行超时
Thread.sleep(10000);
//执行成功标志,默认就是执行成功
XxlJobHelper.handleSuccess();
}
}
# 单机串行
调度请求进入单机执行器后,调度请求进入FIFO队列并以串行方式运行
# 测试
将阻塞策略设置为
单机串行
,当执行的任务耗时过长,同一个任务将进入执行队列等待执行,这个时候将任务停止掉,执行器还会将队列中的任务执行完成
但是执行器还在继续执行队列中的任务,直到所有任务执行完成
等一段时间后,我们发现定时任务状态都已经正常了
# 丢弃后续调度
调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败
# 测试
将阻塞策略设置为
丢弃后续调度
,执行器发现调度未完成则直接设置为失败
# 覆盖之前调度
调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务
# 测试
将阻塞策略设置为
覆盖之前调度
,执行器发现有任务正在调度直接将该任务设置为失败,任何进行后续调度任务
# 资料
xxl-job学习笔记(一)——使用xxl-job搭建作业调度平台 (opens new window)
XXL-JOB学习笔记(一) (opens new window)
XXL-JOB学习笔记(二) (opens new window)
xxl-job配置遇到的坑:java.net.BindException: Address already in use: bind (opens new window)