文章目录
- 前言
- 定时任务
- 分布式任务调度
- 1、XXL-JOB介绍
- 1.1 XXL-JOB概述
- 1.2 XXL-JOB特性
- 1.3 整体架构
- 2、XXL-JOB任务中心环境搭建
- 2.1 XXL-JOB源码下载
- 2.2 IDEA导入xxljob工程
- 2.3 初始化数据库
- 2.4 Docker安装任务管理中心
- 3、XXL-JOB任务注册测试
- 3.1 引入xxl-job核心依赖
- 3.2 配置xxljob相关信息
- 3.3 定义定时任务执行方法
- 3.4 配置任务执行器
- 3.5 配置任务执行计划
- 3.6 调度流程
- 4、CRON表达式入门
- 4.1 cron表达式介绍
- 4.2 cron表达式语法介绍
- 4.3 cron表达式阅读练习
- 5、总结
- 5.1 XXL-JOB 执行原理
- 5.2 XXLO-JOB 工作流程
- 5.3 如何保证任务不重复执行
前言
定时任务
一般在项目中实现定时任务主要是两种技术方案,一种是Spring Task,另一种是xxl-job,其中Spring Task是适合单体项目中使用,而xxl-job是分布式任务调度框架,更适合在分布式项目中使用。
分布式任务调度
在微服务架构体系中,服务之间通过网络交互来完成业务处理的,在分布式架构下,一个服务往往会部署多个实例来运行我们的业务,如果在这种分布式系统环境下运行任务调度,我们称之为分布式任务调度。
分布式系统的特点,并且提高任务的调度处理能力:
- 并行任务调度
- 并行任务调度实现靠多线程,如果有大量任务需要调度,此时光靠多线程就会有瓶颈了,因为一台计算机CPU的处理能力是有限的。
- 如果将任务调度程序分布式部署,每个结点还可以部署为集群,这样就可以让多台计算机共同去完成任务调度,我们可以将任务分割为若干个分片,由不同的实例并行执行,来提高任务调度的处理效率。
- 高可用
- 若某一个实例宕机,不影响其他实例来执行任务。
- 弹性扩容
- 当集群中增加实例就可以提高并执行任务的处理效率。
- 任务管理与监测
- 对系统中存在的所有定时任务进行统一的管理及监测。
- 让开发人员及运维人员能够时刻了解任务执行情况,从而做出快速的应急处理响应。
- 避免任务重复执行
- 当任务调度以集群方式部署,同一个任务调度可能会执行多次,比如在电商系统中到点发优惠券的例子,就会发放多次优惠券,对公司造成很多损失,所以我们需要控制相同的任务在多个运行实例上只执行一次。
1、XXL-JOB介绍
1.1 XXL-JOB概述
XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。
目前已有多家公司接入xxl-job,包括比较知名的大众点评,京东,优信二手车,北京尚德,360金融 (360),联想集团 (联想),易信 (网易)等;
1.2 XXL-JOB特性
官方地址:http://www.xuxueli.com/xxl-job
更多详情见官网;
1.3 整体架构
xxl-job架构图(官图):
调度中心:
- 负责管理调度信息,按照调度配置发出调度请求,自身不承担业务代码;
- 主要职责为执行器管理、任务管理、监控运维、日志管理等
任务执行器:
- 负责接收调度请求并执行任务逻辑;
- 只要职责是注册服务、任务执行服务(接收到任务后会放入线程池中的任务队列)、执行结果上报、日志服务等
任务:负责执行具体的业务处理。调度中心与执行器之间的工作流程如下:
执行流程:
- 任务执行器根据配置的调度中心的地址,自动注册到调度中心。
- 达到任务触发条件,调度中心下发任务。
- 执行器基于线程池执行任务,并把执行结果放入内存队列中、把执行日志写入日志文件中。
- 执行器消费内存队列中的执行结果,主动上报给调度中心。
- 当用户在调度中心查看任务日志,调度中心请求任务执行器,任务执行器读取任务日志文件并返回日志详情。
2、XXL-JOB任务中心环境搭建
2.1 XXL-JOB源码下载
考虑到网络原因,我们选择gitee下的开源地址下载:
选择最新的2.30版本下载.
2.2 IDEA导入xxljob工程
2.3 初始化数据库
数据库脚本:https://gitee.com/xuxueli0323/xxl-job/blob/2.3.0/doc/db/tables_xxl_job.sql
将xxljob提供的SQL脚本导入到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:系统用户表;
注意:
如果表xxl_job_registry导入过程报Specified key was too long; max key length is 767 bytes错误,则将i_g_k_v联合索引相关字段的varchar改小一些即可;
2.4 Docker安装任务管理中心
拉取xxl-job-admin任务中心镜像:
docker pull xuxueli/xxl-job-admin:2.3.0
启动xxl-job任务中心容器:
# 在指定目录构建xxldata目录,然后运行如下docker指令:
docker run -e PARAMS="--spring.datasource.url=jdbc:mysql://192.168.200.128:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=UTC --spring.datasource.username=root --spring.datasource.password=root" -p 8093:8080 -v $PWD/xxldata:/data/applogs --name=xxl-job-admin -d xuxueli/xxl-job-admin:2.3.0
docker run -e PARAMS="--spring.datasource.url=jdbc:mysql://192.168.20.128:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=UTC --spring.datasource.username=root --spring.datasource.password=1234" -p 8093:8080 -v $PWD/xxldata:/data/applogs --name=xxl-job-admin -d xuxueli/xxl-job-admin:2.3.0
访问容器服务:
http://192.168.200.128:8093/xxl-job-admin
效果如下:
登录进入后效果:
注意:Docker服务重启时,保证对应的mysql服务启动,否则任务信息无法加载!
3、XXL-JOB任务注册测试
3.1 引入xxl-job核心依赖
通过官方提供的xxljob代码,我们可知道在xxl-job-executor-sample-springboot工程中引入和xxljob的核心依赖:
<!-- xxl-job-core -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>${project.parent.version}</version>
</dependency>
将来我们的项目也可通过这种方式集成xxl-job
3.2 配置xxljob相关信息
接下来就是配置任务工程信息:
配置完毕后,工程底层通过XxlJobConfig配置类加载配置信息,实现xxljob相关资源的初始化工作:
package com.xxl.job.executor.core.config;
@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;
}
}
3.3 定义定时任务执行方法
package com.xxl.job.executor.service.jobhandler;
@Component
public class SampleXxlJob {
private static Logger logger = LoggerFactory.getLogger(SampleXxlJob.class);
/**
* 1、简单任务示例(Bean模式)
*/
@XxlJob("demoJobHandler")
public void demoJobHandler() throws Exception {
//todo 打印时间
System.out.println("hello xxljob.....");
}
//.....省略......
/**
* 5、生命周期任务示例:任务初始化与销毁时,支持自定义相关逻辑;
*/
@XxlJob(value = "demoJobHandler2", init = "init", destroy = "destroy")
public void demoJobHandler2() throws Exception {
XxlJobHelper.log("XXL-JOB, Hello World.");
}
public void init(){
logger.info("init");
}
public void destroy(){
logger.info("destory");
}
}
说明:
@XxlJob中的value值就是定时任务的一个标识,注解作用的方法就是定时任务要执行逻辑的逻辑方法;
3.4 配置任务执行器
注意:如果自动注册不识别,可手动录入执行服务地址,格式比如:http://192.168.200.1:9999
3.5 配置任务执行计划
接下来,我们将xxl-job-executor-sample-springboot工程下的demoJobHandler任务,可视化配置,并启动:
接下来,输入JobHanler,输入的名称保证与@xxljob注解下的value值一致即可:
xxl-job支持的路由策略非常丰富:
-
FIRST(第一个):固定选择第一个机器;
-
LAST(最后一个):固定选择最后一个机器;
-
ROUND(轮询):在线的机器按照顺序一次执行一个
-
RANDOM(随机):随机选择在线的机器;
-
CONSISTENT_HASH(一致性HASH):每个任务按照Hash算法固定选择某一台机器,且所有任务均匀散列在不同机器上。
-
LEAST_FREQUENTLY_USED(最不经常使用):使用频率最低的机器优先被选举;
-
LEAST_RECENTLY_USED(最近最久未使用):最久未使用的机器优先被选举;
-
FAILOVER(故障转移):按照顺序依次进行心跳检测,第一个心跳检测成功的机器选定为目标执行器并发起调度;
-
BUSYOVER(忙碌转移):按照顺序依次进行空闲检测,第一个空闲检测成功的机器选定为目标执行器并发起调度;
-
SHARDING_BROADCAST(分片广播):广播触发对应集群中所有机器执行一次任务,同时系统自动传递分片参数;可根据分片参数开发分片任务;
XL-JOB并不直接提供数据处理的功能,它只会给执行器分配好分片序号,在向执行器任务调度的同时下发分片总数以及分片序号等参数,执行器收到这些参数根据自己的业务需求去利用这些参数。
分片广播下可以配合如下参数设置合理的调度策略(最简单的如:取模拆分)。
// 分片节点总数 int shardTotal = XxlJobHelper.getShardTotal(); // 当前节点下标,从0开始 int shardIndex = XxlJobHelper.getShardIndex();
还有一些其他参数配置的解析:
- 子任务:每个任务都拥有一个唯一的任务ID(任务ID可以从任务列表获取),当本任务执行结束并且执行成功时,将会触发子任务ID所对应的任务的一次主动调度,通过子任务可以实现一个任务执行完成去执行另一个任务。
- 调度过期策略:
- 忽略:调度过期后,忽略过期的任务,从当前时间开始重新计算下次触发时间;
- 立即执行一次:调度过期后,立即执行一次,并从当前时间开始重新计算下次触发时间;
- 阻塞处理策略: 调度过于密集执行器来不及处理时的处理策略;
- 单机串行(默认):调度请求进入单机执行器后,调度请求进入FIFO队列并以串行方式运行;
- 丢弃后续调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败;
- 覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务;
- 任务超时时间: 支持自定义任务超时时间,任务运行超时将会主动中断任务;
- 失败重试次数; 支持自定义任务失败重试次数,当任务失败时将会按照预设的失败重试次数主动进行重试;
启动任务查看执行效果:
当然,我们也可以随时停止正在被执行的任务;
3.6 调度流程
4、CRON表达式入门
4.1 cron表达式介绍
cron表达式类似就java中的正则,通过cron表达式可定义周期性的任务计划。
许多开源的定时任务框架大多支持cron表达式;
4.2 cron表达式语法介绍
xxl-job同样支持通过cron表达式来控制任务周期性调度执行表达式包含7个部分:分别从秒、分、时、日、月、星期、年七个时间维度来定义任务执行的周期;
cron表达式时间取值范围:
cron表达式格式:
* * * * * * *
- - - - - - -
| | | | | | |
| | | | | | + year [optional]
| | | | | +----- day of week (1 - 7)
| | | | +---------- month (1 - 12)
| | | +--------------- day of month (1 - 31)
| | +-------------------- hour (0 - 23)
| +------------------------- min (0 - 59)
+------------------------------ second (0 - 59)
字段 | 允许值 | 允许的特殊字符 |
---|---|---|
秒 | 0-59 | , - * / |
分 | 0-59 | , - * / |
小时 | 0-23 | , - * / |
月内日期 | 1-31 | , - * ? / L W C |
月 | 1-12 或者 JAN-DEC | , - * / |
周内日期 | 1-7 或者 SUN-SAT(注意:周日是1,周一为2,周六位7) | , - * ? / L C # |
年(可选) | 留空, 1970-2099 | , - * / |
特殊字段含义:
特殊字符 | 意义 |
---|---|
* | 匹配所有的值。如:*在分钟的字段域里表示 每分钟 |
? | 只在日期域和星期域中使用。它被用来指定“非明确的值” 不关心 |
- | 指定一个范围。如:“10-12”在小时域意味着“10点、11点、12点” |
, | 指定几个可选值。如:“MON,WED,FRI”在星期域里表示“星期一、星期三、星期五” |
/ | 指定增量。如:“0/15”在秒域意思是每分钟的0,15,30和45秒。“5/15”在分钟域表示每小时的5,20,35和50。符号“”在“/”前面(如:/10)等价于0在“/”前面(如:0/10) |
L | 表示day-of-month和day-of-week域,但在两个字段中的意思不同,例如day-of-month域中表示一个月的最后一天。如果在day-of-week域表示‘7’或者‘SAT’,如果在day-of-week域中前面加上数字,它表示一个月的最后几天,例如‘6L’就表示一个月的最后一个星期五(在西方,周末索引位为1,那么周一就是2,其它一次类推) |
W | 只允许日期域出现。这个字符用于指定日期的最近工作日。例如:如果你在日期域中写 “15W”,表示:这个月15号最近的工作日。所以,如果15号是周六,则任务会在14号触发。如果15好是周日,则任务会在周一也就是16号触发。如果是在日期域填写“1W”即使1号是周六,那么任务也只会在下周一,也就是3号触发,“W”字符指定的最近工作日是不能够跨月份的。字符“W”只能配合一个单独的数值使用,不能够是一个数字段,如:1-15W是错误的 |
LW | L和W可以在日期域中联合使用,LW表示这个月最后一周的工作日 |
# | 只允许在星期域中出现。这个字符用于指定本月的某某天。例如:“6#3”表示本月第三周的星期五(6表示星期五,3表示第三周)。“2#1”表示本月第一周的星期一。“4#5”表示第五周的星期三 |
参考cron在线表达式:https://www.matools.com/cron/
4.3 cron表达式阅读练习
(1)0 0 2 1 * ? 表示在每月的1日的凌晨2点调整任务
(2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业 ★★★
(3)0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作
(4)0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
(5)0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
(6)0 0 12 ? * WED 表示每个星期三中午12点
(7)0 15 10 * * ? 每天上午10:15触发
(8)0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发
(9)0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
(10)0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发
(12)0 15 10 L * ? 每月最后一日的上午10:15触发
(13)0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发
5、总结
知识梳理:
5.1 XXL-JOB 执行原理
XXL-JOB 分布式任务调度服务由调度中心和执行器组成,调用中心负责按任务调度策略向执行器下发任务,执行器负责接收任务并执行。
5.2 XXLO-JOB 工作流程
- 首先部署并启动xxl-job调度中心。(一个java工程)
- 首先在微服务添加xxl-job依赖,在微服务中配置执行器
- 启动微服务,执行器向调度中心上报自己。
- 在微服务中写一个任务方法并用xxl-job的注解去标记执行任务的方法名称。
- 在调度中心配置任务调度策略,调度策略就是每隔多长时间执行还是在每天或每月的固定时间去执行,比如每天0点执行,或每隔1小时执行一次等。
- 在调度中心启动任务。
- 调度中心根据任务调度策略,到达时间就开始下发任务给执行器。
- 执行器收到任务就开始执行任务。
5.3 如何保证任务不重复执行
- 调度中心按分片广播的方式去下发任务
- 执行器收到作业分片广播的参数:分片总数和分片序号,计算 任务id 除以 分片总数得到一个余数,如果余数等于分片序号这时就去执行这个任务,这里保证了不同的执行器执行不同的任务。
- 配置调度过期策略为“忽略”,避免同一个执行器多次重复执行同一个任务
- 配置任务阻塞处理策略为“丢弃后续调度”,注意:丢弃也没事下一次调度就又可以执行了
- 另外还要保证任务处理的幂等性,执行过的任务可以打一个状态标记已完成,下次再调度执行该任务判断该任务已完成就不再执行