分布式调度 XXL-JOB(一)
一、概述
1.1 什么是任务调度
可以思考一下下面业务场景的解决方案:
- 某电商平台需要每天上午10点,下午3点,晚上8点发放一批优惠券;
- 某银行系统需要在信用卡到期还款日的前三天进行短信提醒;
- 某财务系统需要在每天凌晨0:10分结算前一天的财务数据,统计汇总;
以上场景就是任务调度所需要解决的问题。
任务调度是为了自动完成特定的任务,在约定的特定时刻去执行任务的过程。
1.2 为什么需要分布式任务调度
使用 Spring 中提供的注解 @Scheduled
,也能实现调度的功能,在业务类中方法上使用这个注解,然后在启动类上加上 @EnableScheduling
注解。
@Scheduled(cron = "0/20 * * * * ? ")
public void doWork(){
//doSomething
}
感觉 Spring 给我们提供的这个注解可以完成任务调度的功能,好像已经完美解决问题了,为什么还需要分布式呢?
主要有如下这几点原因:
- 高可用:单机版的定时任务调度只能在一台机器上运行,如果程序或者系统出现异常就会导致功能不可用。
- 防止重复执行:在单机模式下,定时任务是没重复执行问题的。但当我们部署了多台服务,同时每台服务又有定时任务时,若不进行合理的控制在同一时间,只有一个定时任务启动执行,这时,定时执行的结果就可能存在混乱和错误了。
- 单机处理极限:原本1分钟内需要处理1万个订单,但是现在需要1分钟内处理10万个订单;原来一个统计需要1小时,现在业务方需要10分钟就统计出来。你也许会说,你也可以多线程、单机多进程处理。的确,多线程并行处理可以提高单位时间的处理效率,但是单机能力毕竟有限(主要是CPU、内存和磁盘),始终会有单机处理不过来的情况。
1.3 XXL-JOB 介绍
- 简介
XXL-Job:是大众点评的分布式任务调度平台,是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。大众点评目前已接入XXL-JOB,该系统在内部已调度约 100 万次,表现优异。目前已有多家公司接入 xxl-job,包括比较知名的大众点评,京东,优信二手车,360金融 (360),联想集团 (联想),易信 (网易)等等。XXL-JOB 开源社区 。
- 系统架构图
xxl-job 源码项目中 /xxl-job-master/doc/XXL-JOB架构图.pptx
有各个版本的架构图记录。
- 设计思想
xxl-job 将调度行为抽象形成调度中心公共平台,而平台自身并不承担业务逻辑,调度中心负责发起调度请求。将任务抽象成分散的JobHandler,交由执行器统一管理,执行器负责接收调度请求并执行对应的 JobHandler 中业务逻辑。因此,调度和任务两部分可以相互解耦,提高系统整体稳定性和扩展性。
二、快速入门
2.1 源码下载
源码下载地址:
- gitHub 地址:https://github.com/xuxueli/xxl-job
- gitee 地址:https://gitee.com/xuxueli0323/xxl-job
解压下载的项目,用 idea 导入项目,可能需要设置 jdk、maven 等相关工具,完成后可以看到项目中有三个模块:
2.2 初始化调度中心数据库
调度中心的运行是需要连接数据库读取相关数据的,在 /xxl-job-master/doc/db/tables_xxl_job.sql
提供了调度数据库初始化SQL脚本。下面示例在本地数据库执行脚本并成功生成相关库和表及初始化数据。
xxl-job 表介绍:
-
xxl_job_registry:执行器注册表,执行器在启动时会将自身信息发给调度中心,然后调度中心存入该表;
-
xxl_job_group:执行器信息表,维护执行器信息;包括执行器的注册名,ip地址等。与上面的 xxl_job_registry 关联使用;
-
xxl_job_info:调度扩展信息表, 用于保存 XXL-JOB 调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
-
xxl_job_lock:任务调度锁表;
-
xxl_job_log:调度日志表, 用于保存 XXL-JOB 任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
-
xxl_job_log_report:调度日志报表:用户存储 XXL-JOB 任务调度日志的报表,调度中心报表功能页面会用到;
-
xxl_job_logglue:任务 GLUE 日志:用于保存 GLUE 更新历史,用于支持 GLUE 的版本回溯功能;
-
xxl_job_user:系统用户表;
2.3 调度中心的配置与部署
2.3.1 调度中心的配置
修改 xxl-job-admin
模块中的的配置文件 /xxl-job-master/xxl-job-admin/src/main/resources/application.properties
,主要把自己的数据库账号密码配置上即可。
2.3.2 调度中心的部署
运行 /xxl-job-master/xxl-job-admin/src/main/java/com/xxl/job/admin/XxlJobAdminApplication.java
程序即可。
Mac 和 Linux 系统可能会出现下面目录不存在问题,
这个问题一般只会出现在 Mac 和Linux 系统上,意思是无法为创建日志文件:/data/applogs/xxl-job/xxl-job-admin.log
等。
-
解决方案1: logback.xml 文件中的 log 路径,改成服务器中当前用户有权限访问,或真实存在的路径。
-
解决方案2: 在你的电脑或者服务器上手动创建 /log 目录,并授权给当前启用项目的用户
如下图在原路径前加上一个 . 表示在项目当前目录下创建日志目录。
重新运行程序后能看到 xxl-job-admin 模块在 8080 端口上启动成功,并在 xxl-job-master 项目下创建了 data 的日志目录文件夹。
此时就可以访问 xxl-job-admin 调度中心:http://localhost:8080/xxl-job-admin ,用户名和秘密为 “admin/123456” (初始化调度中心数据库时保存在 xxl_job_user 表中的用户数据,能看到如下页面表示调度中心部署成功。
2.4 执行器项目的配置与部署
2.4.1 执行器项目的创建
File>New>Project… 新建项目,选择 Spring Initializr,选好 jdk 和项目配置相关信息如下。
2.4.2 添加Maven 依赖
执行器项目需要添加 xxl-job 的核心依赖 xxl-job-core
。
<!-- xxl-job 执行器核心依赖 -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.3.1</version>
</dependency>
2.4.3 配置执行器的参数
在执行器项目的配置文件中 /xxl-bob-test/src/main/resources/application.properties
配置 xxl-job 的相关参数。
### 调度中心部署根地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
### 执行器通讯 TOKEN [选填]:非空时启用;
xxl.job.accessToken=default_token
### 执行器 AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
xxl.job.executor.appname=xxl-job-executor-sample
### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
xxl.job.executor.address=
### 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
xxl.job.executor.ip=127.0.0.1
### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
xxl.job.executor.port=9999
### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
xxl.job.executor.logpath=./data/applogs/xxl-job/jobhandler
### 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
xxl.job.executor.logretentiondays=30
注意:执行器配置的 xxl.job.accessToken 与 调度中心配置的 xxl.job.accessToken 要保持一致。
2.4.4 添加执行器配置文件
添加 xxl-job 的 配置文件 /xxl-bob-test/src/main/java/com/ganming/xxljobtest/config/XxlJobConfig.java
@Configuration
public class XxlJobConfig {
@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() {
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;}
}
2.4.5 添加任务处理类
添加任务处理类/xxl-bob-test/src/main/java/com/ganming/xxljobtest/job/TestXxlJob.java
,交给 Spring 容器管理,在处理方法上贴上 @XxlJob
注解。
@Component
public class TestXxlJob {
@XxlJob("testJobHandler")
public void demoJobHandler() {
LocalDateTime now = LocalDateTime.now();
String format = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println("testJobHandler 定时任务执行完毕:" + format);}
}
2.4.6 运行执行器项目
运行启动类 /xxl-bob-test/src/main/java/com/ganming/xxljobtest/XxlJobTestApplication.java
,会发现 8080 端口被占用,因为我们的调度中心 xxl-job-admin
正运行在 8080 端口。
在 /xxl-bob-test/src/main/resources/application.properties
文件中新增端口的修改。
server.port=8081
重新启动项目,项目启动成功后,可以在控制中心http://localhost:8080/xxl-job-admin中的执行器管理菜单里看到注册的执行器信息。
项目注册的执行器 AppName 为 2.4.3 中所配置的内容。
### 执行器 AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
xxl.job.executor.appname=xxl-job-executor-sample
2.5 配置并执行定时任务
2.5.1 定时任务的配置
登录调度中心,在任务管理中新增任务,设置相关基本配置,注意 JobHandler 和注解中的值保持一致即可。
2.5.2 定时任务执行一次
添加定时任务后,可以在任务管理菜单看到新增的数据,点击 操作 > 执行一次 > 弹框直接保存后可以在页面上看到执行成功提示信息,同时执行器程序中也会打印定时任务的输出内容。
2.5.3 启动定时任务
直接在操作中启动定时任务,可以看到该任务状态变为运行状态,测会按照配置的 cron:0/5 * * * * ?;每隔5秒钟执行一次定时任务。
2.5.4 调度日志与报表
2.6 GLUE模式(Java)
我们在新增任务时,有很多配置参数要填,如下所示:
2.6.1 新增任务配置说明
基础配置
- 执行器:任务绑定的执行器,任务触发调度时将会自动发现注册成功的执行器, 实现任务自动发现功能; 另一方面也可以方便的进行任务分组。每个任务必须绑定一个执行器, 可在【执行器管理】进行设置;
- 任务描述:任务的描述信息,便于任务管理;
- 负责人:任务的负责人;
- 报警邮件:任务调度失败时邮件通知的邮箱地址,支持配置多邮箱地址,配置多个邮箱地址时用逗号分隔;
调度配置
- 调度类型
- 无:该类型不会主动触发调度;
- CRON:该类型将会通过 CRON,触发任务调度;
- Cron:触发任务执行的 Cron 表达式;
- 固定速度:该类型将会以固定速度,触发任务调度;按照固定的间隔时间,周期性触发;
- 固定速度:固件速度的时间间隔,单位为秒;
任务配置
- 运行模式:
- BEAN:任务以 JobHandler 方式维护在执行器端;需要结合 “JobHandler” 属性匹配执行器中任务;
- JobHandler:
- GLUE(Java):任务以源码方式维护在调度中心;该模式的任务实际上是一段继承自 IJobHandler 的 Java 类代码并 “groovy” 源码方式维护,它在执行器项目中运行,可使用 @Resource/@Autowire 注入执行器里中的其他服务;
- GLUE(Shell):任务以源码方式维护在调度中心;该模式的任务实际上是一段 “shell” 脚本;
- GLUE模式(Python):任务以源码方式维护在调度中心;该模式的任务实际上是一段 “python” 脚本;
- …
- BEAN:任务以 JobHandler 方式维护在执行器端;需要结合 “JobHandler” 属性匹配执行器中任务;
- 任务参数:任务执行所需的参数;
高级配置
- 路由策略:当执行器集群部署时,提供丰富的路由策略,包括如下:
- 第一个:固定选择第一个机器;
- 最后一个:固定选择最后一个机器;
- 轮询:;
- 随机:随机选择在线的机器;
- 一致性HASH:每个任务按照Hash算法固定选择某一台机器,且所有任务均匀散列在不同机器上。
- 最不经常使用:使用频率最低的机器优先被选举;
- 最近最久未使用:最久未使用的机器优先被选举;
- 故障转移:按照顺序依次进行心跳检测,第一个心跳检测成功的机器选定为目标执行器并发起调度;
- 忙碌转移:按照顺序依次进行空闲检测,第一个空闲检测成功的机器选定为目标执行器并发起调度;
- 分片广播:广播触发对应集群中所有机器执行一次任务,同时系统自动传递分片参数;可根据分片参数开发分片任务;
- 子任务ID:输入子任务的任务 ID,如存在多个则逗号分隔;每个任务都拥有一个唯一的任务 ID (任务ID可以从任务列表获取),当本任务执行结束并且执行成功时,将会触发子任务 ID 所对应的任务的一次主动调度。
- 调度过期策略:
- 忽略:调度过期后,忽略过期的任务,从当前时间开始重新计算下次触发时间;
- 立即执行一次:调度过期后,立即执行一次,并从当前时间开始重新计算下次触发时间;
- 阻塞处理策略:调度过于密集执行器来不及处理时的处理策略;
- 单机串行(默认):调度请求进入单机执行器后,调度请求进入FIFO队列并以串行方式运行;
- 丢弃后续调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败;
- 覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务;
- 任务超时时间:支持自定义任务超时时间,任务运行超时将会主动中断任务;
- 失败重试次数;支持自定义任务失败重试次数,当任务失败时将会按照预设的失败重试次数主动进行重试;
2.6.2 GLUE模式(Java)
任务以源码方式维护在调度中心,该模式的任务实际上是一段继承自 IJobHandler 的 Java 类代码并 “groovy” 源码方式维护,它在执行器项目中运行,可使用 @Resource/@Autowire 注入执行器里中的其他服务;
添加 Service 业务类
/xxl-bob-test/src/main/java/com/ganming/xxljobtest/service/HelloService.java
@Service
public class HelloService {
public void methodA() {
String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println("执行MethodA的方法:"+ time);
}
public void methodB() {
String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println("执行MethodB的方法:"+ time);
}
}
重启项目。
添加 GLUE 模式任务
功能验证
进行上述操作后,可以在任务管理菜单里看到新添加的 GLUE 模式类型任务,通过操作里的 执行一次 和 启动可以验证其功能。
通过 GLUE 模式能在调度中心控制具体执行执行器项目中的某个方法。