XXL-JOB是一个轻量级分布式任务调度平台,笔者带入新手的视角,从项目概览和一个示例程序开始,浅析项目的启动流程,然后以业务角度解析几个关键请求的执行过程,最后站在纯技术角度扩展一部分技术内容,带大伙浅浅分析一波浅析XXL-JOB框架。
https://gitee.com/xuxueli0323/xxl-job.git
xxl-job-admin:调度中心
xxl-job-core:公共依赖
xxl-job-executor-samples:执行器Sample示例(选择合适的版本执行器,可直接使用,也可以参考其并将现有项目改造成执行器)
:xxl-job-executor-sample-springboot:Springboot版本,通过Springboot管理执行器,推荐这种方式;
:xxl-job-executor-sample-frameless:无框架版本;
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.xuxueligroupId>
<artifactId>xxl-jobartifactId>
<version>2.4.1-SNAPSHOTversion>
parent>
<artifactId>xxl-job-coreartifactId>
<packaging>jarpackaging>
<name>${project.artifactId}name>
<description>A distributed task scheduling framework.description>
<url>https://www.xuxueli.com/url>
<dependencies>
<dependency>
<groupId>io.nettygroupId>
<artifactId>netty-codec-httpartifactId>
<version>${netty.version}version>
dependency>
<dependency>
<groupId>com.google.code.gsongroupId>
<artifactId>gsonartifactId>
<version>${gson.version}version>
dependency>
<dependency>
<groupId>org.apache.groovygroupId>
<artifactId>groovyartifactId>
<version>${groovy.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>${spring.version}version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>${slf4j-api.version}version>
dependency>
<dependency>
<groupId>javax.annotationgroupId>
<artifactId>javax.annotation-apiartifactId>
<version>${javax.annotation-api.version}version>
<scope>providedscope>
dependency>
dependencies>
project>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.xuxueligroupId>
<artifactId>xxl-jobartifactId>
<version>2.4.1-SNAPSHOTversion>
parent>
<artifactId>xxl-job-adminartifactId>
<packaging>jarpackaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>${spring-boot.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-freemarkerartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-mailartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>${mybatis-spring-boot-starter.version}version>
dependency>
<dependency>
<groupId>com.mysqlgroupId>
<artifactId>mysql-connector-jartifactId>
<version>${mysql-connector-j.version}version>
dependency>
<dependency>
<groupId>com.xuxueligroupId>
<artifactId>xxl-job-coreartifactId>
<version>${project.parent.version}version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<version>${spring-boot.version}version>
<executions>
<execution>
<goals>
<goal>repackagegoal>
goals>
execution>
executions>
plugin>
<plugin>
<groupId>com.spotifygroupId>
<artifactId>docker-maven-pluginartifactId>
<version>0.4.13version>
<configuration>
<imageName>${project.artifactId}:${project.version}imageName>
<dockerDirectory>${project.basedir}dockerDirectory>
<resources>
<resource>
<targetPath>/targetPath>
<directory>${project.build.directory}directory>
<include>${project.build.finalName}.jarinclude>
resource>
resources>
configuration>
plugin>
plugins>
build>
project>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.xuxueligroupId>
<artifactId>xxl-job-executor-samplesartifactId>
<version>2.4.1-SNAPSHOTversion>
parent>
<artifactId>xxl-job-executor-sample-framelessartifactId>
<packaging>jarpackaging>
<name>${project.artifactId}name>
<description>Example executor project for spring boot.description>
<url>https://www.xuxueli.com/url>
<dependencies>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>${slf4j-api.version}version>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiter-engineartifactId>
<version>${junit-jupiter.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>com.xuxueligroupId>
<artifactId>xxl-job-coreartifactId>
<version>${project.parent.version}version>
dependency>
dependencies>
project>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.xuxueligroupId>
<artifactId>xxl-job-executor-samplesartifactId>
<version>2.4.1-SNAPSHOTversion>
parent>
<artifactId>xxl-job-executor-sample-springbootartifactId>
<packaging>jarpackaging>
<name>${project.artifactId}name>
<description>Example executor project for spring boot.description>
<url>https://www.xuxueli.com/url>
<properties>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>${spring-boot.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.xuxueligroupId>
<artifactId>xxl-job-coreartifactId>
<version>${project.parent.version}version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<version>${spring-boot.version}version>
<executions>
<execution>
<goals>
<goal>repackagegoal>
goals>
execution>
executions>
plugin>
plugins>
build>
project>
1、依赖xxl-job-core依赖netty
2、xxl-job-admin依赖xxl-job-core、mysql-connector-j
3、xxl-job-executor-sample-frameless依赖xxl-job-core
4、xxl-job-executor-sample-springboot依赖xxl-job-core
5、xxl-job-executor-sample-frameless、xxl-job-executor-sample-springboot与xxl-job-admin无依赖关系
1、jdk-17.0.7
2、mysql-5.7.41-winx64
3、IntelliJ IDEA 2023.1 (Ultimate Edition)
4、apache-maven-3.6.2
1、示例程序包含一个调度中心和两个执行器,其中执行器由xxl-job-executor-sample-frameless和xxl-job-executor-sample-springboot组成,项目做了少许改造,本意是借助Quick Start强化调度中心、执行器并非强耦合关系,为服务端与客户端关系,这也是上面所列pom依赖关系的一种衍生应用。
2、Quick Start官方参考地址:https://www.xuxueli.com/xxl-job/#1.6 环境
访问地址:http://localhost:8080/xxl-job-admin
账户/密码:admin/123456
1、访问调度中心页面 http://localhost:8080/xxl-job-admin -> 执行器管理 -> 新增 -> 保存
1、访问调度中心页面 http://localhost:8080/xxl-job-admin -> 任务管理 -> 选择任务 -> 操作 -> 启动 -> 确定
1、作用:存储执行器信息
2、相关接口:http://localhost:8080/xxl-job-admin/jobgroup/pageList
1、作用:存储任务配置信息
2、相关接口:http://localhost:8080/xxl-job-admin/jobinfo/pageList
1、作用:存储任务执行日志信息
2、相关接口:http://localhost:8080/xxl-job-admin/joblog/pageList
1、作用:存储glue脚本语言信息(除bean外的6种运行模式使用)
2、相关接口:http://127.0.0.1:8080/xxl-job-admin/jobcode/save
1、作用:存储每日任务执行情况统计信息
2、相关接口:http://localhost:8080/xxl-job-admin/chartInfo
1、作用:存储执行器对应执行节点注册信息
2、相关接口:http://localhost:8080/api/registry
1、作用:存储账户信息,对应界面管理信息如下截图
2、相关接口:http://localhost:8080/xxl-job-admin/user/pageList
1、ER图
待补充
2、文字说明
① xxl_job_group:执行器信息表
② xxl_job_info:任务配置信息表,关联关系
(n)xxl_job_info.job_group(1)xxl_job_group.id
③ xxl_job_log:任务执行信息表,关联关系
(n)xxl_job_log.job_id (1)xxl_job_info.id
④ xxl_job_logglue:存储glue脚本语言信息(除bean外的6种运行模式使用)
⑤ xxl_job_log_report:每日任务执行情况(xxl_job_log)统计信息表
⑥ xxl_job_registry:执行节点信息表,关联关系
(n)xxl_job_registry.registry_key (1)xxl_job_group.app_name
⑦ xxl_job_user:用户表
⑧ xxl_job_lock:表锁,集群模式下xxl-job进行任务调度时,为了避免任务被重复调度,会先通过SQL对表xxl_job_lock的记录schedule_lock加锁
1、调度中心为springboot项目,启动时会注入com.xxl.job.admin.core.conf.XxlJobAdminConfig类,并执行回调方法com.xxl.job.admin.core.conf.XxlJobAdminConfig#afterPropertiesSet;
2、com.xxl.job.admin.core.conf.XxlJobAdminConfig类注入了系统运行所需的各种类、变量,并对外暴露相应方法方法
3、com.xxl.job.admin.core.conf.XxlJobAdminConfig#afterPropertiesSet则为咱们调度中心启动代码的入口
package com.xxl.job.admin.core.conf;
import *;
@Component
public class XxlJobAdminConfig implements InitializingBean, DisposableBean {
private static XxlJobAdminConfig adminConfig = null;
public static XxlJobAdminConfig getAdminConfig() {
return adminConfig;
}
private XxlJobScheduler xxlJobScheduler;
@Override
public void afterPropertiesSet() throws Exception {
// 系统启动入口
adminConfig = this;
xxlJobScheduler = new XxlJobScheduler();
xxlJobScheduler.init();
}
@Override
public void destroy() throws Exception {
xxlJobScheduler.destroy();
}
@Value("${xxl.job.i18n}")
private String i18n;
@Value("${xxl.job.accessToken}")
private String accessToken;
@Value("${spring.mail.from}")
private String emailFrom;
@Value("${xxl.job.triggerpool.fast.max}")
private int triggerPoolFastMax;
@Value("${xxl.job.triggerpool.slow.max}")
private int triggerPoolSlowMax;
@Value("${xxl.job.logretentiondays}")
private int logretentiondays;
@Resource
private XxlJobLogDao xxlJobLogDao;
@Resource
private XxlJobInfoDao xxlJobInfoDao;
@Resource
private XxlJobRegistryDao xxlJobRegistryDao;
@Resource
private XxlJobGroupDao xxlJobGroupDao;
@Resource
private XxlJobLogReportDao xxlJobLogReportDao;
@Resource
private JavaMailSender mailSender;
@Resource
private DataSource dataSource;
@Resource
private JobAlarmer jobAlarmer;
public String getI18n() {
if (!Arrays.asList("zh_CN", "zh_TC", "en").contains(i18n)) {
return "zh_CN";
}
return i18n;
}
public String getAccessToken() {
return accessToken;
}
public String getEmailFrom() {
return emailFrom;
}
/**
* 快任务-调度线程池最大线程配置【必填】
*/
public int getTriggerPoolFastMax() {
if (triggerPoolFastMax < 200) {
return 200;
}
return triggerPoolFastMax;
}
/**
* 慢任务-调度线程池最大线程配置【必填】
*/
public int getTriggerPoolSlowMax() {
if (triggerPoolSlowMax < 100) {
return 100;
}
return triggerPoolSlowMax;
}
/**
* 日志最大保留时间(默认xxl.job.logretentiondays=30),必须大于7天,否则关闭(-1)
*/
public int getLogretentiondays() {
if (logretentiondays < 7) {
return -1;
}
return logretentiondays;
}
public XxlJobLogDao getXxlJobLogDao() {
return xxlJobLogDao;
}
public XxlJobInfoDao getXxlJobInfoDao() {
return xxlJobInfoDao;
}
public XxlJobRegistryDao getXxlJobRegistryDao() {
return xxlJobRegistryDao;
}
public XxlJobGroupDao getXxlJobGroupDao() {
return xxlJobGroupDao;
}
public XxlJobLogReportDao getXxlJobLogReportDao() {
return xxlJobLogReportDao;
}
public JavaMailSender getMailSender() {
return mailSender;
}
public DataSource getDataSource() {
return dataSource;
}
public JobAlarmer getJobAlarmer() {
return jobAlarmer;
}
}
1、com.xxl.job.admin.core.scheduler.XxlJobScheduler#init具体实现如下(接下来对init每行代码进行逐个分析)
package com.xxl.job.admin.core.scheduler;
import *;
public class XxlJobScheduler {
private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);
public void init() throws Exception {
// 1.1、加载国际化配置, 默认加载/resources/i18n/message_zh_CN.properties,赋值给变量com.xxl.job.admin.core.util.I18nUtil.prop,
// 1.2、并初始化\替换系统枚举、常量
initI18n();
// 2.1、初始化快、慢任务 两个 任务调度线程池,并赋值给com.xxl.job.admin.core.thread.JobTriggerPoolHelper.fastTriggerPool,
// com.xxl.job.admin.core.thread.JobTriggerPoolHelper.slowTriggerPool俩变量
// 注:任务执行时,调用com.xxl.job.admin.core.thread.JobTriggerPoolHelper.addTrigger方法,内部会根据条件自动判断到底使用那个线程池
JobTriggerPoolHelper.toStart();
// 3.1、初始化 任务执行节点注册 线程池,并赋值给com.xxl.job.admin.core.thread.JobRegistryHelper.registryOrRemoveThreadPool,
// 注:手动注册执行器时,调用com.xxl.job.admin.core.thread.JobRegistryHelper.registry方法,用于设置执行器对应的任务执行节信息
// 3.2、启动 任务执行节点注册 后台监控线程,赋值给com.xxl.job.admin.core.thread.JobRegistryHelper.registryMonitorThread变量,
// 每30s执行一次,主要负责 刷新所有 执行器地址类型 为 自动注册类型 xxl_job_group表 执行器信息(同时移除失效/超时任务执行节点信息)
JobRegistryHelper.getInstance().start();
// 4.1、启动 失败任务 后台监控线程,并赋值给com.xxl.job.admin.core.thread.JobFailMonitorHelper.monitorThread变量,
// 每10s执行一次,主要负责失败任务的重试、已经失败任务的报警工作
JobFailMonitorHelper.getInstance().start();
// 5.1、始化 任务回调方法 调度 线程池,并赋值给com.xxl.job.admin.core.thread.JobCompleteHelper.callbackThreadPool,
// 注:任务执行结束时,调用com.xxl.job.admin.core.thread.JobCompleteHelper.callback方法,执行默认回调方法(即主动结束当前方法执行,如果有子任务则触发子任务执行,并更新xxl_job_log表记录)
// 5.2、启动 执行结果丢失任务 后台监控线程,赋值给com.xxl.job.admin.core.thread.JobCompleteHelper.monitorThread
// 每60s执行一次,主要负责 将 执行结果丢失任务 主动标记为失败 (调度记录停留在 "运行中" 状态超过10min,且对应执行器心跳注册失败不在线,则将本地调度主动标记失败)
JobCompleteHelper.getInstance().start();// admin lose-monitor run ( depend on JobTriggerPoolHelper )
// 6.1、启动 定时日志清理 后台监控线程,赋值给com.xxl.job.admin.core.thread.JobLogReportHelper.logrThread
// 每1天执行一次,主要负责 清理 已过期的xxl_job_log表记录(xxl.job.logretentiondays)
JobLogReportHelper.getInstance().start();
// https://www.dandelioncloud.cn/article/details/1611003603865272322
// 启动 俩任务调度 后台监控线程,赋值给com.xxl.job.admin.core.thread.JobScheduleHelper.scheduleThread,com.xxl.job.admin.core.thread.JobScheduleHelper.ringThread
// 7.1、线程 scheduleThread 运行中不断的从任务表中查询 查询近 5秒 中要执行的任务,
// 分情况将判断任务是否立即执行,或者将任务执行时间除以 1000 变为秒之后再与 60 求余添加到时间轮中(时间轮实现方式比较简单,就是一个 Map 结构数据,key值0-60,value是任务ID列表 Map ringData)
// 7.2、线程 ringThread 运行中不断根据当前时间求余从 时间轮 ringData 中获取任务列表,取出任务之后执行任务
JobScheduleHelper.getInstance().start();// start-schedule ( depend on JobTriggerPoolHelper )
}
public void destroy() throws Exception {
JobScheduleHelper.getInstance().toStop();
JobLogReportHelper.getInstance().toStop();
JobCompleteHelper.getInstance().toStop();
JobFailMonitorHelper.getInstance().toStop();
JobRegistryHelper.getInstance().toStop();
JobTriggerPoolHelper.toStop();
}
private void initI18n(){
// 替换 阻塞处理策略 对应的title值
for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
// I18nUtil.getString方法,默认加载/resources/i18n/message_zh_CN.properties,赋值给变量com.xxl.job.admin.core.util.I18nUtil.prop
// 然后按照给定的key返回对应的值
item.setTitle(I18nUtil.getString("jobconf_block_".concat(item.name())));
}
}
private static ConcurrentMap<String, ExecutorBiz> executorBizRepository = new ConcurrentHashMap<String, ExecutorBiz>();
/**
* 获取address对应ExecutorBiz实例(ExecutorBizClient)
*/
public static ExecutorBiz getExecutorBiz(String address) throws Exception {
// 验证address非空
if (address==null || address.trim().length()==0) {
return null;
}
// 从executorBizRepository中加载缓存信息
address = address.trim();
ExecutorBiz executorBiz = executorBizRepository.get(address);
if (executorBiz != null) {
return executorBiz;
}
// 设置ExecutorBiz实例,并存入executorBizRepository缓存
executorBiz = new ExecutorBizClient(address, XxlJobAdminConfig.getAdminConfig().getAccessToken());
executorBizRepository.put(address, executorBiz);
return executorBiz;
}
}
1、com.xxl.job.admin.core.scheduler.XxlJobScheduler#init方法内部调用com.xxl.job.admin.core.scheduler.XxlJobScheduler#initI18n方法,代码实现见step 2
2、com.xxl.job.admin.core.scheduler.XxlJobScheduler#initI18n方法,内部调用了com.xxl.job.core.enums.ExecutorBlockStrategyEnum#values方法,具体实现如下
package com.xxl.job.core.enums;
/**
* 阻塞处理策略 枚举
* Created by xuxueli on 17/5/9.
*/
public enum ExecutorBlockStrategyEnum {
//单机串行
SERIAL_EXECUTION("Serial execution"),
//丢弃后续调度
DISCARD_LATER("Discard Later"),
//覆盖之前调度
COVER_EARLY("Cover Early");
private String title;
private ExecutorBlockStrategyEnum (String title) {
this.title = title;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public static ExecutorBlockStrategyEnum match(String name, ExecutorBlockStrategyEnum defaultItem) {
if (name != null) {
for (ExecutorBlockStrategyEnum item:ExecutorBlockStrategyEnum.values()) {
if (item.name().equals(name)) {
return item;
}
}
}
return defaultItem;
}
}
3、com.xxl.job.admin.core.scheduler.XxlJobScheduler#initI18n方法,内部调用了com.xxl.job.admin.core.util.I18nUtil#getString方法,具体实现如下
package com.xxl.job.admin.core.util;
import *;
public class I18nUtil {
private static Logger logger = LoggerFactory.getLogger(I18nUtil.class);
private static Properties prop = null;
public static Properties loadI18nProp(){
if (prop != null) {
return prop;
}
try {
// 构建i18n配置文件
String i18n = XxlJobAdminConfig.getAdminConfig().getI18n();
String i18nFile = MessageFormat.format("i18n/message_{0}.properties", i18n);
// 加载i18n对应prop
Resource resource = new ClassPathResource(i18nFile);
EncodedResource encodedResource = new EncodedResource(resource,"UTF-8");
prop = PropertiesLoaderUtils.loadProperties(encodedResource);
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
return prop;
}
public static String getString(String key) {
// 默认加载/resources/i18n/message_zh_CN.properties,赋值给变量com.xxl.job.admin.core.util.I18nUtil.prop
return loadI18nProp().getProperty(key);
}
public static String getMultString(String... keys) {
Map<String, String> map = new HashMap<String, String>();
Properties prop = loadI18nProp();
if (keys!=null && keys.length>0) {
for (String key: keys) {
map.put(key, prop.getProperty(key));
}
} else {
for (String key: prop.stringPropertyNames()) {
map.put(key, prop.getProperty(key));
}
}
String json = JacksonUtil.writeValueAsString(map);
return json;
}
}
1、com.xxl.job.admin.core.scheduler.XxlJobScheduler#init方法内部调用方法com.xxl.job.admin.core.thread.JobTriggerPoolHelper#toStart,具体实现如下
package com.xxl.job.admin.core.thread;
import *;
public class JobTriggerPoolHelper {
private static Logger logger = LoggerFactory.getLogger(JobTriggerPoolHelper.class);
private ThreadPoolExecutor fastTriggerPool = null;
private ThreadPoolExecutor slowTriggerPool = null;
public void start(){
fastTriggerPool = new ThreadPoolExecutor(
10,
XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax(),
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(1000),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-fastTriggerPool-" + r.hashCode());
}
});
slowTriggerPool = new ThreadPoolExecutor(
10,
XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax(),
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(2000),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-slowTriggerPool-" + r.hashCode());
}
});
}
public void stop() {
//triggerPool.shutdown();
fastTriggerPool.shutdownNow();
slowTriggerPool.shutdownNow();
}
private volatile long minTim = System.currentTimeMillis()/60000; // ms > min
private volatile ConcurrentMap<Integer, AtomicInteger> jobTimeoutCountMap = new ConcurrentHashMap<>();
/**
* 添加 执行任务
*/
public void addTrigger(final int jobId,
final TriggerTypeEnum triggerType,
final int failRetryCount,
final String executorShardingParam,
final String executorParam,
final String addressList) {
// 内部会根据条件自动判断到底使用那个线程池 (一分钟超时10次,防止频繁请求)
ThreadPoolExecutor triggerPool_ = fastTriggerPool;
AtomicInteger jobTimeoutCount = jobTimeoutCountMap.get(jobId);
if (jobTimeoutCount!=null && jobTimeoutCount.get() > 10) { // job-timeout 10 times in 1 min
triggerPool_ = slowTriggerPool;
}
// 触发任务执行
triggerPool_.execute(new Runnable() {
@Override
public void run() {
long start = System.currentTimeMillis();
try {
XxlJobTrigger.trigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList);
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
// 刷新任务执行时间(单位:分钟)
long minTim_now = System.currentTimeMillis()/60000;
if (minTim != minTim_now) { // 执行结束时间 跨 分钟
minTim = minTim_now;// 刷新上次执行时间(单位:分钟)
jobTimeoutCountMap.clear();
}
// 消耗时间 大于 500ms, 任务执行超时次数+1
long cost = System.currentTimeMillis()-start;
if (cost > 500) {
AtomicInteger timeoutCount = jobTimeoutCountMap.putIfAbsent(jobId, new AtomicInteger(1));//不存在则赋后面的值
if (timeoutCount != null) {
timeoutCount.incrementAndGet();
}
}
}
}
});
}
private static JobTriggerPoolHelper helper = new JobTriggerPoolHelper();
public static void toStart() {
helper.start();
}
public static void toStop() {
helper.stop();
}
public static void trigger(int jobId, TriggerTypeEnum triggerType, int failRetryCount, String executorShardingParam, String executorParam, String addressList) {
helper.addTrigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList);
}
}
2、com.xxl.job.admin.core.thread.JobTriggerPoolHelper#toStart方法内部调用方法com.xxl.job.admin.core.trigger.XxlJobTrigger#trigger,具体实现如下
package com.xxl.job.admin.core.trigger;
import *;
public class XxlJobTrigger {
private static Logger logger = LoggerFactory.getLogger(XxlJobTrigger.class);
/**
* 触发任务执行
*/
public static void trigger(int jobId,
TriggerTypeEnum triggerType,
int failRetryCount,
String executorShardingParam,
String executorParam,
String addressList) {
// 组装任务执行参数,触发任务执行;通过jobId,加载数据
XxlJobInfo jobInfo = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(jobId);
if (jobInfo == null) {
return;
}
if (executorParam != null) {
jobInfo.setExecutorParam(executorParam);
}
int finalFailRetryCount = failRetryCount>=0?failRetryCount:jobInfo.getExecutorFailRetryCount();
XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().load(jobInfo.getJobGroup());
// 覆盖待执行地址(优先外部传入执行器地址)
if (addressList!=null && addressList.trim().length()>0) {
group.setAddressType(1);//执行器地址类型:0=自动注册、1=手动录入
group.setAddressList(addressList.trim());
}
// 组装分片参数 并赋值给变量shardingParam
int[] shardingParam = null;
if (executorShardingParam!=null){
String[] shardingArr = executorShardingParam.split("/");
if (shardingArr.length==2 && isNumeric(shardingArr[0]) && isNumeric(shardingArr[1])) {
shardingParam = new int[2];
shardingParam[0] = Integer.valueOf(shardingArr[0]);
shardingParam[1] = Integer.valueOf(shardingArr[1]);
}
}
// 路由策略 是否为 分片广播
if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST==ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null)
&& group.getRegistryList()!=null && !group.getRegistryList().isEmpty()
&& shardingParam==null) {
for (int i = 0; i < group.getRegistryList().size(); i++) {
processTrigger(group, jobInfo, finalFailRetryCount, triggerType, i, group.getRegistryList().size());
}
} else {
// 路由策略 为 其他9种
if (shardingParam == null) {
shardingParam = new int[]{0, 1};
}
// 执行任务
processTrigger(group, jobInfo, finalFailRetryCount, triggerType, shardingParam[0], shardingParam[1]);
}
}
private static boolean isNumeric(String str){
try {
int result = Integer.valueOf(str);
return true;
} catch (NumberFormatException e) {
return false;
}
}
/**
* 处理任务
*/
private static void processTrigger(XxlJobGroup group, XxlJobInfo jobInfo, int finalFailRetryCount, TriggerTypeEnum triggerType, int index, int total){
// 阻塞处理策略 枚举
ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), ExecutorBlockStrategyEnum.SERIAL_EXECUTION); // block strategy
// 路由策略 枚举
ExecutorRouteStrategyEnum executorRouteStrategyEnum = ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null); // route strategy
// 任务分片参数
String shardingParam = (ExecutorRouteStrategyEnum.SHARDING_BROADCAST==executorRouteStrategyEnum)?String.valueOf(index).concat("/").concat(String.valueOf(total)):null;
// 1、保存一条xxl_job_log记录(**)
XxlJobLog jobLog = new XxlJobLog();
jobLog.setJobGroup(jobInfo.getJobGroup());
jobLog.setJobId(jobInfo.getId());
jobLog.setTriggerTime(new Date());
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().save(jobLog);
// 2、初始化任务执行参数triggerParam, 用于后续远程调度入参 以及 具体路由地址入参
TriggerParam triggerParam = new TriggerParam();
triggerParam.setJobId(jobInfo.getId());
triggerParam.setExecutorHandler(jobInfo.getExecutorHandler());
triggerParam.setExecutorParams(jobInfo.getExecutorParam());
triggerParam.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy());
triggerParam.setExecutorTimeout(jobInfo.getExecutorTimeout());
triggerParam.setLogId(jobLog.getId());
triggerParam.setLogDateTime(jobLog.getTriggerTime().getTime());
triggerParam.setGlueType(jobInfo.getGlueType());
triggerParam.setGlueSource(jobInfo.getGlueSource());
triggerParam.setGlueUpdatetime(jobInfo.getGlueUpdatetime().getTime());
triggerParam.setBroadcastIndex(index);
triggerParam.setBroadcastTotal(total);
// 3、获取 具体任务执行地址 address
String address = null;
ReturnT<String> routeAddressResult = null;
if (group.getRegistryList()!=null && !group.getRegistryList().isEmpty()) {
if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum) {
// 分片广播 路由策略
if (index < group.getRegistryList().size()) {
address = group.getRegistryList().get(index);
} else {
address = group.getRegistryList().get(0);
}
} else {
// 另外9种分片广播策略
routeAddressResult = executorRouteStrategyEnum.getRouter().route(triggerParam, group.getRegistryList());
if (routeAddressResult.getCode() == ReturnT.SUCCESS_CODE) {
address = routeAddressResult.getContent();
}
}
} else {
routeAddressResult = new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobconf_trigger_address_empty"));
}
// 4、触发远程执行器
ReturnT<String> triggerResult = null;
if (address != null) {
triggerResult = runExecutor(triggerParam, address);//
} else {
// 获取具体执行地址为空,直接返回执行失败
triggerResult = new ReturnT<String>(ReturnT.FAIL_CODE, null);
}
// 5、收集 执行节点 信息
StringBuffer triggerMsgSb = new StringBuffer();
triggerMsgSb.append(I18nUtil.getString("jobconf_trigger_type")).append(":").append(triggerType.getTitle());
triggerMsgSb.append("
").append(I18nUtil.getString("jobconf_trigger_admin_adress")).append(":").append(IpUtil.getIp());
triggerMsgSb.append("
").append(I18nUtil.getString("jobconf_trigger_exe_regtype")).append(":")
.append( (group.getAddressType() == 0)?I18nUtil.getString("jobgroup_field_addressType_0"):I18nUtil.getString("jobgroup_field_addressType_1") );
triggerMsgSb.append("
").append(I18nUtil.getString("jobconf_trigger_exe_regaddress")).append(":").append(group.getRegistryList());
triggerMsgSb.append("
").append(I18nUtil.getString("jobinfo_field_executorRouteStrategy")).append(":").append(executorRouteStrategyEnum.getTitle());
if (shardingParam != null) {
triggerMsgSb.append("("+shardingParam+")");
}
triggerMsgSb.append("
").append(I18nUtil.getString("jobinfo_field_executorBlockStrategy")).append(":").append(blockStrategy.getTitle());
triggerMsgSb.append("
").append(I18nUtil.getString("jobinfo_field_timeout")).append(":").append(jobInfo.getExecutorTimeout());
triggerMsgSb.append("
").append(I18nUtil.getString("jobinfo_field_executorFailRetryCount")).append(":").append(finalFailRetryCount);
triggerMsgSb.append("
>>>"+ I18nUtil.getString("jobconf_trigger_run") +"<<<
")
.append((routeAddressResult!=null&&routeAddressResult.getMsg()!=null)?routeAddressResult.getMsg()+"
":"").append(triggerResult.getMsg()!=null?triggerResult.getMsg():"");
// 6、更新 执行节点 信息
jobLog.setExecutorAddress(address);
jobLog.setExecutorHandler(jobInfo.getExecutorHandler());
jobLog.setExecutorParam(jobInfo.getExecutorParam());
jobLog.setExecutorShardingParam(shardingParam);
jobLog.setExecutorFailRetryCount(finalFailRetryCount);
//jobLog.setTriggerTime();
jobLog.setTriggerCode(triggerResult.getCode());
jobLog.setTriggerMsg(triggerMsgSb.toString());
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(jobLog);
}
/**
* 执行程序运行
*/
public static ReturnT<String> runExecutor(TriggerParam triggerParam, String address){
ReturnT<String> runResult = null;
try {
ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
runResult = executorBiz.run(triggerParam);//
} catch (Exception e) {
runResult = new ReturnT<String>(ReturnT.FAIL_CODE, ThrowableUtil.toString(e));
}
StringBuffer runResultSB = new StringBuffer(I18nUtil.getString("jobconf_trigger_run") + ":");
runResultSB.append("
address:").append(address);
runResultSB.append("
code:").append(runResult.getCode());
runResultSB.append("
msg:").append(runResult.getMsg());
runResult.setMsg(runResultSB.toString());
return runResult;
}
}
1、com.xxl.job.admin.core.scheduler.XxlJobScheduler#init方法内部调用com.xxl.job.admin.core.thread.JobRegistryHelper#start方法,具体实现如下
package com.xxl.job.admin.core.thread;
import *;
public class JobRegistryHelper {
private static Logger logger = LoggerFactory.getLogger(JobRegistryHelper.class);
private static JobRegistryHelper instance = new JobRegistryHelper();
public static JobRegistryHelper getInstance(){
return instance;
}
private ThreadPoolExecutor registryOrRemoveThreadPool = null;
private Thread registryMonitorThread;
private volatile boolean toStop = false;
public void start(){
registryOrRemoveThreadPool = new ThreadPoolExecutor(
2,
10,
30L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(2000),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "xxl-job, admin JobRegistryMonitorHelper-registryOrRemoveThreadPool-" + r.hashCode());
}
},
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
r.run();
}
});
registryMonitorThread = new Thread(new Runnable() {
@Override
public void run() {
while (!toStop) {
try {
//扫描xxl_job_group表所有自动注册的记录(address_type 执行器地址类型:0=自动注册、1=手动录入)
List<XxlJobGroup> groupList = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().findByAddressType(0);
if (groupList!=null && !groupList.isEmpty()) {
//循环执行器,扫描xxl_job_registry表所有超时的执行器-任务执行节点记录(更新时间update_time超过90s)
List<Integer> ids = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().findDead(RegistryConfig.DEAD_TIMEOUT, new Date());
if (ids!=null && ids.size()>0) {
//移除超时的 任务执行节点记录
XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().removeDead(ids);
}
// > - < //
//刷新 执行器 所有在线 任务执行节点 信息,并存储到变量appAddressMap
HashMap<String, List<String>> appAddressMap = new HashMap<String, List<String>>();
//扫描xxl_job_registry表所有未超时的执行器-任务执行节点记录(更新时间update_time在90s之内)
List<XxlJobRegistry> list = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().findAll(RegistryConfig.DEAD_TIMEOUT, new Date());
if (list != null) {
for (XxlJobRegistry item: list) {
//过滤RegistryConfig.RegistType.ADMIN类型数据,找到RegistryConfig.RegistType.EXECUTOR数据
if (RegistryConfig.RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) {
String appname = item.getRegistryKey();
List<String> registryList = appAddressMap.get(appname);
if (registryList == null) {
registryList = new ArrayList<String>();
}
if (!registryList.contains(item.getRegistryValue())) {
registryList.add(item.getRegistryValue());
}
appAddressMap.put(appname, registryList);//xxl_job_registry registry_key
}
}
}
//刷新 所有 执行器地址类型 为 自动注册类型 xxl_job_group表 执行器信息
for (XxlJobGroup group: groupList) {
List<String> registryList = appAddressMap.get(group.getAppname());//xxl_job_group app_name
String addressListStr = null;
if (registryList!=null && !registryList.isEmpty()) {
Collections.sort(registryList);
StringBuilder addressListSB = new StringBuilder();
for (String item:registryList) {
addressListSB.append(item).append(",");
}
addressListStr = addressListSB.toString();
addressListStr = addressListStr.substring(0, addressListStr.length()-1);
}
group.setAddressList(addressListStr);
group.setUpdateTime(new Date());
XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().update(group);
}
}
} catch (Exception e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
// 睡眠30s
try {
TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
} catch (InterruptedException e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
}
}
});
registryMonitorThread.setDaemon(true);
registryMonitorThread.setName("xxl-job, admin JobRegistryMonitorHelper-registryMonitorThread");
registryMonitorThread.start();
}
public void toStop(){
toStop = true;
registryOrRemoveThreadPool.shutdownNow();
registryMonitorThread.interrupt();
try {
registryMonitorThread.join();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
/**
* 执行节点启动时,会每隔30s,主动向调度中心发起注册请求,即调用此方法
*/
public ReturnT<String> registry(RegistryParam registryParam) {
// 参数校验
if (!StringUtils.hasText(registryParam.getRegistryGroup())
|| !StringUtils.hasText(registryParam.getRegistryKey())
|| !StringUtils.hasText(registryParam.getRegistryValue())) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "Illegal Argument.");
}
registryOrRemoveThreadPool.execute(new Runnable() {
@Override
public void run() {
// 更新 xxl_job_registry表 任务执行节点 信息
int ret = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().registryUpdate(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
if (ret < 1) {
// 没找到对应记录,就新增一条 xxl_job_registry表 任务执行节点 记录
XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().registrySave(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue(), new Date());
// 刷新执行器 对应 任务执行节点信息(默认空实现)
freshGroupRegistryInfo(registryParam);
}
}
});
return ReturnT.SUCCESS;
}
/**
* 执行节点停止时,会主动向调度中心发起移除注册信息请求,即调用此方法
*/
public ReturnT<String> registryRemove(RegistryParam registryParam) {
// 参数校验
if (!StringUtils.hasText(registryParam.getRegistryGroup())
|| !StringUtils.hasText(registryParam.getRegistryKey())
|| !StringUtils.hasText(registryParam.getRegistryValue())) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "Illegal Argument.");
}
// async execute
registryOrRemoveThreadPool.execute(new Runnable() {
@Override
public void run() {
// 移除 xxl_job_registry表 任务执行节点 信息
int ret = XxlJobAdminConfig.getAdminConfig().getXxlJobRegistryDao().registryDelete(registryParam.getRegistryGroup(), registryParam.getRegistryKey(), registryParam.getRegistryValue());
if (ret > 0) {//执行成功
// 刷新执行器 对应 任务执行节点信息(默认空实现)
freshGroupRegistryInfo(registryParam);
}
}
});
return ReturnT.SUCCESS;
}
private void freshGroupRegistryInfo(RegistryParam registryParam){
}
}
1、com.xxl.job.admin.core.scheduler.XxlJobScheduler#init方法内部调用com.xxl.job.admin.core.thread.JobFailMonitorHelper#start方法,具体实现如下
package com.xxl.job.admin.core.thread;
import *;
public class JobFailMonitorHelper {
private static Logger logger = LoggerFactory.getLogger(JobFailMonitorHelper.class);
private static JobFailMonitorHelper instance = new JobFailMonitorHelper();
public static JobFailMonitorHelper getInstance(){
return instance;
}
private Thread monitorThread;
private volatile boolean toStop = false;
public void start(){
monitorThread = new Thread(new Runnable() {
@Override
public void run() {
while (!toStop) {
try {
// 循环扫描xxl_job_log表,每次取1000条记录,取出执行失败的记录集合
List<Long> failLogIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findFailJobLogIds(1000);
if (failLogIds!=null && !failLogIds.isEmpty()) {
for (long failLogId: failLogIds) {
// 更新记录状态,标记记录正在处理中;锁记录,如果更新失败表示此记录锁已被其他人持有 ---- 这也是xxl中db锁的具体应用之一
int lockRet = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateAlarmStatus(failLogId, 0, -1);
if (lockRet < 1) {
continue;
}
XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().load(failLogId);
XxlJobInfo info = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(log.getJobId());
// 1、失败任务重试
if (log.getExecutorFailRetryCount() > 0) {
// 任务重试!!!
JobTriggerPoolHelper.trigger(log.getJobId(), TriggerTypeEnum.RETRY, (log.getExecutorFailRetryCount()-1), log.getExecutorShardingParam(), log.getExecutorParam(), null);
String retryMsg = "
>>>"+ I18nUtil.getString("jobconf_trigger_type_retry") +"<<<
";
log.setTriggerMsg(log.getTriggerMsg() + retryMsg);
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(log);
}
// 2、执行 失败任务的报警 程序
int newAlarmStatus = 0; // 告警状态:0-默认、-1=锁定状态、1-无需告警、2-告警成功、3-告警失败
if (info != null) {
boolean alarmResult = XxlJobAdminConfig.getAdminConfig().getJobAlarmer().alarm(info, log);
newAlarmStatus = alarmResult?2:3;
} else {
newAlarmStatus = 1;
}
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateAlarmStatus(failLogId, -1, newAlarmStatus);
}
}
} catch (Exception e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
// 睡眠10s
try {
TimeUnit.SECONDS.sleep(10);
} catch (Exception e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
}
}
});
monitorThread.setDaemon(true);
monitorThread.setName("xxl-job, admin JobFailMonitorHelper");
monitorThread.start();
}
public void toStop(){
toStop = true;
monitorThread.interrupt();
try {
monitorThread.join();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
}
1、com.xxl.job.admin.core.scheduler.XxlJobScheduler#init方法内部调用com.xxl.job.admin.core.thread.JobCompleteHelper#start方法,具体实现如下
package com.xxl.job.admin.core.thread;
import *;
public class JobCompleteHelper {
private static Logger logger = LoggerFactory.getLogger(JobCompleteHelper.class);
private static JobCompleteHelper instance = new JobCompleteHelper();
public static JobCompleteHelper getInstance(){
return instance;
}
private ThreadPoolExecutor callbackThreadPool = null;
private Thread monitorThread;
private volatile boolean toStop = false;
public void start(){
callbackThreadPool = new ThreadPoolExecutor(
2,
20,
30L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(3000),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "xxl-job, admin JobLosedMonitorHelper-callbackThreadPool-" + r.hashCode());
}
},
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
r.run();
}
});
monitorThread = new Thread(new Runnable() {
@Override
public void run() {
// wait for JobTriggerPoolHelper-init
try {
TimeUnit.MILLISECONDS.sleep(50);
} catch (InterruptedException e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
while (!toStop) {
try {
// 任务结果丢失处理:调度记录停留在 "运行中" 状态超过10min,且对应执行器心跳注册失败不在线,则将本地调度主动标记失败;
Date losedTime = DateUtil.addMinutes(new Date(), -10);
// 扫描xxl_job_log表,找到所有执行结果丢失任务记录id集合
List<Long> losedJobIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findLostJobIds(losedTime);
if (losedJobIds!=null && losedJobIds.size()>0) {
for (Long logId: losedJobIds) {
XxlJobLog jobLog = new XxlJobLog();
jobLog.setId(logId);
jobLog.setHandleTime(new Date());// 设置处理时间
jobLog.setHandleCode(ReturnT.FAIL_CODE);// 设置处理结果状态码为500: 执行失败
jobLog.setHandleMsg( I18nUtil.getString("joblog_lost_fail") );// 任务结果丢失,标记失败
// 标记当前xxl_job_log表记录为执行失败
XxlJobCompleter.updateHandleInfoAndFinish(jobLog);
}
}
} catch (Exception e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
// 睡眠60s
try {
TimeUnit.SECONDS.sleep(60);
} catch (Exception e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
}
}
});
monitorThread.setDaemon(true);
monitorThread.setName("xxl-job, admin JobLosedMonitorHelper");
monitorThread.start();
}
public void toStop(){
toStop = true;
callbackThreadPool.shutdownNow();
monitorThread.interrupt();
try {
monitorThread.join();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
/**
* 执行节点启动后,
* 任务回调 监控线程 & 失败回调重试 监控线程 会主动消费 com.xxl.job.core.thread.TriggerCallbackThread.callBackQueue队列,
* 主动向调度中心发起回调请求,即调用此方法
*/
public ReturnT<String> callback(List<HandleCallbackParam> callbackParamList) {
callbackThreadPool.execute(new Runnable() {
@Override
public void run() {
for (HandleCallbackParam handleCallbackParam: callbackParamList) {
//执行回调方法
ReturnT<String> callbackResult = callback(handleCallbackParam);
}
}
});
return ReturnT.SUCCESS;
}
private ReturnT<String> callback(HandleCallbackParam handleCallbackParam) {
// 加载xxl_job_log表记录
XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().load(handleCallbackParam.getLogId());
if (log == null) {//callback方法执行的前提是xxl_job_log表记录存在
return new ReturnT<String>(ReturnT.FAIL_CODE, "log item not found.");
}
// 判断handleCode避免重复执行
if (log.getHandleCode() > 0) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "log repeate callback."); // avoid repeat callback, trigger child job etc
}
// 重设handleMsg字段记录
StringBuffer handleMsg = new StringBuffer();
if (log.getHandleMsg()!=null) {
handleMsg.append(log.getHandleMsg()).append("
");
}
if (handleCallbackParam.getHandleMsg() != null) {
handleMsg.append(handleCallbackParam.getHandleMsg());
}
// 结束当前任务执行,如果有子任务则触发子任务执行,并且更新xxl_job_log表记录(更新handle_time、handle_code、handle_msg这三个字段)
log.setHandleTime(new Date());
log.setHandleCode(handleCallbackParam.getHandleCode());
log.setHandleMsg(handleMsg.toString());
XxlJobCompleter.updateHandleInfoAndFinish(log);
return ReturnT.SUCCESS;
}
}
1、com.xxl.job.admin.core.scheduler.XxlJobScheduler#init方法内部调用com.xxl.job.admin.core.thread.JobLogReportHelper#start方法,具体实现如下
package com.xxl.job.admin.core.thread;
import *;
public class JobLogReportHelper {
private static Logger logger = LoggerFactory.getLogger(JobLogReportHelper.class);
private static JobLogReportHelper instance = new JobLogReportHelper();
public static JobLogReportHelper getInstance(){
return instance;
}
private Thread logrThread;
private volatile boolean toStop = false;
public void start(){
logrThread = new Thread(new Runnable() {
@Override
public void run() {
long lastCleanLogTime = 0;// lastCleanLogTime上次清理日志时间
while (!toStop) {
// 1、刷新3天内xxl_job_log_report表,每日任务执行情况统计记录
try {
for (int i = 0; i < 3; i++) {
Calendar itemDay = Calendar.getInstance();
itemDay.add(Calendar.DAY_OF_MONTH, -i);
itemDay.set(Calendar.HOUR_OF_DAY, 0);
itemDay.set(Calendar.MINUTE, 0);
itemDay.set(Calendar.SECOND, 0);
itemDay.set(Calendar.MILLISECOND, 0);
Date todayFrom = itemDay.getTime();
itemDay.set(Calendar.HOUR_OF_DAY, 23);
itemDay.set(Calendar.MINUTE, 59);
itemDay.set(Calendar.SECOND, 59);
itemDay.set(Calendar.MILLISECOND, 999);
Date todayTo = itemDay.getTime();
XxlJobLogReport xxlJobLogReport = new XxlJobLogReport();
xxlJobLogReport.setTriggerDay(todayFrom);
xxlJobLogReport.setRunningCount(0);
xxlJobLogReport.setSucCount(0);
xxlJobLogReport.setFailCount(0);
// 查询时间段内xxl_job_log表执行情况统计
Map<String, Object> triggerCountMap = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findLogReport(todayFrom, todayTo);
if (triggerCountMap!=null && triggerCountMap.size()>0) {
int triggerDayCount = triggerCountMap.containsKey("triggerDayCount")?Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCount"))):0;
int triggerDayCountRunning = triggerCountMap.containsKey("triggerDayCountRunning")?Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCountRunning"))):0;
int triggerDayCountSuc = triggerCountMap.containsKey("triggerDayCountSuc")?Integer.valueOf(String.valueOf(triggerCountMap.get("triggerDayCountSuc"))):0;
int triggerDayCountFail = triggerDayCount - triggerDayCountRunning - triggerDayCountSuc;
xxlJobLogReport.setRunningCount(triggerDayCountRunning);// 正在运行任务总数
xxlJobLogReport.setSucCount(triggerDayCountSuc);// 执行成功任务总数
xxlJobLogReport.setFailCount(triggerDayCountFail);// 执行失败任务总数
}
// 更新xxl_job_log_report表记录
int ret = XxlJobAdminConfig.getAdminConfig().getXxlJobLogReportDao().update(xxlJobLogReport);
if (ret < 1) {
// 不存在,则执行保存
XxlJobAdminConfig.getAdminConfig().getXxlJobLogReportDao().save(xxlJobLogReport);
}
}
} catch (Exception e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
// 2、判断日志最大保留时间是否开启 & 距离上次日志清理时间 超过1天
if (XxlJobAdminConfig.getAdminConfig().getLogretentiondays()>0
&& System.currentTimeMillis() - lastCleanLogTime > 24*60*60*1000) {
Calendar expiredDay = Calendar.getInstance();// 过期时间
expiredDay.add(Calendar.DAY_OF_MONTH, -1 * XxlJobAdminConfig.getAdminConfig().getLogretentiondays());
expiredDay.set(Calendar.HOUR_OF_DAY, 0);
expiredDay.set(Calendar.MINUTE, 0);
expiredDay.set(Calendar.SECOND, 0);
expiredDay.set(Calendar.MILLISECOND, 0);
Date clearBeforeTime = expiredDay.getTime();
// 清理过期日志
List<Long> logIds = null;
do {
logIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findClearLogIds(0, 0, clearBeforeTime, 0, 1000);
if (logIds!=null && logIds.size()>0) {
// 清理xxl_job_log表记录
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().clearLog(logIds);
}
} while (logIds!=null && logIds.size()>0);
// 更新上次清理时间
lastCleanLogTime = System.currentTimeMillis();
}
// 睡眠1分钟
try {
TimeUnit.MINUTES.sleep(1);
} catch (Exception e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
}
}
});
logrThread.setDaemon(true);
logrThread.setName("xxl-job, admin JobLogReportHelper");
logrThread.start();
}
public void toStop(){
toStop = true;
// interrupt and wait
logrThread.interrupt();
try {
logrThread.join();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
}
1、com.xxl.job.admin.core.scheduler.XxlJobScheduler#init方法内部调用com.xxl.job.admin.core.thread.JobScheduleHelper#start方法,具体实现如下
package com.xxl.job.admin.core.thread;
import *;
public class JobScheduleHelper {
private static Logger logger = LoggerFactory.getLogger(JobScheduleHelper.class);
private static JobScheduleHelper instance = new JobScheduleHelper();
public static JobScheduleHelper getInstance(){
return instance;
}
public static final long PRE_READ_MS = 5000;// 任务间隔大小
private Thread scheduleThread;
private Thread ringThread;
private volatile boolean scheduleThreadToStop = false; // 任务调度线程 是否关闭
private volatile boolean ringThreadToStop = false;
private volatile static Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>();
public void start(){
// schedule thread
scheduleThread = new Thread(new Runnable() {
@Override
public void run() {
// 睡眠
try {
TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis()%1000 );
} catch (InterruptedException e) {
if (!scheduleThreadToStop) {
logger.error(e.getMessage(), e);
}
}
// pre-read count: treadpool-size * trigger-qps (each trigger cost 50ms, qps = 1000/50 = 20)
// 每次读取到任务个数 = (快任务 + 慢任务-调度线程池最大线程配置)* 20 (每个trigger花费50ms, QPS = 1000/50 = 20)
int preReadCount = (XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax() + XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax()) * 20;
while (!scheduleThreadToStop) {
// 扫描任务
long start = System.currentTimeMillis();
Connection conn = null;
Boolean connAutoCommit = null;
PreparedStatement preparedStatement = null;
boolean preReadSuc = true;
try {
conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection();
connAutoCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
// https://github.com/xuxueli/xxl-job/pull/2766
// 集群模式下xxl-job进行任务调度时,为了避免任务被重复调度,因此会先通过以下SQL对表xxl_job_lock的记录schedule_lock加锁
preparedStatement = conn.prepareStatement( "select * from xxl_job_lock where lock_name = 'schedule_lock' for update" );
preparedStatement.execute();
// tx start
// 1、预读开始
long nowTime = System.currentTimeMillis();
// 查询 xxl_job_info表 trigger_status =1 && trigger_next_time 小于等于 当前时间 + 5000ms,就是接下来 5 秒要执行到任务
List<XxlJobInfo> scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount);
if (scheduleList!=null && scheduleList.size()>0) {
for (XxlJobInfo jobInfo: scheduleList) {
/**
* - - - - - - - - -17-18-19-20-21-22-23-24-25-26-27-28-29-30-31-32-34-35-36-37- - - - - - - - - - - - - - -
* ^ ^ ^
* | | |
* | | <- query
* 调度过期 <-|-> 立即执行 now 待执行 <-|
*/
if (nowTime > jobInfo.getTriggerNextTime() + PRE_READ_MS) {// 调度过期
// 1、调度过期策略 为 立即执行(则任务触发类型-调度过期补偿 : 立即执行一次)
MisfireStrategyEnum misfireStrategyEnum = MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), MisfireStrategyEnum.DO_NOTHING);
if (MisfireStrategyEnum.FIRE_ONCE_NOW == misfireStrategyEnum) {
JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.MISFIRE, -1, null, null, null);
}
// 2、刷新XxlJobInfo 对象 调度状态、上次调度时间、下次调度时间
refreshNextValidTime(jobInfo, new Date());
} else if (nowTime > jobInfo.getTriggerNextTime()) {// 当前时间往前推5s之内
// 1、任务触发类型-Cron触发 : 立即执行一次
JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null);
// 2、刷新XxlJobInfo 对象 调度状态、上次调度时间、下次调度时间
refreshNextValidTime(jobInfo, new Date());
// 3、如果接下来 5 秒内还执行则直接放到时间轮中
if (jobInfo.getTriggerStatus()==1 && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) {
int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
pushTimeRing(ringSecond, jobInfo.getId());
refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
}
} else {// 当前时间往后推5s之内
// 1、将XxlJobInfo id直接放到时间轮中
int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60);
pushTimeRing(ringSecond, jobInfo.getId());
refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime()));
}
}
// 3、更新XxlJobInfo 对象(调度状态、上次调度时间、下次调度时间)
for (XxlJobInfo jobInfo: scheduleList) {
XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo);
}
} else {
preReadSuc = false;// 预读失败
}
// tx stop
} catch (Exception e) {
if (!scheduleThreadToStop) {
logger.error(e.getMessage(), e);
}
} finally {
// 事务提交,释放链接
if (conn != null) {
try {
conn.commit();
} catch (SQLException e) {
if (!scheduleThreadToStop) {
logger.error(e.getMessage(), e);
}
}
try {
conn.setAutoCommit(connAutoCommit);
} catch (SQLException e) {
if (!scheduleThreadToStop) {
logger.error(e.getMessage(), e);
}
}
try {
conn.close();
} catch (SQLException e) {
if (!scheduleThreadToStop) {
logger.error(e.getMessage(), e);
}
}
}
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException e) {
if (!scheduleThreadToStop) {
logger.error(e.getMessage(), e);
}
}
}
}
long cost = System.currentTimeMillis()-start;
// 等待数秒,时间对其(方式频繁请求数据库)
if (cost < 1000) { // 扫描时间小于1秒,等待
try {
// 预读周期 : 执行成功,睡眠1s内; 执行失败,睡眠5s内;
TimeUnit.MILLISECONDS.sleep((preReadSuc?1000:PRE_READ_MS) - System.currentTimeMillis()%1000);
} catch (InterruptedException e) {
if (!scheduleThreadToStop) {
logger.error(e.getMessage(), e);
}
}
}
}
}
});
scheduleThread.setDaemon(true);
scheduleThread.setName("xxl-job, admin JobScheduleHelper#scheduleThread");
scheduleThread.start();
ringThread = new Thread(new Runnable() {
@Override
public void run() {
while (!ringThreadToStop) {
// 时间对其, 睡眠后刚好是整数秒(程序读秒)
try {
TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis() % 1000);
} catch (InterruptedException e) {
if (!ringThreadToStop) {
logger.error(e.getMessage(), e);
}
}
try {
// 取2个刻度 时间环 待执行任务
List<Integer> ringItemData = new ArrayList<>();
int nowSecond = Calendar.getInstance().get(Calendar.SECOND); // 避免处理耗时太长,跨过刻度,向前校验一个刻度;
for (int i = 0; i < 2; i++) {
List<Integer> tmpData = ringData.remove( (nowSecond+60-i)%60 );
if (tmpData != null) {
ringItemData.addAll(tmpData);
}
}
// 循环执行任务
if (ringItemData.size() > 0) {
for (int jobId: ringItemData) {
JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null, null);
}
ringItemData.clear();
}
} catch (Exception e) {
if (!ringThreadToStop) {
logger.error(e.getMessage(), e);
}
}
}
}
});
ringThread.setDaemon(true);
ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread");
ringThread.start();
}
/**
* 刷新 调度状态、上次调度时间、下次调度时间
*/
private void refreshNextValidTime(XxlJobInfo jobInfo, Date fromTime) throws Exception {
Date nextValidTime = generateNextValidTime(jobInfo, fromTime);
if (nextValidTime != null) {
jobInfo.setTriggerLastTime(jobInfo.getTriggerNextTime());
jobInfo.setTriggerNextTime(nextValidTime.getTime());
} else {
jobInfo.setTriggerStatus(0);// 调度状态:0-停止,1-运行
jobInfo.setTriggerLastTime(0);
jobInfo.setTriggerNextTime(0);
}
}
private void pushTimeRing(int ringSecond, int jobId){
// push async ring
List<Integer> ringItemData = ringData.get(ringSecond);
if (ringItemData == null) {
ringItemData = new ArrayList<Integer>();
ringData.put(ringSecond, ringItemData);
}
ringItemData.add(jobId);
}
public void toStop(){
scheduleThreadToStop = true;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
if (scheduleThread.getState() != Thread.State.TERMINATED){
scheduleThread.interrupt();
try {
scheduleThread.join();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
boolean hasRingData = false;
if (!ringData.isEmpty()) {
for (int second : ringData.keySet()) {
List<Integer> tmpData = ringData.get(second);
if (tmpData!=null && tmpData.size()>0) {
hasRingData = true;
break;
}
}
}
if (hasRingData) {
try {
TimeUnit.SECONDS.sleep(8);
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
ringThreadToStop = true;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
if (ringThread.getState() != Thread.State.TERMINATED){
ringThread.interrupt();
try {
ringThread.join();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
}
/**
* 生成下次执行时间
*/
public static Date generateNextValidTime(XxlJobInfo jobInfo, Date fromTime) throws Exception {
ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(jobInfo.getScheduleType(), null);
if (ScheduleTypeEnum.CRON == scheduleTypeEnum) {// 调度类型 : CORN
Date nextValidTime = new CronExpression(jobInfo.getScheduleConf()).getNextValidTimeAfter(fromTime);
return nextValidTime;
} else if (ScheduleTypeEnum.FIX_RATE == scheduleTypeEnum) {// 调度类型 : 固定速度
return new Date(fromTime.getTime() + Integer.valueOf(jobInfo.getScheduleConf())*1000 );
}
return null;
}
}
1、执行器为普通java项目,启动方法为com.xxl.job.executor.sample.frameless.FramelessApplication#main,具体代码实现如下
package com.xxl.job.executor.sample.frameless;
import *;
public class FramelessApplication {
private static Logger logger = LoggerFactory.getLogger(FramelessApplication.class);
public static void main(String[] args) {
try {
FrameLessXxlJobConfig.getInstance().initXxlJobExecutor();
// 阻塞直到线程被打断
while (true) {
try {
TimeUnit.HOURS.sleep(1);
} catch (InterruptedException e) {
break;
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
FrameLessXxlJobConfig.getInstance().destroyXxlJobExecutor();
}
}
}
1、com.xxl.job.executor.sample.frameless.FramelessApplication#main方法,内部调用com.xxl.job.executor.sample.frameless.config.FrameLessXxlJobConfig#initXxlJobExecutor方法,具体实现如下
package com.xxl.job.executor.sample.frameless.config;
import *;
public class FrameLessXxlJobConfig {
private static Logger logger = LoggerFactory.getLogger(FrameLessXxlJobConfig.class);
private static FrameLessXxlJobConfig instance = new FrameLessXxlJobConfig();
public static FrameLessXxlJobConfig getInstance() {
return instance;
}
private XxlJobSimpleExecutor xxlJobExecutor = null;
public void initXxlJobExecutor() {
// 1、加载resources/xxl-job-executor.properties配置文件
Properties xxlJobProp = loadProperties("xxl-job-executor.properties");
// 2、初始化XxlJobSimpleExecutor对象
xxlJobExecutor = new XxlJobSimpleExecutor();
// 2.1、调度中心部署根地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
// 执行器回调地址(xxl.job.admin.addresses)需要保持一致;执行器根据该配置进行执行器自动注册等操作。
xxlJobExecutor.setAdminAddresses(xxlJobProp.getProperty("xxl.job.admin.addresses"));
// 2.2、为提升系统安全性,调度中心和执行器进行安全性校验,双方AccessToken匹配才允许通讯;
// 调度中心和执行器,可通过配置项 "xxl.job.accessToken" 进行AccessToken的设置。
// 调度中心和执行器,如果需要正常通讯,只有两种设置;
// - 设置一:调度中心和执行器,均不设置AccessToken;关闭安全性校验;
// - 设置二:调度中心和执行器,设置了相同的AccessToken;
xxlJobExecutor.setAccessToken(xxlJobProp.getProperty("xxl.job.accessToken"));
// 2.3、执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
// 同一个执行器集群内AppName(xxl.job.executor.appname)需要保持一致;调度中心根据该配置动态发现不同集群的在线执行器列表。
xxlJobExecutor.setAppname(xxlJobProp.getProperty("xxl.job.executor.appname"));
// 2.4、”注册地址 / xxl.job.executor.address“,优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。
// 从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
xxlJobExecutor.setAddress(xxlJobProp.getProperty("xxl.job.executor.address"));
// 2.5、执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
xxlJobExecutor.setIp(xxlJobProp.getProperty("xxl.job.executor.ip"));
// 2.6、执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
// 执行器实际上是一个内嵌的Server,默认端口9999
xxlJobExecutor.setPort(Integer.valueOf(xxlJobProp.getProperty("xxl.job.executor.port")));
// 2.7、执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
xxlJobExecutor.setLogPath(xxlJobProp.getProperty("xxl.job.executor.logpath"));
// 2.8、执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
xxlJobExecutor.setLogRetentionDays(Integer.valueOf(xxlJobProp.getProperty("xxl.job.executor.logretentiondays")));
// 3、注册xxl执行器bean对象
xxlJobExecutor.setXxlJobBeanList(Arrays.asList(new SampleXxlJob()));
try {
// 4、执行启动方法
xxlJobExecutor.start();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
public void destroyXxlJobExecutor() {
if (xxlJobExecutor != null) {
xxlJobExecutor.destroy();
}
}
public static Properties loadProperties(String propertyFileName) {
InputStreamReader in = null;
try {
ClassLoader loder = Thread.currentThread().getContextClassLoader();
in = new InputStreamReader(loder.getResourceAsStream(propertyFileName), "UTF-8");;
if (in != null) {
Properties prop = new Properties();
prop.load(in);
return prop;
}
} catch (IOException e) {
logger.error(e.getMessage(), e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
}
return null;
}
}
1、com.xxl.job.executor.sample.frameless.config.FrameLessXxlJobConfig#initXxlJobExecutor方法,内部调用com.xxl.job.core.executor.impl.XxlJobSimpleExecutor#start方法,具体实现如下
package com.xxl.job.core.executor.impl;
import *;
public class XxlJobSimpleExecutor extends XxlJobExecutor {
private static final Logger logger = LoggerFactory.getLogger(XxlJobSimpleExecutor.class);
private List<Object> xxlJobBeanList = new ArrayList<>();
public List<Object> getXxlJobBeanList() {
return xxlJobBeanList;
}
public void setXxlJobBeanList(List<Object> xxlJobBeanList) {
this.xxlJobBeanList = xxlJobBeanList;
}
@Override
public void start() {
// 初始化 xxl执行器处理方法 仓库
initJobHandlerMethodRepository(xxlJobBeanList);
// 执行启动方法
try {
super.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void destroy() {
super.destroy();
}
/**
* 初始化 xxl执行器处理方法 仓库
*/
private void initJobHandlerMethodRepository(List<Object> xxlJobBeanList) {
if (xxlJobBeanList==null || xxlJobBeanList.size()==0) {
return;
}
for (Object bean: xxlJobBeanList) {
Method[] methods = bean.getClass().getDeclaredMethods();
if (methods.length == 0) {
continue;
}
for (Method executeMethod : methods) {
XxlJob xxlJob = executeMethod.getAnnotation(XxlJob.class);
// 注册xxl执行器处理方法
// 将@XxlJob 上注册的 执行器方法、初始化方法、销毁方法 存储到变量com.xxl.job.core.executor.XxlJobExecutor#jobHandlerRepository
registJobHandler(xxlJob, bean, executeMethod);
}
}
}
}
1、com.xxl.job.core.executor.impl.XxlJobSimpleExecutor#start方法,内部com.xxl.job.core.executor.impl.XxlJobSimpleExecutor#initJobHandlerMethodRepository方法,调用基础类的com.xxl.job.core.executor.XxlJobExecutor#registJobHandler方法,具体代码实现如下
2、com.xxl.job.core.executor.impl.XxlJobSimpleExecutor#start方法,内部调用基础类的com.xxl.job.core.executor.XxlJobExecutor#start方法,具体代码实现如下
package com.xxl.job.core.executor;
import *;
public class XxlJobExecutor {
private static final Logger logger = LoggerFactory.getLogger(XxlJobExecutor.class);
private String adminAddresses;
private String accessToken;
private String appname;
private String address;
private String ip;
private int port;
private String logPath;
private int logRetentionDays;
public void setAdminAddresses(String adminAddresses) {
this.adminAddresses = adminAddresses;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public void setAppname(String appname) {
this.appname = appname;
}
public void setAddress(String address) {
this.address = address;
}
public void setIp(String ip) {
this.ip = ip;
}
public void setPort(int port) {
this.port = port;
}
public void setLogPath(String logPath) {
this.logPath = logPath;
}
public void setLogRetentionDays(int logRetentionDays) {
this.logRetentionDays = logRetentionDays;
}
public void start() throws Exception {
// 1、初始化 执行器运行日志文件存储磁盘路径(xxl.job.executor.logpath)、及gluesource子目录
XxlJobFileAppender.initLogPath(logPath);
// 2、初始化 调度中心client请求对象(xxl.job.admin.addresses,xxl.job.accessToken)
// 并赋值给com.xxl.job.core.executor.XxlJobExecutor.adminBizList对象
initAdminBizList(adminAddresses, accessToken);
// 3、启动 过期日志自动清理 监控线程
JobLogFileCleanThread.getInstance().start(logRetentionDays);
// 4.1、启动 任务回调 监控线程,并赋值给com.xxl.job.core.thread.TriggerCallbackThread.triggerCallbackThread
// 主要负责消耗com.xxl.job.core.thread.TriggerCallbackThread.callBackQueue队列中的数据,进行任务回调
// 4.2、启动 失败回调重试 监控线程,并赋值给com.xxl.job.core.thread.TriggerCallbackThread.triggerRetryCallbackThread
// 主要负责 加载失败回调参数(磁盘文件反序列化),并主动执行回调方法
TriggerCallbackThread.getInstance().start();
// 5、初始化内嵌服务,用户注册中心调用(netty实现)
initEmbedServer(address, ip, port, appname, accessToken);
}
public void destroy(){
// destroy executor-server
stopEmbedServer();
// destroy jobThreadRepository
if (jobThreadRepository.size() > 0) {
for (Map.Entry<Integer, JobThread> item: jobThreadRepository.entrySet()) {
JobThread oldJobThread = removeJobThread(item.getKey(), "web container destroy and kill the job.");
// wait for job thread push result to callback queue
if (oldJobThread != null) {
try {
oldJobThread.join();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
}
jobThreadRepository.clear();
}
jobHandlerRepository.clear();
JobLogFileCleanThread.getInstance().toStop();
TriggerCallbackThread.getInstance().toStop();
}
private static List<AdminBiz> adminBizList;// 调度中心client请求对象集合
/**
* 初始化 调度中心client请求对象,并赋值给变量com.xxl.job.core.executor.XxlJobExecutor.adminBizList
*/
private void initAdminBizList(String adminAddresses, String accessToken) throws Exception {
if (adminAddresses!=null && adminAddresses.trim().length()>0) {
for (String address: adminAddresses.trim().split(",")) { // 多个逗号分割
if (address!=null && address.trim().length()>0) {
AdminBiz adminBiz = new AdminBizClient(address.trim(), accessToken);
if (adminBizList == null) {
adminBizList = new ArrayList<AdminBiz>();
}
adminBizList.add(adminBiz);
}
}
}
}
public static List<AdminBiz> getAdminBizList(){
return adminBizList;
}
private EmbedServer embedServer = null;
private void initEmbedServer(String address, String ip, int port, String appname, String accessToken) throws Exception {
// 1、获取ip、port(赋予初始值)
port = port>0?port: NetUtil.findAvailablePort(9999);// 获取可用的端口,默认端口9999
ip = (ip!=null&&ip.trim().length()>0)?ip: IpUtil.getIp();// 获取本机地址
// 2、默认使用address来注册,如果address为空则使用ip:port
if (address==null || address.trim().length()==0) {
String ip_port_address = IpUtil.getIpPort(ip, port);
address = "http://{ip_port}/".replace("{ip_port}", ip_port_address);
}
if (accessToken==null || accessToken.trim().length()==0) {
logger.warn("xxl-job accessToken is empty.");
}
// 3、启动netty服务 监控线程
embedServer = new EmbedServer();
embedServer.start(address, port, appname, accessToken);
}
private void stopEmbedServer() {
// stop provider factory
if (embedServer != null) {
try {
embedServer.stop();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
private static ConcurrentMap<String, IJobHandler> jobHandlerRepository = new ConcurrentHashMap<String, IJobHandler>();
public static IJobHandler loadJobHandler(String name){
return jobHandlerRepository.get(name);
}
/**
* 存储到变量com.xxl.job.core.executor.XxlJobExecutor#jobHandlerRepository
*/
public static IJobHandler registJobHandler(String name, IJobHandler jobHandler){
return jobHandlerRepository.put(name, jobHandler);
}
/**
* 注册xxl执行器处理方法(执行器方法、初始化方法、销毁方法)
*/
protected void registJobHandler(XxlJob xxlJob, Object bean, Method executeMethod){
// 1、跳过未加@XxlJob注解方法
if (xxlJob == null) {
return;
}
// 2、获取@XxlJob注解 上配置的 执行器方法、init方法、destory方法配置
String name = xxlJob.value();// 获取执行器名称
Class<?> clazz = bean.getClass();// 获取bean全类名
String methodName = executeMethod.getName();// 获取方法名称
if (name.trim().length() == 0) {
throw new RuntimeException("xxl-job method-jobhandler name invalid");
}
if (loadJobHandler(name) != null) {
throw new RuntimeException("xxl-job jobhandler[" + name + "] naming conflicts.");
}
executeMethod.setAccessible(true);// 打开反射访问权限
// 获取@XxlJob注解上配置的init初始化方法、destroy销毁方法
Method initMethod = null;
Method destroyMethod = null;
if (xxlJob.init().trim().length() > 0) {
try {
initMethod = clazz.getDeclaredMethod(xxlJob.init());
initMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
if (xxlJob.destroy().trim().length() > 0) {
try {
destroyMethod = clazz.getDeclaredMethod(xxlJob.destroy());
destroyMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
// 注册jobhandler(存储到变量com.xxl.job.core.executor.XxlJobExecutor#jobHandlerRepository)
registJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod));// MethodJobHandler extends IJobHandler
}
private static ConcurrentMap<Integer, JobThread> jobThreadRepository = new ConcurrentHashMap<Integer, JobThread>();
public static JobThread registJobThread(int jobId, IJobHandler handler, String removeOldReason){
JobThread newJobThread = new JobThread(jobId, handler);
newJobThread.start();// 线程启动
JobThread oldJobThread = jobThreadRepository.put(jobId, newJobThread);
if (oldJobThread != null) {
oldJobThread.toStop(removeOldReason);
oldJobThread.interrupt();
}
return newJobThread;
}
/**
* 从com.xxl.job.core.executor.XxlJobExecutor.jobThreadRepository缓存中移除JobThread,并且停止线程
*/
public static JobThread removeJobThread(int jobId, String removeOldReason){
JobThread oldJobThread = jobThreadRepository.remove(jobId);
if (oldJobThread != null) {
oldJobThread.toStop(removeOldReason);
oldJobThread.interrupt();
return oldJobThread;
}
return null;
}
public static JobThread loadJobThread(int jobId){
return jobThreadRepository.get(jobId);
}
}
1、咱们对com.xxl.job.core.executor.XxlJobExecutor#start实现进行逐行分析,首先执行com.xxl.job.core.log.XxlJobFileAppender#initLogPath方法,具体代码实现如下
package com.xxl.job.core.log;
import *;
public class XxlJobFileAppender {
private static Logger logger = LoggerFactory.getLogger(XxlJobFileAppender.class);
private static String logBasePath = "/data/applogs/xxl-job/jobhandler";
private static String glueSrcPath = logBasePath.concat("/gluesource");
public static void initLogPath(String logPath){
// logPath值不为空,则覆盖com.xxl.job.core.log.XxlJobFileAppender.logBasePath的值
if (logPath!=null && logPath.trim().length()>0) {
logBasePath = logPath;
}
// 创建目录
File logPathDir = new File(logBasePath);
if (!logPathDir.exists()) {
logPathDir.mkdirs();
}
logBasePath = logPathDir.getPath();// 获取真实路径,并覆盖logBasePath的值
// 创建gluesource子目录
File glueBaseDir = new File(logPathDir, "gluesource");
if (!glueBaseDir.exists()) {
glueBaseDir.mkdirs();
}
glueSrcPath = glueBaseDir.getPath();// 获取真实路径,并覆盖glueSrcPath的值
}
public static String getLogPath() {
return logBasePath;
}
public static String getGlueSrcPath() {
return glueSrcPath;
}
/**
* 获取日志文件 路径名称(目录不存在则先创建目录)
*/
public static String makeLogFileName(Date triggerDate, long logId) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
File logFilePath = new File(getLogPath(), sdf.format(triggerDate));
if (!logFilePath.exists()) {
logFilePath.mkdir();
}
String logFileName = logFilePath.getPath()
.concat(File.separator)
.concat(String.valueOf(logId))
.concat(".log");
return logFileName;
}
public static void appendLog(String logFileName, String appendLog) {
if (logFileName==null || logFileName.trim().length()==0) {
return;
}
File logFile = new File(logFileName);
if (!logFile.exists()) {
try {
logFile.createNewFile();
} catch (IOException e) {
logger.error(e.getMessage(), e);
return;
}
}
if (appendLog == null) {
appendLog = "";
}
appendLog += "\r\n";
FileOutputStream fos = null;
try {
fos = new FileOutputStream(logFile, true);
fos.write(appendLog.getBytes("utf-8"));
fos.flush();
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
}
}
public static LogResult readLog(String logFileName, int fromLineNum){
if (logFileName==null || logFileName.trim().length()==0) {
return new LogResult(fromLineNum, 0, "readLog fail, logFile not found", true);
}
File logFile = new File(logFileName);
if (!logFile.exists()) {
return new LogResult(fromLineNum, 0, "readLog fail, logFile not exists", true);
}
StringBuffer logContentBuffer = new StringBuffer();
int toLineNum = 0;
LineNumberReader reader = null;
try {
reader = new LineNumberReader(new InputStreamReader(new FileInputStream(logFile), "utf-8"));
String line = null;
while ((line = reader.readLine())!=null) {
toLineNum = reader.getLineNumber();
if (toLineNum >= fromLineNum) {
logContentBuffer.append(line).append("\n");
}
}
} catch (IOException e) {
logger.error(e.getMessage(), e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
}
LogResult logResult = new LogResult(fromLineNum, toLineNum, logContentBuffer.toString(), false);
return logResult;
}
public static String readLines(File logFile){
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(new FileInputStream(logFile), "utf-8"));
if (reader != null) {
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
return sb.toString();
}
} catch (IOException e) {
logger.error(e.getMessage(), e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
}
return null;
}
}
1、com.xxl.job.core.executor.XxlJobExecutor#start内部执行com.xxl.job.core.executor.XxlJobExecutor#initAdminBizList方法,具体现实见step 4
1、com.xxl.job.core.executor.XxlJobExecutor#start内部调用com.xxl.job.core.thread.JobLogFileCleanThread#start方法,具体代码实现如下
package com.xxl.job.core.thread;
import *;
public class JobLogFileCleanThread {
private static Logger logger = LoggerFactory.getLogger(JobLogFileCleanThread.class);
private static JobLogFileCleanThread instance = new JobLogFileCleanThread();
public static JobLogFileCleanThread getInstance(){
return instance;
}
private Thread localThread;
private volatile boolean toStop = false;
public void start(final long logRetentionDays){
// 执行器日志文件保存天数xxl.job.executor.logretentiondays必须大于3,否则关闭自动清理功能
if (logRetentionDays < 3 ) {
return;
}
localThread = new Thread(new Runnable() {
@Override
public void run() {
while (!toStop) {
try {
// 清理执行器运行日志文件存储磁盘路径(xxl.job.executor.logpath)下,所有过期(xxl.job.executor.logretentiondays)日志
File[] childDirs = new File(XxlJobFileAppender.getLogPath()).listFiles();
if (childDirs!=null && childDirs.length>0) {
Calendar todayCal = Calendar.getInstance();
todayCal.set(Calendar.HOUR_OF_DAY,0);
todayCal.set(Calendar.MINUTE,0);
todayCal.set(Calendar.SECOND,0);
todayCal.set(Calendar.MILLISECOND,0);
Date todayDate = todayCal.getTime();
for (File childFile: childDirs) {
// 不是目录 或者 文件夹不包含-,则返回
if (!childFile.isDirectory()) {
continue;
}
if (childFile.getName().indexOf("-") == -1) {
continue;
}
// 获取文件夹创建时间(形如yyyy-MM-dd)
Date logFileCreateDate = null;
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
logFileCreateDate = simpleDateFormat.parse(childFile.getName());
} catch (ParseException e) {
logger.error(e.getMessage(), e);
}
if (logFileCreateDate == null) {
continue;
}
// 过期,则递归删除此目录
if ((todayDate.getTime()-logFileCreateDate.getTime()) >= logRetentionDays * (24 * 60 * 60 * 1000) ) {
FileUtil.deleteRecursively(childFile);
}
}
}
} catch (Exception e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
// 睡眠一天
try {
TimeUnit.DAYS.sleep(1);
} catch (InterruptedException e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
}
}
});
localThread.setDaemon(true);
localThread.setName("xxl-job, executor JobLogFileCleanThread");
localThread.start();
}
public void toStop() {
toStop = true;
if (localThread == null) {
return;
}
localThread.interrupt();
try {
localThread.join();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
}
1、com.xxl.job.core.executor.XxlJobExecutor#start内部调用com.xxl.job.core.thread.TriggerCallbackThread#start方法,具体代码实现如下
package com.xxl.job.core.thread;
import *;
public class TriggerCallbackThread {
private static Logger logger = LoggerFactory.getLogger(TriggerCallbackThread.class);
private static TriggerCallbackThread instance = new TriggerCallbackThread();
public static TriggerCallbackThread getInstance(){
return instance;
}
private LinkedBlockingQueue<HandleCallbackParam> callBackQueue = new LinkedBlockingQueue<HandleCallbackParam>();
public static void pushCallBack(HandleCallbackParam callback){
getInstance().callBackQueue.add(callback);
}
private Thread triggerCallbackThread;
private Thread triggerRetryCallbackThread;
private volatile boolean toStop = false;
public void start() {
// 执行器回调 配置错误,直接返回(xxl.job.admin.addresses)
if (XxlJobExecutor.getAdminBizList() == null) {
return;
}
//1、任务回调 监控线程
triggerCallbackThread = new Thread(new Runnable() {
@Override
public void run() {
// 1、正常回调
while(!toStop){
try {
// 取阻塞队列com.xxl.job.core.thread.TriggerCallbackThread.callBackQueue中取值
// put()方法向队列中生产数据,当队列满时,线程阻塞; take()方法从队列中消费数据,当队列为空是,线程阻塞;
HandleCallbackParam callback = getInstance().callBackQueue.take();
if (callback != null) {
// 将队列中的数据 传递给 集合callbackParamList
List<HandleCallbackParam> callbackParamList = new ArrayList<HandleCallbackParam>();
int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList);
callbackParamList.add(callback);// 把第一个参数 补齐
// 执行回调
if (callbackParamList!=null && callbackParamList.size()>0) {
doCallback(callbackParamList);
}
}
} catch (Exception e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
}
// 2、任务结束,执行最后一次回调
try {
List<HandleCallbackParam> callbackParamList = new ArrayList<HandleCallbackParam>();
int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList);
if (callbackParamList!=null && callbackParamList.size()>0) {
doCallback(callbackParamList);
}
} catch (Exception e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
}
});
triggerCallbackThread.setDaemon(true);
triggerCallbackThread.setName("xxl-job, executor TriggerCallbackThread");
triggerCallbackThread.start();
//2、失败回调重试 监控线程
triggerRetryCallbackThread = new Thread(new Runnable() {
@Override
public void run() {
while(!toStop){
try {
//2、失败回调重试
retryFailCallbackFile();
} catch (Exception e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
//2、睡眠30s
try {
TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
} catch (InterruptedException e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
}
}
});
triggerRetryCallbackThread.setDaemon(true);
triggerRetryCallbackThread.start();
}
public void toStop(){
toStop = true;
if (triggerCallbackThread != null) {
triggerCallbackThread.interrupt();
try {
triggerCallbackThread.join();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
if (triggerRetryCallbackThread != null) {
triggerRetryCallbackThread.interrupt();
try {
triggerRetryCallbackThread.join();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
}
private void doCallback(List<HandleCallbackParam> callbackParamList){
boolean callbackRet = false;
for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {// 调度中心client请求对象集合(xxl.job.admin.addresses)
try {
// 执行回调方法(向调度中心发请求)
ReturnT<String> callbackResult = adminBiz.callback(callbackParamList);
if (callbackResult!=null && ReturnT.SUCCESS_CODE == callbackResult.getCode()) {// 执行成功
callbackLog(callbackParamList, "
- xxl-job job callback finish.");
callbackRet = true;
break;
} else {// 执行失败
callbackLog(callbackParamList, "
- xxl-job job callback fail, callbackResult:" + callbackResult);
}
} catch (Exception e) {// 执行异常
callbackLog(callbackParamList, "
- xxl-job job callback error, errorMsg:" + e.getMessage());
}
}
if (!callbackRet) {// 执行失败
// 追加回调失败文件(序列化回调参数)
appendFailCallbackFile(callbackParamList);
}
}
/**
* 记录任务执行日志
*/
private void callbackLog(List<HandleCallbackParam> callbackParamList, String logContent){
for (HandleCallbackParam callbackParam: callbackParamList) {
// 查找 任务执行日志文件所在 路径名称
String logFileName = XxlJobFileAppender.makeLogFileName(new Date(callbackParam.getLogDateTim()), callbackParam.getLogId());
// 线程间传递XxlJobContext对象(日志文件 路径名称)
XxlJobContext.setXxlJobContext(new XxlJobContext(
-1,
null,
logFileName,
-1,
-1));
// 记录日志
XxlJobHelper.log(logContent);
}
}
/**
* 回调失败文件所在目录
*/
private static String failCallbackFilePath = XxlJobFileAppender.getLogPath().concat(File.separator).concat("callbacklog").concat(File.separator);
/**
* 回调失败文件地址
*/
private static String failCallbackFileName = failCallbackFilePath.concat("xxl-job-callback-{x}").concat(".log");
/**
* 追加回调失败文件
*/
private void appendFailCallbackFile(List<HandleCallbackParam> callbackParamList){
if (callbackParamList==null || callbackParamList.size()==0) {
return;
}
byte[] callbackParamList_bytes = JdkSerializeTool.serialize(callbackParamList);
// 获取回调日志文件名称
File callbackLogFile = new File(failCallbackFileName.replace("{x}", String.valueOf(System.currentTimeMillis())));
if (callbackLogFile.exists()) {
// 文件存在就往文件名后加-i,直到找到一个不存在的文件(最大为-99)
for (int i = 0; i < 100; i++) {
callbackLogFile = new File(failCallbackFileName.replace("{x}", String.valueOf(System.currentTimeMillis()).concat("-").concat(String.valueOf(i)) ));
if (!callbackLogFile.exists()) {
break;
}
}
}
// 写入磁盘(序列化)
FileUtil.writeFileContent(callbackLogFile, callbackParamList_bytes);
}
private void retryFailCallbackFile(){
//1、目录不存在则结束
File callbackLogPath = new File(failCallbackFilePath);
if (!callbackLogPath.exists()) {
return;
}
//2、是文件则直接删除
if (callbackLogPath.isFile()) {
callbackLogPath.delete();
}
//3、不是目录 或者 子目录为空 或者 子目录数量不为空 则直接结束
if (!(callbackLogPath.isDirectory() && callbackLogPath.list()!=null && callbackLogPath.list().length>0)) {
return;
}
//4、加载失败任务回调 参数,重试
for (File callbaclLogFile: callbackLogPath.listFiles()) {
//反序列化callbackParamList对象
byte[] callbackParamList_bytes = FileUtil.readFileContent(callbaclLogFile);
//内容为空,则删除
if(callbackParamList_bytes == null || callbackParamList_bytes.length < 1){
callbaclLogFile.delete();
continue;
}
List<HandleCallbackParam> callbackParamList = (List<HandleCallbackParam>) JdkSerializeTool.deserialize(callbackParamList_bytes, List.class);
//删除此文件
callbaclLogFile.delete();
//主动执行回调方法
doCallback(callbackParamList);
}
}
}
1、com.xxl.job.core.executor.XxlJobExecutor#start方法,内部调用方法com.xxl.job.core.executor.XxlJobExecutor#initEmbedServer,具体代码见step 4
2、com.xxl.job.core.executor.XxlJobExecutor#initEmbedServer内部调用方法com.xxl.job.core.server.EmbedServer#start,具体代码如下
package com.xxl.job.core.server;
import *;
public class EmbedServer {
private static final Logger logger = LoggerFactory.getLogger(EmbedServer.class);
private ExecutorBiz executorBiz;
private Thread thread;
public void start(final String address, final int port, final String appname, final String accessToken) {
//1、初始化com.xxl.job.core.biz.ExecutorBiz对象
executorBiz = new ExecutorBizImpl();
//2、启动netty服务 监控线程
thread = new Thread(new Runnable() {
@Override
public void run() {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
//初始化业务请求线程池
ThreadPoolExecutor bizThreadPool = new ThreadPoolExecutor(
0,
200,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(2000),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "xxl-job, EmbedServer bizThreadPool-" + r.hashCode());
}
},
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
throw new RuntimeException("xxl-job, EmbedServer bizThreadPool is EXHAUSTED!");
}
});
try {
//启动服务
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
/**
* 心跳检测处理器
*
* 对Netty的IdleStateHandler的认识
* 首先,此Handler位于:io.netty.handler.timeout包下。
* 说明:
* 当一个channel在一定时间内没有发生:读、写操作时或读写都未发生时,就会触发⼀个IdleStateEvent事件。事件的状态共有3种,对应的枚举类是:IdleState。
*
* 属性和说明:
* readerIdleTime:如果在指定的时间周期内,没有读操作发⽣,就会触发⼀个状态为IdleState.READER_IDLE的IdleStateEvent事件。如果要禁⽤它的话,就设置readerIdleTime=0。
* writerIdleTime:如果在指定的时间周期内,没有写操作发⽣的话,就会触发⼀个状态为IdleState.WRITER_IDLE的IdleStateEvent事件。如果要禁⽤它的话,设置其writerIdleTime=0即可。
* allIdleTime:如果在指定的时间周期内,当既没有读也没有写操作发⽣的话,就触发⼀个状态为IdleState.ALL_IDLE事件。如果要禁⽤它的话,就设置allIdleTime=0。
*/
.addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS)) // beat 3N, close if idle
/**
* http编解码器处理器
*/
.addLast(new HttpServerCodec())
/**
* 合并request、reponse请求,保证消息的完整性(最大长度5 * 1024 * 1024 防止拆包、粘包、半包等导致数据发送、接收不完整)
*/
.addLast(new HttpObjectAggregator(5 * 1024 * 1024))
/**
* 自定义处理器
*/
.addLast(new EmbedHttpServerHandler(executorBiz, accessToken, bizThreadPool));
}
})
.childOption(ChannelOption.SO_KEEPALIVE, true);
//绑定端口
ChannelFuture future = bootstrap.bind(port).sync();
//开始注册
startRegistry(appname, address);
//等待直到关闭服务
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
logger.info("xxl-job remoting server stop.");
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
//关闭时间循环组
try {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
});
thread.setDaemon(true);
thread.start();
}
public void stop() throws Exception {
if (thread != null && thread.isAlive()) {
thread.interrupt();
}
stopRegistry();
}
public static class EmbedHttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private static final Logger logger = LoggerFactory.getLogger(EmbedHttpServerHandler.class);
private ExecutorBiz executorBiz;
private String accessToken;
private ThreadPoolExecutor bizThreadPool;
public EmbedHttpServerHandler(ExecutorBiz executorBiz, String accessToken, ThreadPoolExecutor bizThreadPool) {
this.executorBiz = executorBiz;
this.accessToken = accessToken;
this.bizThreadPool = bizThreadPool;
}
/**
* 接收请求时
*/
@Override
protected void channelRead0(final ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
// request parse
//final byte[] requestBytes = ByteBufUtil.getBytes(msg.content()); // byteBuf.toString(io.netty.util.CharsetUtil.UTF_8);
String requestData = msg.content().toString(CharsetUtil.UTF_8);
String uri = msg.uri();
HttpMethod httpMethod = msg.method();
boolean keepAlive = HttpUtil.isKeepAlive(msg);
String accessTokenReq = msg.headers().get(XxlJobRemotingUtil.XXL_JOB_ACCESS_TOKEN);
// 异步处理请求
bizThreadPool.execute(new Runnable() {
@Override
public void run() {
//1、处理请求
Object responseObj = process(httpMethod, uri, requestData, accessTokenReq);
//2、处理结果转json
String responseJson = GsonTool.toJson(responseObj);
//3、写出到响应体response
writeResponse(ctx, keepAlive, responseJson);
}
});
}
private Object process(HttpMethod httpMethod, String uri, String requestData, String accessTokenReq) {
//参数校验:POST请求 && uri不为空 && (accessToken为空 || accessToken一致)
if (HttpMethod.POST != httpMethod) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, HttpMethod not support.");
}
if (uri == null || uri.trim().length() == 0) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping empty.");
}
if (accessToken != null
&& accessToken.trim().length() > 0
&& !accessToken.equals(accessTokenReq)) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "The access token is wrong.");
}
//api服务映射
try {
switch (uri) {
case "/beat"://心跳检测
return executorBiz.beat();
case "/idleBeat"://闲置检测
IdleBeatParam idleBeatParam = GsonTool.fromJson(requestData, IdleBeatParam.class);
return executorBiz.idleBeat(idleBeatParam);
case "/run"://执行任务
TriggerParam triggerParam = GsonTool.fromJson(requestData, TriggerParam.class);
return executorBiz.run(triggerParam);
case "/kill"://关闭
KillParam killParam = GsonTool.fromJson(requestData, KillParam.class);
return executorBiz.kill(killParam);
case "/log"://记录日志
LogParam logParam = GsonTool.fromJson(requestData, LogParam.class);
return executorBiz.log(logParam);
default:
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping(" + uri + ") not found.");
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
return new ReturnT<String>(ReturnT.FAIL_CODE, "request error:" + ThrowableUtil.toString(e));
}
}
/**
* 写出到请求体
*/
private void writeResponse(ChannelHandlerContext ctx, boolean keepAlive, String responseJson) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(responseJson, CharsetUtil.UTF_8));
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
if (keepAlive) {
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
}
ctx.writeAndFlush(response);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
logger.error(e.getMessage(), e);
ctx.close();
}
/**
* 3轮心跳周期(90s)未发生读写则关闭通道,否则放行
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
ctx.channel().close();// beat 3N, close if idle
} else {
super.userEventTriggered(ctx, evt);
}
}
}
/**
* 向调度中心,注册本执行节点的appName、address
*/
public void startRegistry(final String appname, final String address) {
//1、启动 执行节点注册 监控线程,赋值给com.xxl.job.core.thread.ExecutorRegistryThread.registryThread
// 每隔30s执行一次,主要负责向调度中心,注册本执行节点的appName、address
ExecutorRegistryThread.getInstance().start(appname, address);
}
public void stopRegistry() {
ExecutorRegistryThread.getInstance().toStop();
}
}
1、com.xxl.job.core.server.EmbedServer#start内部调用方法com.xxl.job.core.server.EmbedServer#startRegistry,具体代码实现见step 9
2、com.xxl.job.core.server.EmbedServer#startRegistry内部调用方法com.xxl.job.core.thread.ExecutorRegistryThread#start,具体代码实现如下
package com.xxl.job.core.thread;
import *;
public class ExecutorRegistryThread {
private static Logger logger = LoggerFactory.getLogger(ExecutorRegistryThread.class);
private static ExecutorRegistryThread instance = new ExecutorRegistryThread();
public static ExecutorRegistryThread getInstance(){
return instance;
}
private Thread registryThread;
private volatile boolean toStop = false;
public void start(final String appname, final String address){
// 参数校验
if (appname==null || appname.trim().length()==0) {
return;
}
if (XxlJobExecutor.getAdminBizList() == null) {
return;
}
registryThread = new Thread(new Runnable() {
@Override
public void run() {
//1、每隔30s执行一次注册
while (!toStop) {
try {
RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), appname, address);
for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
try {
//2、调用注册方法
ReturnT<String> registryResult = adminBiz.registry(registryParam);
if (registryResult!=null && ReturnT.SUCCESS_CODE == registryResult.getCode()) {//注册成功
registryResult = ReturnT.SUCCESS;
break;
} else {//注册失败
logger.info("xxl-job registry fail"});
}
} catch (Exception e) {//注册异常
logger.info("xxl-job registry error", e);
}
}
} catch (Exception e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
try {
if (!toStop) {
TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);// 睡眠30s
}
} catch (InterruptedException e) {
if (!toStop) {
logger.warn("{}", e.getMessage());
}
}
}
//2、移除注册信息
try {
RegistryParam registryParam = new RegistryParam(RegistryConfig.RegistType.EXECUTOR.name(), appname, address);
for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
try {
ReturnT<String> registryResult = adminBiz.registryRemove(registryParam);
if (registryResult!=null && ReturnT.SUCCESS_CODE == registryResult.getCode()) {
registryResult = ReturnT.SUCCESS;
logger.info("xxl-job registry-remove success");
break;
} else {
logger.info("xxl-job registry-remove fail");
}
} catch (Exception e) {
if (!toStop) {
logger.info("xxl-job registry-remove error", e);
}
}
}
} catch (Exception e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
}
});
registryThread.setDaemon(true);
registryThread.setName("xxl-job, executor ExecutorRegistryThread");
registryThread.start();
}
public void toStop() {
toStop = true;
if (registryThread != null) {
registryThread.interrupt();
try {
registryThread.join();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
}
}
}
1、执行节点启动时调用方法com.xxl.job.core.thread.ExecutorRegistryThread#start,具体代码参见执行器启动流程的step 10;方法内部 启动 节点注册 监控线程:每30s执行一次,主要负责向调度中心注册本执行节点的appName、address信息
2、com.xxl.job.core.thread.ExecutorRegistryThread#start内部调用com.xxl.job.core.biz.AdminBiz#registry方法,向调度中心发起节点注册rpc请求,具体代码实现如下
package com.xxl.job.core.biz.client;
import *;
public class AdminBizClient implements AdminBiz {
public AdminBizClient() {
}
public AdminBizClient(String addressUrl, String accessToken) {
this.addressUrl = addressUrl;// xxl.job.admin.addresses
this.accessToken = accessToken;// xxl.job.accessToken
if (!this.addressUrl.endsWith("/")) {
this.addressUrl = this.addressUrl + "/";
}
}
private String addressUrl ;
private String accessToken;
private int timeout = 3;
@Override
public ReturnT<String> callback(List<HandleCallbackParam> callbackParamList) {
return XxlJobRemotingUtil.postBody(addressUrl+"api/callback", accessToken, timeout, callbackParamList, String.class);
}
@Override
public ReturnT<String> registry(RegistryParam registryParam) {
return XxlJobRemotingUtil.postBody(addressUrl + "api/registry", accessToken, timeout, registryParam, String.class);
}
@Override
public ReturnT<String> registryRemove(RegistryParam registryParam) {
return XxlJobRemotingUtil.postBody(addressUrl + "api/registryRemove", accessToken, timeout, registryParam, String.class);
}
}
3、对应调度中心请求接收端代码实现如下
package com.xxl.job.admin.controller;
import *;
@Controller
@RequestMapping("/api")
public class JobApiController {
@Resource
private AdminBiz adminBiz;
@RequestMapping("/{uri}")
@ResponseBody
@PermissionLimit(limit=false)
public ReturnT<String> api(HttpServletRequest request, @PathVariable("uri") String uri, @RequestBody(required = false) String data) {
// 参数校验
if (!"POST".equalsIgnoreCase(request.getMethod())) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, HttpMethod not support.");
}
if (uri==null || uri.trim().length()==0) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping empty.");
}
// accessToken为空 或者 相等,否则不放行
if (XxlJobAdminConfig.getAdminConfig().getAccessToken()!=null
&& XxlJobAdminConfig.getAdminConfig().getAccessToken().trim().length()>0
&& !XxlJobAdminConfig.getAdminConfig().getAccessToken().equals(request.getHeader(XxlJobRemotingUtil.XXL_JOB_ACCESS_TOKEN))) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "The access token is wrong.");
}
if ("callback".equals(uri)) {// 任务回调
List<HandleCallbackParam> callbackParamList = GsonTool.fromJson(data, List.class, HandleCallbackParam.class);
return adminBiz.callback(callbackParamList);
} else if ("registry".equals(uri)) {// 执行节点注册
RegistryParam registryParam = GsonTool.fromJson(data, RegistryParam.class);
return adminBiz.registry(registryParam);
} else if ("registryRemove".equals(uri)) {// 移除执行节点信息
RegistryParam registryParam = GsonTool.fromJson(data, RegistryParam.class);
return adminBiz.registryRemove(registryParam);
} else {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping("+ uri +") not found.");
}
}
}
4、com.xxl.job.core.biz.AdminBiz#registry对应实现代码如下
package com.xxl.job.admin.service.impl;
import *;
@Service
public class AdminBizImpl implements AdminBiz {
@Override
public ReturnT<String> callback(List<HandleCallbackParam> callbackParamList) {
return JobCompleteHelper.getInstance().callback(callbackParamList);
}
@Override
public ReturnT<String> registry(RegistryParam registryParam) {
return JobRegistryHelper.getInstance().registry(registryParam);
}
@Override
public ReturnT<String> registryRemove(RegistryParam registryParam) {
return JobRegistryHelper.getInstance().registryRemove(registryParam);
}
}
5、由com.xxl.job.admin.core.thread.JobRegistryHelper#registry方法,完成节点信息的新增和更新操作,具体代码参见调度中心启动流程的step 5
6、同时任务节点注册后台监控线程com.xxl.job.admin.core.thread.JobRegistryHelper#registryMonitorThread每30s执行一次,实现 失效执行节点的移除操作 并 刷新所有在线执行节点绑定信息,具体代码参见调度中心启动流程的step 5
1、操作流程:访问调度中心页面http://localhost:8080/xxl-job-admin -> 执行器管理 -> 新增 -> 保存
2、接口地址:http://127.0.0.1:8080/xxl-job-admin/jobgroup/save
3、接口描述:执行器 新增接口
4、代码说明:对应controller代码实现如下
package com.xxl.job.admin.controller;
import *;
@Controller
@RequestMapping("/jobgroup")
public class JobGroupController {
@Resource
public XxlJobInfoDao xxlJobInfoDao;
@Resource
public XxlJobGroupDao xxlJobGroupDao;
@Resource
private XxlJobRegistryDao xxlJobRegistryDao;
@RequestMapping
@PermissionLimit(adminuser = true)
public String index(Model model) {
return "jobgroup/jobgroup.index";
}
@RequestMapping("/pageList")
@ResponseBody
@PermissionLimit(adminuser = true)
public Map<String, Object> pageList(HttpServletRequest request,
@RequestParam(required = false, defaultValue = "0") int start,
@RequestParam(required = false, defaultValue = "10") int length,
String appname, String title) {
List<XxlJobGroup> list = xxlJobGroupDao.pageList(start, length, appname, title);
int list_count = xxlJobGroupDao.pageListCount(start, length, appname, title);
Map<String, Object> maps = new HashMap<String, Object>();
maps.put("recordsTotal", list_count);
maps.put("recordsFiltered", list_count);
maps.put("data", list);
return maps;
}
/**
* 执行器新增接口
*/
@RequestMapping("/save")
@ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<String> save(XxlJobGroup xxlJobGroup){
//1、参数校验
if (xxlJobGroup.getAppname()==null || xxlJobGroup.getAppname().trim().length()==0) {
return new ReturnT<String>(500, (I18nUtil.getString("system_please_input")+"AppName") );
}
if (xxlJobGroup.getAppname().length()<4 || xxlJobGroup.getAppname().length()>64) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_appname_length") );
}
if (xxlJobGroup.getAppname().contains(">") || xxlJobGroup.getAppname().contains("<")) {
return new ReturnT<String>(500, "AppName"+I18nUtil.getString("system_unvalid") );
}
if (xxlJobGroup.getTitle()==null || xxlJobGroup.getTitle().trim().length()==0) {
return new ReturnT<String>(500, (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobgroup_field_title")) );
}
if (xxlJobGroup.getTitle().contains(">") || xxlJobGroup.getTitle().contains("<")) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_title")+I18nUtil.getString("system_unvalid") );
}
if (xxlJobGroup.getAddressType()!=0) {
if (xxlJobGroup.getAddressList()==null || xxlJobGroup.getAddressList().trim().length()==0) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_addressType_limit") );
}
if (xxlJobGroup.getAddressList().contains(">") || xxlJobGroup.getAddressList().contains("<")) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_registryList")+I18nUtil.getString("system_unvalid") );
}
String[] addresss = xxlJobGroup.getAddressList().split(",");
for (String item: addresss) {
if (item==null || item.trim().length()==0) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_registryList_unvalid") );
}
}
}
//2、设置更新时间
xxlJobGroup.setUpdateTime(new Date());
//3、存储xxl_job_group记录
int ret = xxlJobGroupDao.save(xxlJobGroup);
return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL;
}
@RequestMapping("/update")
@ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<String> update(XxlJobGroup xxlJobGroup){
// valid
if (xxlJobGroup.getAppname()==null || xxlJobGroup.getAppname().trim().length()==0) {
return new ReturnT<String>(500, (I18nUtil.getString("system_please_input")+"AppName") );
}
if (xxlJobGroup.getAppname().length()<4 || xxlJobGroup.getAppname().length()>64) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_appname_length") );
}
if (xxlJobGroup.getTitle()==null || xxlJobGroup.getTitle().trim().length()==0) {
return new ReturnT<String>(500, (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobgroup_field_title")) );
}
if (xxlJobGroup.getAddressType() == 0) {
// 0=自动注册
List<String> registryList = findRegistryByAppName(xxlJobGroup.getAppname());
String addressListStr = null;
if (registryList!=null && !registryList.isEmpty()) {
Collections.sort(registryList);
addressListStr = "";
for (String item:registryList) {
addressListStr += item + ",";
}
addressListStr = addressListStr.substring(0, addressListStr.length()-1);
}
xxlJobGroup.setAddressList(addressListStr);
} else {
// 1=手动录入
if (xxlJobGroup.getAddressList()==null || xxlJobGroup.getAddressList().trim().length()==0) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_addressType_limit") );
}
String[] addresss = xxlJobGroup.getAddressList().split(",");
for (String item: addresss) {
if (item==null || item.trim().length()==0) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_registryList_unvalid") );
}
}
}
xxlJobGroup.setUpdateTime(new Date());
int ret = xxlJobGroupDao.update(xxlJobGroup);
return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL;
}
private List<String> findRegistryByAppName(String appnameParam){
HashMap<String, List<String>> appAddressMap = new HashMap<String, List<String>>();
List<XxlJobRegistry> list = xxlJobRegistryDao.findAll(RegistryConfig.DEAD_TIMEOUT, new Date());
if (list != null) {
for (XxlJobRegistry item: list) {
if (RegistryConfig.RegistType.EXECUTOR.name().equals(item.getRegistryGroup())) {
String appname = item.getRegistryKey();
List<String> registryList = appAddressMap.get(appname);
if (registryList == null) {
registryList = new ArrayList<String>();
}
if (!registryList.contains(item.getRegistryValue())) {
registryList.add(item.getRegistryValue());
}
appAddressMap.put(appname, registryList);
}
}
}
return appAddressMap.get(appnameParam);
}
@RequestMapping("/remove")
@ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<String> remove(int id){
// valid
int count = xxlJobInfoDao.pageListCount(0, 10, id, -1, null, null, null);
if (count > 0) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_del_limit_0") );
}
List<XxlJobGroup> allList = xxlJobGroupDao.findAll();
if (allList.size() == 1) {
return new ReturnT<String>(500, I18nUtil.getString("jobgroup_del_limit_1") );
}
int ret = xxlJobGroupDao.remove(id);
return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL;
}
@RequestMapping("/loadById")
@ResponseBody
@PermissionLimit(adminuser = true)
public ReturnT<XxlJobGroup> loadById(int id){
XxlJobGroup jobGroup = xxlJobGroupDao.load(id);
return jobGroup!=null?new ReturnT<XxlJobGroup>(jobGroup):new ReturnT<XxlJobGroup>(ReturnT.FAIL_CODE, null);
}
}
5、调度中心启动的时候,启动任务执行节点注册线程com.xxl.job.admin.core.thread.JobRegistryHelper#registryMonitorThread,完成所有执行器对应执行节点信息更新,具体代码参见调度中心启动流程的step 5
1、操作流程:访问调度中心页面http://localhost:8080/xxl-job-admin -> 任务管理 -> 新增 -> 保存
2、接口地址:http://127.0.0.1:8080/xxl-job-admin/jobinfo/add
3、接口描述:任务配置新增接口
4、代码说明:对应controller代码实现如下
package com.xxl.job.admin.controller;
import *;
@Controller
@RequestMapping("/jobinfo")
public class JobInfoController {
private static Logger logger = LoggerFactory.getLogger(JobInfoController.class);
@Resource
private XxlJobGroupDao xxlJobGroupDao;
@Resource
private XxlJobService xxlJobService;
@RequestMapping
public String index(HttpServletRequest request, Model model, @RequestParam(required = false, defaultValue = "-1") int jobGroup) {
// 枚举-字典
model.addAttribute("ExecutorRouteStrategyEnum", ExecutorRouteStrategyEnum.values());// 路由策略-列表
model.addAttribute("GlueTypeEnum", GlueTypeEnum.values());// Glue类型-字典
model.addAttribute("ExecutorBlockStrategyEnum", ExecutorBlockStrategyEnum.values());// 阻塞处理策略-字典
model.addAttribute("ScheduleTypeEnum", ScheduleTypeEnum.values());// 调度类型
model.addAttribute("MisfireStrategyEnum", MisfireStrategyEnum.values());// 调度过期策略
// 执行器列表
List<XxlJobGroup> jobGroupList_all = xxlJobGroupDao.findAll();
List<XxlJobGroup> jobGroupList = filterJobGroupByRole(request, jobGroupList_all);
if (jobGroupList==null || jobGroupList.size()==0) {
throw new XxlJobException(I18nUtil.getString("jobgroup_empty"));
}
model.addAttribute("JobGroupList", jobGroupList);
model.addAttribute("jobGroup", jobGroup);
return "jobinfo/jobinfo.index";
}
public static List<XxlJobGroup> filterJobGroupByRole(HttpServletRequest request, List<XxlJobGroup> jobGroupList_all){
List<XxlJobGroup> jobGroupList = new ArrayList<>();
if (jobGroupList_all!=null && jobGroupList_all.size()>0) {
XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY);
if (loginUser.getRole() == 1) {
jobGroupList = jobGroupList_all;
} else {
List<String> groupIdStrs = new ArrayList<>();
if (loginUser.getPermission()!=null && loginUser.getPermission().trim().length()>0) {
groupIdStrs = Arrays.asList(loginUser.getPermission().trim().split(","));
}
for (XxlJobGroup groupItem:jobGroupList_all) {
if (groupIdStrs.contains(String.valueOf(groupItem.getId()))) {
jobGroupList.add(groupItem);
}
}
}
}
return jobGroupList;
}
public static void validPermission(HttpServletRequest request, int jobGroup) {
XxlJobUser loginUser = (XxlJobUser) request.getAttribute(LoginService.LOGIN_IDENTITY_KEY);
if (!loginUser.validPermission(jobGroup)) {
throw new RuntimeException(I18nUtil.getString("system_permission_limit") + "[username="+ loginUser.getUsername() +"]");
}
}
@RequestMapping("/pageList")
@ResponseBody
public Map<String, Object> pageList(@RequestParam(required = false, defaultValue = "0") int start,
@RequestParam(required = false, defaultValue = "10") int length,
int jobGroup, int triggerStatus, String jobDesc, String executorHandler, String author) {
return xxlJobService.pageList(start, length, jobGroup, triggerStatus, jobDesc, executorHandler, author);
}
@RequestMapping("/add")
@ResponseBody
public ReturnT<String> add(XxlJobInfo jobInfo) {
return xxlJobService.add(jobInfo);
}
@RequestMapping("/update")
@ResponseBody
public ReturnT<String> update(XxlJobInfo jobInfo) {
return xxlJobService.update(jobInfo);
}
@RequestMapping("/remove")
@ResponseBody
public ReturnT<String> remove(int id) {
return xxlJobService.remove(id);
}
@RequestMapping("/stop")
@ResponseBody
public ReturnT<String> pause(int id) {
return xxlJobService.stop(id);
}
@RequestMapping("/start")
@ResponseBody
public ReturnT<String> start(int id) {
return xxlJobService.start(id);
}
@RequestMapping("/trigger")
@ResponseBody
//@PermissionLimit(limit = false)
public ReturnT<String> triggerJob(int id, String executorParam, String addressList) {
if (executorParam == null) {
executorParam = "";
}
//任务触发类型:手动触发(TriggerTypeEnum.MANUAL)
JobTriggerPoolHelper.trigger(id, TriggerTypeEnum.MANUAL, -1, null, executorParam, addressList);
return ReturnT.SUCCESS;
}
@RequestMapping("/nextTriggerTime")
@ResponseBody
public ReturnT<List<String>> nextTriggerTime(String scheduleType, String scheduleConf) {
XxlJobInfo paramXxlJobInfo = new XxlJobInfo();
paramXxlJobInfo.setScheduleType(scheduleType);
paramXxlJobInfo.setScheduleConf(scheduleConf);
List<String> result = new ArrayList<>();
try {
Date lastTime = new Date();
for (int i = 0; i < 5; i++) {
lastTime = JobScheduleHelper.generateNextValidTime(paramXxlJobInfo, lastTime);
if (lastTime != null) {
result.add(DateUtil.formatDateTime(lastTime));
} else {
break;
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
return new ReturnT<List<String>>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) + e.getMessage());
}
return new ReturnT<List<String>>(result);
}
}
5、com.xxl.job.admin.service.XxlJobService#add方法,具体代码实现如下
package com.xxl.job.admin.service.impl;
import *;
@Service
public class XxlJobServiceImpl implements XxlJobService {
private static Logger logger = LoggerFactory.getLogger(XxlJobServiceImpl.class);
@Resource
private XxlJobGroupDao xxlJobGroupDao;
@Resource
private XxlJobInfoDao xxlJobInfoDao;
@Resource
public XxlJobLogDao xxlJobLogDao;
@Resource
private XxlJobLogGlueDao xxlJobLogGlueDao;
@Resource
private XxlJobLogReportDao xxlJobLogReportDao;
@Override
public Map<String, Object> pageList(int start, int length, int jobGroup, int triggerStatus, String jobDesc, String executorHandler, String author) {
List<XxlJobInfo> list = xxlJobInfoDao.pageList(start, length, jobGroup, triggerStatus, jobDesc, executorHandler, author);
int list_count = xxlJobInfoDao.pageListCount(start, length, jobGroup, triggerStatus, jobDesc, executorHandler, author);
Map<String, Object> maps = new HashMap<String, Object>();
maps.put("recordsTotal", list_count);
maps.put("recordsFiltered", list_count);
maps.put("data", list);
return maps;
}
@Override
public ReturnT<String> add(XxlJobInfo jobInfo) {
//1、基础 参数 校验
XxlJobGroup group = xxlJobGroupDao.load(jobInfo.getJobGroup());
if (group == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_choose")+I18nUtil.getString("jobinfo_field_jobgroup")) );
}
if (jobInfo.getJobDesc()==null || jobInfo.getJobDesc().trim().length()==0) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_jobdesc")) );
}
if (jobInfo.getAuthor()==null || jobInfo.getAuthor().trim().length()==0) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_author")) );
}
//2、任务调度 类型 校验
ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(jobInfo.getScheduleType(), null);
if (scheduleTypeEnum == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
}
if (scheduleTypeEnum == ScheduleTypeEnum.CRON) {
if (jobInfo.getScheduleConf()==null || !CronExpression.isValidExpression(jobInfo.getScheduleConf())) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "Cron"+I18nUtil.getString("system_unvalid"));
}
} else if (scheduleTypeEnum == ScheduleTypeEnum.FIX_RATE/* || scheduleTypeEnum == ScheduleTypeEnum.FIX_DELAY*/) {
if (jobInfo.getScheduleConf() == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")) );
}
try {
int fixSecond = Integer.valueOf(jobInfo.getScheduleConf());
if (fixSecond < 1) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
}
} catch (Exception e) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
}
}
//3、运行模式 类型 校验
if (GlueTypeEnum.match(jobInfo.getGlueType()) == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_gluetype")+I18nUtil.getString("system_unvalid")) );
}
if (GlueTypeEnum.BEAN==GlueTypeEnum.match(jobInfo.getGlueType()) && (jobInfo.getExecutorHandler()==null || jobInfo.getExecutorHandler().trim().length()==0) ) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+"JobHandler") );
}
// 》fix "\r" in shell
if (GlueTypeEnum.GLUE_SHELL==GlueTypeEnum.match(jobInfo.getGlueType()) && jobInfo.getGlueSource()!=null) {
jobInfo.setGlueSource(jobInfo.getGlueSource().replaceAll("\r", ""));
}
//4、路由策略 类型 校验
if (ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorRouteStrategy")+I18nUtil.getString("system_unvalid")) );
}
if (MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), null) == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("misfire_strategy")+I18nUtil.getString("system_unvalid")) );
}
if (ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), null) == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorBlockStrategy")+I18nUtil.getString("system_unvalid")) );
}
//5、子任务校验
if (jobInfo.getChildJobId()!=null && jobInfo.getChildJobId().trim().length()>0) {
String[] childJobIds = jobInfo.getChildJobId().split(",");
for (String childJobIdItem: childJobIds) {
if (childJobIdItem!=null && childJobIdItem.trim().length()>0 && isNumeric(childJobIdItem)) {
//6、子任务必须存在
XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.parseInt(childJobIdItem));
if (childJobInfo==null) {
return new ReturnT<String>(ReturnT.FAIL_CODE,
MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_not_found")), childJobIdItem));
}
} else {//子任务格式校验不正确
return new ReturnT<String>(ReturnT.FAIL_CODE,
MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_unvalid")), childJobIdItem));
}
}
// 拼接子任务id
String temp = "";
for (String item:childJobIds) {
temp += item + ",";
}
temp = temp.substring(0, temp.length()-1);
jobInfo.setChildJobId(temp);
}
//6、新增xxl_job_info表记录
jobInfo.setAddTime(new Date());
jobInfo.setUpdateTime(new Date());
jobInfo.setGlueUpdatetime(new Date());
xxlJobInfoDao.save(jobInfo);
if (jobInfo.getId() < 1) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_add")+I18nUtil.getString("system_fail")) );
}
//7、返回记录id
return new ReturnT<String>(String.valueOf(jobInfo.getId()));
}
private boolean isNumeric(String str){
try {
int result = Integer.valueOf(str);
return true;
} catch (NumberFormatException e) {
return false;
}
}
@Override
public ReturnT<String> update(XxlJobInfo jobInfo) {
if (jobInfo.getJobDesc()==null || jobInfo.getJobDesc().trim().length()==0) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_jobdesc")) );
}
if (jobInfo.getAuthor()==null || jobInfo.getAuthor().trim().length()==0) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_author")) );
}
ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(jobInfo.getScheduleType(), null);
if (scheduleTypeEnum == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
}
if (scheduleTypeEnum == ScheduleTypeEnum.CRON) {
if (jobInfo.getScheduleConf()==null || !CronExpression.isValidExpression(jobInfo.getScheduleConf())) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "Cron"+I18nUtil.getString("system_unvalid") );
}
} else if (scheduleTypeEnum == ScheduleTypeEnum.FIX_RATE) {
if (jobInfo.getScheduleConf() == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
}
try {
int fixSecond = Integer.valueOf(jobInfo.getScheduleConf());
if (fixSecond < 1) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
}
} catch (Exception e) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
}
}
if (ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorRouteStrategy")+I18nUtil.getString("system_unvalid")) );
}
if (MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), null) == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("misfire_strategy")+I18nUtil.getString("system_unvalid")) );
}
if (ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), null) == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorBlockStrategy")+I18nUtil.getString("system_unvalid")) );
}
if (jobInfo.getChildJobId()!=null && jobInfo.getChildJobId().trim().length()>0) {
String[] childJobIds = jobInfo.getChildJobId().split(",");
for (String childJobIdItem: childJobIds) {
if (childJobIdItem!=null && childJobIdItem.trim().length()>0 && isNumeric(childJobIdItem)) {
XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.parseInt(childJobIdItem));
if (childJobInfo==null) {
return new ReturnT<String>(ReturnT.FAIL_CODE,
MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_not_found")), childJobIdItem));
}
} else {
return new ReturnT<String>(ReturnT.FAIL_CODE,
MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_unvalid")), childJobIdItem));
}
}
String temp = "";
for (String item:childJobIds) {
temp += item + ",";
}
temp = temp.substring(0, temp.length()-1);
jobInfo.setChildJobId(temp);
}
// group valid
XxlJobGroup jobGroup = xxlJobGroupDao.load(jobInfo.getJobGroup());
if (jobGroup == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_jobgroup")+I18nUtil.getString("system_unvalid")) );
}
XxlJobInfo exists_jobInfo = xxlJobInfoDao.loadById(jobInfo.getId());
if (exists_jobInfo == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_id")+I18nUtil.getString("system_not_found")) );
}
// next trigger time (5s后生效,避开预读周期)
long nextTriggerTime = exists_jobInfo.getTriggerNextTime();
boolean scheduleDataNotChanged = jobInfo.getScheduleType().equals(exists_jobInfo.getScheduleType()) && jobInfo.getScheduleConf().equals(exists_jobInfo.getScheduleConf());
if (exists_jobInfo.getTriggerStatus() == 1 && !scheduleDataNotChanged) {
try {
Date nextValidTime = JobScheduleHelper.generateNextValidTime(jobInfo, new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS));
if (nextValidTime == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
}
nextTriggerTime = nextValidTime.getTime();
} catch (Exception e) {
logger.error(e.getMessage(), e);
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
}
}
exists_jobInfo.setJobGroup(jobInfo.getJobGroup());
exists_jobInfo.setJobDesc(jobInfo.getJobDesc());
exists_jobInfo.setAuthor(jobInfo.getAuthor());
exists_jobInfo.setAlarmEmail(jobInfo.getAlarmEmail());
exists_jobInfo.setScheduleType(jobInfo.getScheduleType());
exists_jobInfo.setScheduleConf(jobInfo.getScheduleConf());
exists_jobInfo.setMisfireStrategy(jobInfo.getMisfireStrategy());
exists_jobInfo.setExecutorRouteStrategy(jobInfo.getExecutorRouteStrategy());
exists_jobInfo.setExecutorHandler(jobInfo.getExecutorHandler());
exists_jobInfo.setExecutorParam(jobInfo.getExecutorParam());
exists_jobInfo.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy());
exists_jobInfo.setExecutorTimeout(jobInfo.getExecutorTimeout());
exists_jobInfo.setExecutorFailRetryCount(jobInfo.getExecutorFailRetryCount());
exists_jobInfo.setChildJobId(jobInfo.getChildJobId());
exists_jobInfo.setTriggerNextTime(nextTriggerTime);
exists_jobInfo.setUpdateTime(new Date());
xxlJobInfoDao.update(exists_jobInfo);
return ReturnT.SUCCESS;
}
@Override
public ReturnT<String> remove(int id) {
XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
if (xxlJobInfo == null) {
return ReturnT.SUCCESS;
}
xxlJobInfoDao.delete(id);
xxlJobLogDao.delete(id);
xxlJobLogGlueDao.deleteByJobId(id);
return ReturnT.SUCCESS;
}
@Override
public ReturnT<String> start(int id) {
//加载执行器 任务配置 信息
XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
//获取调度类型,默认值为无
ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(xxlJobInfo.getScheduleType(), ScheduleTypeEnum.NONE);
if (ScheduleTypeEnum.NONE == scheduleTypeEnum) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type_none_limit_start")) );
}
// next trigger time (5s后生效,避开预读周期)
long nextTriggerTime = 0;
try {
//生成下次执行时间
Date nextValidTime = JobScheduleHelper.generateNextValidTime(xxlJobInfo, new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS));
if (nextValidTime == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
}
nextTriggerTime = nextValidTime.getTime();
} catch (Exception e) {
logger.error(e.getMessage(), e);
return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) );
}
//更新xxl_job_info表记录
xxlJobInfo.setTriggerStatus(1);//调度状态:0-停止,1-运行
xxlJobInfo.setTriggerLastTime(0);//上次调度时间
xxlJobInfo.setTriggerNextTime(nextTriggerTime);
xxlJobInfo.setUpdateTime(new Date());//下次调度时间
xxlJobInfoDao.update(xxlJobInfo);
return ReturnT.SUCCESS;
}
@Override
public ReturnT<String> stop(int id) {
XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(id);
xxlJobInfo.setTriggerStatus(0);
xxlJobInfo.setTriggerLastTime(0);
xxlJobInfo.setTriggerNextTime(0);
xxlJobInfo.setUpdateTime(new Date());
xxlJobInfoDao.update(xxlJobInfo);
return ReturnT.SUCCESS;
}
@Override
public Map<String, Object> dashboardInfo() {
int jobInfoCount = xxlJobInfoDao.findAllCount();
int jobLogCount = 0;
int jobLogSuccessCount = 0;
XxlJobLogReport xxlJobLogReport = xxlJobLogReportDao.queryLogReportTotal();
if (xxlJobLogReport != null) {
jobLogCount = xxlJobLogReport.getRunningCount() + xxlJobLogReport.getSucCount() + xxlJobLogReport.getFailCount();
jobLogSuccessCount = xxlJobLogReport.getSucCount();
}
Set<String> executorAddressSet = new HashSet<String>();
List<XxlJobGroup> groupList = xxlJobGroupDao.findAll();
if (groupList!=null && !groupList.isEmpty()) {
for (XxlJobGroup group: groupList) {
if (group.getRegistryList()!=null && !group.getRegistryList().isEmpty()) {
executorAddressSet.addAll(group.getRegistryList());
}
}
}
int executorCount = executorAddressSet.size();
Map<String, Object> dashboardMap = new HashMap<String, Object>();
dashboardMap.put("jobInfoCount", jobInfoCount);
dashboardMap.put("jobLogCount", jobLogCount);
dashboardMap.put("jobLogSuccessCount", jobLogSuccessCount);
dashboardMap.put("executorCount", executorCount);
return dashboardMap;
}
@Override
public ReturnT<Map<String, Object>> chartInfo(Date startDate, Date endDate) {
List<String> triggerDayList = new ArrayList<String>();
List<Integer> triggerDayCountRunningList = new ArrayList<Integer>();
List<Integer> triggerDayCountSucList = new ArrayList<Integer>();
List<Integer> triggerDayCountFailList = new ArrayList<Integer>();
int triggerCountRunningTotal = 0;
int triggerCountSucTotal = 0;
int triggerCountFailTotal = 0;
List<XxlJobLogReport> logReportList = xxlJobLogReportDao.queryLogReport(startDate, endDate);
if (logReportList!=null && logReportList.size()>0) {
for (XxlJobLogReport item: logReportList) {
String day = DateUtil.formatDate(item.getTriggerDay());
int triggerDayCountRunning = item.getRunningCount();
int triggerDayCountSuc = item.getSucCount();
int triggerDayCountFail = item.getFailCount();
triggerDayList.add(day);
triggerDayCountRunningList.add(triggerDayCountRunning);
triggerDayCountSucList.add(triggerDayCountSuc);
triggerDayCountFailList.add(triggerDayCountFail);
triggerCountRunningTotal += triggerDayCountRunning;
triggerCountSucTotal += triggerDayCountSuc;
triggerCountFailTotal += triggerDayCountFail;
}
} else {
for (int i = -6; i <= 0; i++) {
triggerDayList.add(DateUtil.formatDate(DateUtil.addDays(new Date(), i)));
triggerDayCountRunningList.add(0);
triggerDayCountSucList.add(0);
triggerDayCountFailList.add(0);
}
}
Map<String, Object> result = new HashMap<String, Object>();
result.put("triggerDayList", triggerDayList);
result.put("triggerDayCountRunningList", triggerDayCountRunningList);
result.put("triggerDayCountSucList", triggerDayCountSucList);
result.put("triggerDayCountFailList", triggerDayCountFailList);
result.put("triggerCountRunningTotal", triggerCountRunningTotal);
result.put("triggerCountSucTotal", triggerCountSucTotal);
result.put("triggerCountFailTotal", triggerCountFailTotal);
return new ReturnT<Map<String, Object>>(result);
}
}
1、操作流程:访问调度中心页面http://localhost:8080/xxl-job-admin -> 任务管理 -> 选择某条任务配置记录 -> 操作 -> 执行一次
2、接口地址:http://127.0.0.1:8080/xxl-job-admin/jobinfo/trigger
3、接口描述:执行一次任务接口
4、代码说明:对应controller具体代码参见新增任务配置流程 step 1
5、controller内部调用com.xxl.job.admin.core.thread.JobTriggerPoolHelper#trigger实现立即触发一次任务执行,具体代码参见 调度中心启动流程 step 4
5、com.xxl.job.admin.core.thread.JobTriggerPoolHelper#trigger内部继续调用方法com.xxl.job.admin.core.thread.JobTriggerPoolHelper#addTrigger,其实现逻辑为:
① 根据当前任务jobId,去分钟内任务调度超时次数统计变量jobTimeoutCountMap中取值,判断使用那个任务调度线程池;默认使用快任务调度线程池fastTriggerPool,仅当值大于10时使用慢任务调度线程池slowTriggerPool
② 线程池异步调用,触发任务执行
③ 记录任务开始执行时间
④ 调用方法com.xxl.job.admin.core.trigger.XxlJobTrigger#trigger,触发任务执行,内部实现流程分析参见执行一次任务流程 step 4
⑤ 计算当前时间分钟刻度minTim_now,并与全局的分钟刻度com.xxl.job.admin.core.thread.JobTriggerPoolHelper#minTim做比对。仅当不一致时,刷新全局分钟刻度的值;并清空分钟内任务调度超时次数统计变量jobTimeoutCountMap的值
⑥ 结合步骤③的值,计算任务执行消耗时长。仅当耗时大于500ms时,分钟内任务调度超时次数统计变量jobTimeoutCountMap对应当前任务jobId的值,自动+1
具体代码参见 调度中心启动流程 step 4
6、方法com.xxl.job.admin.core.trigger.XxlJobTrigger#trigger,其实现逻辑为:
① 根据入参jobId,去数据库加载任务配置信息XxlJobInfo记录,查询结果为空则直接返回
② 入参executorParam不为空,则覆盖XxlJobInfo记录的执行器任务参数executorParam字段
③ 根据入参failRetryCount,获取当前任务失败重试次数finalFailRetryCount;默认值为XxlJobInfo记录的executorFailRetryCount,仅当failRetryCount大于等于0时值为failRetryCount
④ 入参addressList不为空,则覆盖XxlJobInfo记录的执行器地址列表addressList字段,并设置执行器地址类型addressType字段为1(0=自动注册、1=手动录入)
注:即优先外部传入执行器地址,也即优先手动录入执行器地址
⑤ 入参executorShardingParam不为空时,将分片参数按照/符号拆分为index,total两条数据并装入临时变量shardingParam,便于后续方法引用
⑥ 根据XxlJobInfo记录的执行器路由策略类型executorRouteStrategy字段,分情况调用方法com.xxl.job.admin.core.trigger.XxlJobTrigger#processTrigger实现指定节点执行器的任务调度:
1)当路由策略 为 分片广播 且 XxlJobInfo记录的执行器地址列表registryList字段不为空时,循环registryList进行com.xxl.job.admin.core.trigger.XxlJobTrigger#processTrigger调用,内部实现流程分析参见 执行一次任务流程 step 5
2)当路由策略 为 其他情况时,此时若shardingParam为空则赋初值{0, 1};然后继续调用com.xxl.job.admin.core.trigger.XxlJobTrigger#processTrigger方法,内部实现流程分析参见 执行一次任务流程 step 5
具体代码参见 调度中心启动流程 step 4
7、方法com.xxl.job.admin.core.trigger.XxlJobTrigger#processTrigger,其实现逻辑为:
① 根据入参XxlJobInfo记录阻塞处理策略executorBlockStrategy字段,获取对应的阻塞处理策略枚举,枚举默认值为单机串行
②
③
④
⑤
⑥
⑦
⑧
⑨
⑩
具体代码参见 调度中心启动流程 step 4
8、com.xxl.job.core.server.EmbedServer#thread内部调用com.xxl.job.core.biz.impl.ExecutorBizImpl#run方法,具体代码实现如下
package com.xxl.job.core.biz.impl;
import *;
public class ExecutorBizImpl implements ExecutorBiz {
private static Logger logger = LoggerFactory.getLogger(ExecutorBizImpl.class);
@Override
public ReturnT<String> beat() {
return ReturnT.SUCCESS;
}
@Override
public ReturnT<String> idleBeat(IdleBeatParam idleBeatParam) {
boolean isRunningOrHasQueue = false;
JobThread jobThread = XxlJobExecutor.loadJobThread(idleBeatParam.getJobId());
if (jobThread != null && jobThread.isRunningOrHasQueue()) {
isRunningOrHasQueue = true;
}
if (isRunningOrHasQueue) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "job thread is running or has trigger queue.");
}
return ReturnT.SUCCESS;
}
@Override
public ReturnT<String> run(TriggerParam triggerParam) {
//1、从com.xxl.job.core.executor.XxlJobExecutor.jobThreadRepository缓存中获取 任务执行线程,首次为空
JobThread jobThread = XxlJobExecutor.loadJobThread(triggerParam.getJobId());
//2、获取IJobHandler信息(Ctrl+H),首次为空
IJobHandler jobHandler = jobThread!=null?jobThread.getHandler():null;
String removeOldReason = null;
//3、获取 运行模式 类型
GlueTypeEnum glueTypeEnum = GlueTypeEnum.match(triggerParam.getGlueType());
if (GlueTypeEnum.BEAN == glueTypeEnum) {//BEAN模式
//获取@XxlJob注解的配置信息对象,IJobHandler
IJobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerParam.getExecutorHandler());
//参数校验:缓存信息存在情况下 缓存信息 和 节点注册 的 不一致(??)
if (jobThread!=null && jobHandler != newJobHandler) {
removeOldReason = "change jobhandler or glue type, and terminate the old job thread.";
jobThread = null;
jobHandler = null;
}
//jobHandler首次为空,则将加载到的newJobHandler赋值给jobHandler
if (jobHandler == null) {
jobHandler = newJobHandler;
//如果本执行节点未找到newJobHandler信息,则直接返回执行失败
if (jobHandler == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "job handler [" + triggerParam.getExecutorHandler() + "] not found.");
}
}
} else if (GlueTypeEnum.GLUE_GROOVY == glueTypeEnum) {//GLUE模式(Java)
//参数校验:缓存信息存在情况下 缓存信息所属类型 不为GlueJobHandler 并且 更新时间与传入的参数 一致
if (jobThread != null &&
!(jobThread.getHandler() instanceof GlueJobHandler
&& ((GlueJobHandler) jobThread.getHandler()).getGlueUpdatetime()==triggerParam.getGlueUpdatetime() )) {
removeOldReason = "change job source or glue type, and terminate the old job thread.";
jobThread = null;
jobHandler = null;
}
if (jobHandler == null) {
try {
IJobHandler originJobHandler = GlueFactory.getInstance().loadNewInstance(triggerParam.getGlueSource());
jobHandler = new GlueJobHandler(originJobHandler, triggerParam.getGlueUpdatetime());
} catch (Exception e) {
logger.error(e.getMessage