Bootstrap

xxl-job分布式定时任务

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;
@Component
public 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
@EnableScheduling
public 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 token
xxl.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 null
xxl.job.executor.address=
### xxl-job executor server-info
xxl.job.executor.ip=
### 执行器内部端口 rpc 内部调用端口
xxl.job.executor.port=8181
### 日志存储位置
### xxl-job executor log-path
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### xxl-job executor log-retention-days
xxl.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 的服务被调用了。

 

 

 

;