1 前言
1.1 业务场景
业务数据同步
(
线上数据同步到线下,新平台老平台数据的同步
)
,消息通知,业务数据的补偿。
1.2 什么是定时任务
定时任务是指基于给定的时间点,给定的时间间隔或者给定执行次数自动的执行程序。
任务调度是系统的重要组成部分。
任务调度直接影响着系统的实时性。
任务调度涉及到多线程并发、运行时间规则定制及解析、线程池的维护等诸多方面的工作。
1.2.1 常见的任务框架
常见的分布式任务调度框架有:
xxl-job
【美团】、
Elastic-job
【当当】、
saturn
【唯品会】、
lts
【阿
里】、
TBSchedule
、
cronsun
、
Quartz
等。
1.2.2 一般定时任务的不足
不支持集群
不支持任务重试,即任务出错误无解决办法
不支持动态调用规则
无报警机制
不支持生命周期的统一管理
任务数据难以统计
2 XXL-JOB 定时任务
2.1 前言
package com.fanxl.xxljob.task;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;@Componentpublic class TaskDemo {/*** 从数据库中查询数据,插入到另外一个数据* 程序分两部分:调度模块和任务程序块的代码耦合在一起,* 1 、如果想改变调度规则,就必须先停止掉服务,才能改变 cron 表达式里的调用规则* 2 、将 taskdemo 部署多台时,多个线程调用同样的方法,这样的话程序不支持集群部署,可用性无法保障** 两部分:* 1 、 Scheduled: 本质上是由 springboot 内置的线程池, 1 个长度的线程池按照定义的时间规则调用程序* spring.task.scheduling.pool.size=1* 2 、开发者自己定义的业务程序*/@Scheduled(cron = "0/5 * * * * *")public void syncData(){// 查询出的数据int size = 1000;for (int i = 0; i < size; i++) {System.out.println(" 向数据库中插入数据 "+i);}}}
package com.fanxl.xxljob;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.scheduling.annotation.EnableScheduling;@SpringBootApplication@EnableSchedulingpublic class XxljobApplication {public static void main(String[] args) {SpringApplication.run(XxljobApplication.class, args);}}
2.2 设计思想
xxl-job
是一个轻量级的分布式任务调度框架,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。
1.
将调度行为抽象形成
“
调度中心
”
平台,平台本身并不承担业务逻辑,只负责发起调度请求。
2.
将任务抽象成分散的
JobHandler
,交由执行器统一管理,执行器负责接收调度请求并执行对应的
JobHandler
中的业务。
3. “
调度
”
和
“
任务
”
互相解耦,提高系统整体的稳定性和扩展性。
4.
将程序分为调度中心【
A
】和执行器【
B
】两部分。执行器指当前应用程序,应用程序中包含
N
个
具体的任务。相当于【
A
】调用【
B
】里面的一个方法。
5.
调用机制:
1) HTTP
请求
2) RPC
远程调用
无论那种调用方式,首先需要知道执行器的地址
ip
,端口。所以在执行器中起了一个注册线程
【后台线程】。注册线程会向调度中心发送请求【注册服务】。调度中心记下后,方便后期调用执
行器。
数据中心:用于持久化的存储,避免下次启动后数据丢失。
6.
调度中心内置调度器,用来调用执行器,执行器自研
RPC
进行远程调用
,
执行器会注册到调度中
心,调度中心基于地址和端口进行
RPC
的远程调用。双向。
7.
调度中心调用执行器中的某个具体任务之后,执行器接收到服务之后,进行调度,执行某个具体的
方法【
JobHandler
】,而后将执行结果传给调度中心。
8.
调用结果执行完后执行回调服务,回调服务将结果记录,形成调度日志。
2.3 核心组件
1.
调度模块
(
调度中心)
负责管理调度信息,按照调度配置发出调度请求,本身不承担业务代码。
调度系统与任务解耦,提高了系统可用性与稳定性,同时调度系统性能不在受限于任务模块。
支持可视化,简单且动态的管理调度信息,包括任务新建,更新,删除,
GLUE
开发和任务报警
等,所有上述操作都会实时生效,同时支持监控调度结果及执行日志,支持执行器
Failover
【故障
转移】。
2.
执行模块
(
执行器
)
负责接受调度请求并执行任务逻辑。
任务模块专注于任务的执行等操作,开发和维护更加简单和高效。
接受
“
调度中心
”
的执行请求,终止请求和日志请求等。
2.4 特点
官网:
https://www.xuxueli.com/xxl-job/
1
、简单:支持通过
Web
页面对任务进行
CRUD
操作,操作简单,一分钟上手。
2
、动态:支持动态修改任务状态、启动
/
停止任务,以及终止运行中任务,即时生效。
3
、调度中心
HA
(中心式):调度采用中心式设计,
“
调度中心
”
自研调度组件并支持集群部署,可保证
调度中心
HA
;【
HA:
高可用】由于调度的中心式设计,所以调度中心在部署时,需要
所有的调度中心
访问同一个
DB(
数据库
)
。
4
、执行器
HA
(分布式):任务分布式执行,任务
”
执行器
”
支持集群部署,可保证任务执行
HA
。【
不
调用不执行
】
5
、注册中心
:
执行器会周期性自动注册任务
,
调度中心将会自动发现注册的任务并触发执行。同时,也
支持手动录入执行器地址。
6
、自定义任务参数:支持在线配置调度任务入参,即时生效;【当任务失败时,可以手动设置参数使
任务再次执行】
7
、任务依赖:支持配置子任务依赖,当父任务执行结束且执行成功后将会主动触发一次子任务的执行
,
多个子任务用逗号分隔。
8
、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务。
9
、路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询【第一次调用
执行器
1
,第二次调用执行器
2
】、随机、一致性
HASH
【
A
任务永远执行
1
,或者永远执行
2
】、最不
经常使用、最近最久未使用、故障转移【当第一次执行,执行执行器
1
,当第一次执行失败时,可以转
而执行执行器
2
】,忙碌转移【当访问执行器
1
时,可能比较忙碌,此时可以去访问执行器
2
】等。
10
、故障转移:任务路由策略选择
”
故障转移
”
情况下,如果执行器集群中某一台机器故障,将会自动
Failover
切换到一台正常的执行器发送调度请求。
11
、阻塞处理策略:调度过于密集执行器来不及处理时的处理策略。
策略包括:
1
)单机串行(默认):即等待,等待上一个执行完再执行;
2
)丢弃后续调度:即一个任务的后面还有任务时,丢掉后面的任务;
3
)覆盖之前调度:如果现在有一个任务
A
在执行,后面又一个任务
A
在执行,此时可以停掉前面的
A
任务,直接执行第二次的新的
A
任务。
12
、事件触发:除了
“Cron
方式
”
和
“
任务依赖方式
”
触发任务执行之外,支持基于事件的触发任务方
式。调度中心提供触发任务单次执行的
API
服务,可根据业务事件灵活触发。【手动触发和父子任务】
13
、任务进度监控:支持实时监控任务进度;
14
、
Rolling
实时日志:支持在线查看调度结果,并且支持以
Rolling
方式实时查看执行器输出的完整的
执行日志;
3 XXL-JOB 实战
3.1 XXL-JOB 入门使用
3.1.1 下载源码
1.
码云地址:
https://gitee.com/xuxueli0323/xxl-job
3.1.2 初始化数据库
1.
用
IDEA
将
maven
项目导入到本地
2.
初始化数据库,运行
doc/db/tables_xxl_job.sql
中的
sql
生成数据库和表
右键数据库点击运行 SQL 文件,选择 Sql 脚本对应文件
3.1.3 数据库介绍
- 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.1.4 编译部署调度中心
3.1.4.1 项目介绍
3.1.4.2 配置管理数据源
修改
application.properties
文件的配置
文件路径:
/xxl-job/xxl-job-admin/src/main/resources/application.properties
3.1.4.3 访问管理界面
启动服务后访问网址:
http://localhost:8080/xxl-job-admin/
3.1.5 部署架构图
用一个调度中心部署多个节点,多个调度中心必须使用同一个
Mysql
数据库,多个节点前面使用负载均
衡器,所有的任务执行器也可以部署多个节点,任务执行器配置调度中心的地址的时候,配置负载均衡
器的地址。
3.1.6 创建执行器
3.1.6.1 引入核心依赖
<dependency><groupId>com.xuxueli</groupId><artifactId>xxl-job-core</artifactId><version>2.3.1</version></dependency>
3.1.6.2 编辑配置文件
将官方代码示例文件
application.properties
和
logback.xml
中的内容拷贝到执行器项目中,根据需要
修改
application.properties
文件中的配置
# web port
当前应用端口
server.port=8081# no web#spring.main.web-environment=false# log config 日志名称logging.config=classpath:logback.xml### 注册地址# xxl-job admin address list, such as "http://address" or"http://address01,http://address02"xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin### xxl-job, access tokenxxl.job.accessToken=default_token### xxl-job executor appname 执行器名称,同一应用多个节点使用同样的名称xxl.job.executor.appname=xxl-job-executor-sample### 执行器的 ip 和 address 可以动态的被发现### xxl-job executor registry-address: default use address to registry ,otherwise use ip:port if address is nullxxl.job.executor.address=### xxl-job executor server-infoxxl.job.executor.ip=### 执行器内部端口 rpc 内部调用端口xxl.job.executor.port=8181### 日志存储位置### xxl-job executor log-pathxxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler### xxl-job executor log-retention-daysxxl.job.executor.logretentiondays=30
3.1.6.3 配置类
新建
core.config
目录,并新建类
XxlJobConfig
,将官方文档中的代码拷入。
package com.fanxl.xxljobdemo.config.XxlJobConfig;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @description:
* @author: fanxl
* @date: 2023/4/26 11:07
* @param:
* @return:
**/
@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;
}
/**
* 针对多网卡、容器内部署等情况,可借助 "spring-cloud-commons" 提供的 "InetUtils"
组件灵活定制注册IP;
*
1、引入依赖:
* <dependency>
* <groupId>org.springframework.cloud</groupId>
* <artifactId>spring-cloud-commons</artifactId>
* <version>${version}</version>
* </dependency>
*
* 2、配置文件,或者容器启动变量
* spring.cloud.inetutils.preferred-networks: 'xxx.xxx.xxx.'
*
* 3、获取IP
* String ip_ =
inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
*/
}
3.1.6.4 编写 xxl-job 任务
在
src/main/java/com/fanxl /
下新建一个包
job,
并在包下新建配置类:
xxljobdemo.job
package com.fanxl.xxljobdemo.job;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.springframework.stereotype.Component;
@Component //定义Spring管理Bean(也就是将标注@Component注解的类交由spring管理)
public class DemoXxlJob {
/**
* @description:第一个xxl-job任务
* @author: fanxl
* @date: 2023/4/26 11:16
* @param: []
* @return: com.xxl.job.core.biz.model.ReturnT
**/
//value:任务名称
@XxlJob(value = "demoTask")
public ReturnT demoTask(){
System.out.println("Hello xxl job");
//调度结果
return ReturnT.SUCCESS;
}
}
3.1.7 配置定时任务
3.1.7.1 配置执行
注册方式:后台线程自动注册
/
手动录入。
当注册方式为自动注册时,会自动生成
OnLine
机器地址。【需要先在配置文件中配置执行器的名称
xxl.job.executor.appname=xxl-job-executor-sample
】
注册节点的端口号为配置文件中设置的执行器内部调用的端口号。
3.1.7.2 配置具体定时任务
方法上加注解
@XxlJob,value
为任务的名称。
JobHandler
的值为
DemoXxlJob
中指定的任务名称,即
value
的值。
3.1.7.3 手动触发执行
3.1.7.4 结果示例
3.2 路由策略
假设有三台执行器:
A1
,
A2
,
A3
第一个:默认调用
A1
最后一个:默认调用
A3
轮询:第一次调用时调用
A1
,第二次调用时调用
A2
,第三次调用时调用
A3
随机:随机调用
A1
,
A2
,
A3
步骤一:
创建
xxl-job
任务
package com.fanxl.xxljobdemo.job;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Component;
/**
*@Author: fanxl
*@CreateTime: 2023-04-26 11:14
*/
@Component //定义Spring管理Bean(也就是将标注@Component注解的类交由spring管理)
@Log4j2
public class DemoXxlJob {
/**
* @description:第一个xxl-job任务
* @author: fanxl
* @date: 2023/4/26 11:16
* @param: []
* @return: com.xxl.job.core.biz.model.ReturnT
**/
//value:任务名称
@XxlJob(value = "demoTask")
public ReturnT demoTask(){
System.out.println("Hello xxl job");
//调度结果
return ReturnT.SUCCESS;
}
/**
* @description:演示:调度策略:第一个执行器
* @author: fanxl
* @date: 2023/4/26 13:35
* @param: []
* @return: com.xxl.job.core.biz.model.ReturnT
**/
@XxlJob("fisrtTask")
public ReturnT firstTask(){
log.info("========演示调度策略:第一个执行器");
return ReturnT.SUCCESS;
}
/**
* @description:演示:调度策略:最后一个执行器
* @author: fanxl
* @date: 2023/4/26 13:37
* @param: []
* @return: com.xxl.job.core.biz.model.ReturnT
**/
@XxlJob("lastTask")
public ReturnT lastTask(){
log.info("========演示调度策略:最后一个执行器");
return ReturnT.SUCCESS;
}
/**
* @description:演示:调度策略:轮询
* @author: fanxl
* @date: 2023/4/26 13:38
* @param: []
* @return: com.xxl.job.core.biz.model.ReturnT
**/
@XxlJob("pollTask")
public ReturnT pollTask(){
log.info("========演示调度策略:轮询执行器");
return ReturnT.SUCCESS;
}
/**
* @description:演示:调度策略:随机
* @author: fanxl
* @date: 2023/4/26 13:39
* @param: []
* @return: com.xxl.job.core.biz.model.ReturnT
**/
@XxlJob("randomTask")
public ReturnT randomTask(){
log.info("========演示调度策略:随机执行器");
return ReturnT.SUCCESS;
}
/**
* @description:演示:调度策略:hash策略
* @author: fanxl
* @date: 2023/4/26 13:39
* @param: []
* @return: com.xxl.job.core.biz.model.ReturnT
**/
@XxlJob("hashTask")
public ReturnT hashTask(){
log.info("========演示调度策略:哈希执行器");
return ReturnT.SUCCESS;
}
}
步骤二:编辑启动项配置和端口
1.
复制原启动项,更改名称;
2. 修改配置文件中的应用端口和内部调用端口
步骤三:
创建任务
1、在步骤二启动服务后,可以在执行器管理处看见已经生成三个注册节点。
2、在任务管理处创建对应的任务
步骤四:
手动执行任务测试
结果
:
1
、当路由策略为第一个时,只有 8081 的服务被调用了。