文章目录
XXL-JOB 简述
XXL-JOB 是一个分布式任务调度平台,核心特征:开发迅速、学习简单、轻量级、易扩展,开箱即用。目前已经开发源代码,可以结合自身的业务进行二次开发。
定时任务的基本概念
程序为决解一个信息处理任务而预先编制的工作执行方案,这就是定时任务,核心组成如下:
- 执行器:负责管理应用运行时环境,用于调度定时任务。
- 任务:任务执行的流程,是一个类,具体的业务。
- 触发器:按照某种时间规则,执行具体的调度任务。
定时任务的使用场景
日常开发中,定时任务主要分为如下两种使用场景:
时间驱动:
- 对账单、日结
- 营销类短信
- 房贷短信等
数据驱动:
- 异步数据交换
- 数据同步
原生定时任务缺陷有哪些缺陷?
分布式技术应用的时代,原生定时任务的缺陷显得更为突出。结合传统项目与分布式微服务的架构,思考总结如下,欢迎各位大神给与补充:
- 不支持集群多节点部署,需要自己实现避免任务重复执行的问题。
- 不支持生命周期统一管理,如不重启服务情况下关闭、启动任务。
- 不支持分片任务,处理有序数据时,多机器分片执行任务处理不同数据。
- 不支持失败重试,出现异常后任务终结,不能根据状态控制任务重新执行。
- 不支持动态调整,不重启服务的情况下修改任务的参数。
- 没有报警机制,当任务失败后没有报警机制通知。
- 无法统计任务数据,当任务数据量大的时候,对于任务执行情况无法高效的统计执行情况。
基于当前 XXL-JOB 我们能做什么?
- 执行器 HA(分布式):天生支持任务分布式执行,无需自己实现。任务"执行器"支持集群部署,可保证任务执行 HA;
- 调度中心 HA(中心式):调度中心相当于传统调度任务的触发器,调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心 HA;
XXL-JOB 的基础说明
下载源码,按照 Maven 格式将源码导入 IDE,使用 Maven 进行编译即可,源码结构如下:
-
xxl-job-admin:调度中心 , 负责调度分布式多节点任务的执行工作
-
xxl-job-core:公共依赖,核心代码,调度中心以及任务客户端都依赖核心 jar
-
xxl-job-executor-samples:执行器 Sample 示例(选择合适的版本执行器,可直接使用,也可以参考其并将现有项目改造成执行器)
- xxl-job-executor-sample-springboot:Springboot 版本,通过 Springboot 管理执行器,推荐这种方式
- xxl-job-executor-sample-frameless:无框架版本
具体安装使用以及配置说明,仔细阅读 XXL-JOB 官方文档.md。
手把手教你解读 XXL-JOB 的核心源码以及运行原理
XXL-JOB 架构图:
整理的核心关系源码图,讲述了整合核心的调用流程以及关系原理:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q7V0ieKV-1617238484945)(https://note.youdao.com/yws/api/personal/file/56CA49DF454141E2A3523510EB283BFD?method=download&shareKey=a13380a25274728a8f289af08ad89ed1)]
Admin 核心代码
程序初始化启动入口:
@Component
public class XxlJobAdminConfig implements InitializingBean, DisposableBean {
private static XxlJobAdminConfig adminConfig = null;
public static XxlJobAdminConfig getAdminConfig() {
return adminConfig;
}
// ---------------------- XxlJobScheduler ----------------------
private XxlJobScheduler xxlJobScheduler;
@Override
public void afterPropertiesSet() throws Exception {
adminConfig = this;
xxlJobScheduler = new XxlJobScheduler();
xxlJobScheduler.init();
}
@Override
public void destroy() throws Exception {
xxlJobScheduler.destroy();
}
init() 核心步骤:
1:init i18n
2:运行注册表监视器
3:运行管理失败的监视器(守护线程运行)
4:运行管理丢失的监视器(守护线程运行)
5:启动管理触发池(守护线程运行)
fast/slow thread pool
fast 初始化
corePoolSize:10
maximumPoolSize : 200
keepAliveTime 60m
LinkedBlockingQueue 1000
slow 初始化
corePoolSize:10
maximumPoolSize : 100
keepAliveTime 60m
LinkedBlockingQueue 2000
6:管理日志报告(守护线程运行)
7:任务管理器(守护线程运行)
admin 初始化工作
JobRegistryMonitorHelper.getInstance().start()
保证任务执行的时候拿到的执行器列表都是运行的。
启动一个守护线程,每 30 秒查询数据库中自动注册的执行器,查询 90 秒未再次注册的执行器,删除 90 秒未再次注册的执行器 register 表,更新 group 表的 addressList。
JobRegistryMonitorHelper.getInstance().start()
启动一个线程,扫描失败日志判断是否需要重试,需要重试则执行触发器。
initRpcProvider();
初始化 RPC 服务 AdminBiz(任务回调、注册执行器、移除执行器)
JobScheduleHelper.getInstance().start();
- scheduleThread 构建 5 秒内要执行的任务
- ringThread 执行 5 秒内任务的触发器
触发器的工作
- 失败次数超过 10 次的任务使用慢线程池执行
- 查询要执行任务的详细信息
- 根据路由策略从执行器地址列表中得到通知的地址
- 根据通知地址初始化远程代理对象 ExecutorBiz
- 远程调用 executorBiz.run
Excutor 执行器核心
初始化做的事情
1. 、把当前应用中所有声明了 JobHandler 注解的类放到ConcurrentMap<String, IJobHandler>
。
public ReturnT<String> beat()
2. 初始化 GlueFactory
根据 codeSource 获得任务实例对象,SpringGlueFactory 拿到实例并注入属性。
3. initAdminBizList()
初始化远程 AdminBiz 远程服务代理对象,并启动一个守护线程每 30s 执行一次。
this.xxlRpcProviderFactory.initConfig(NetEnum.NETTY_HTTP, SerializeEnum.HESSIAN.getSerializer(), ip, port, accessToken, XxlJobExecutor.ExecutorServiceRegistry.class, serviceRegistryParam);
4. JobLogFileCleanThread.getInstance().start();
该线程一天执行一次,清理过期文件。
5. TriggerCallbackThread
处理任务执行结果回调给调度中心。
6. initRpcProvider()
初始化执行器 RPC 服务 ExecutorBiz。
ExecutorBiz
public ReturnT<String> beat();
路由策略故障转移算法中调用的 RPC 接口。
public ReturnT<String> idleBeat(int jobId);
路由策略忙碌转移算法中调用的 RPC 接口。
public ReturnT<String> kill(int jobId);
删除某任务。
public ReturnT<LogResult> log(long logDateTim, long logId, int fromLineNum);
查看日志。
public ReturnT<String> run(TriggerParam triggerParam);
从 jobThreadRepository 获取 JobThread。
如果没有对应的 JobThread registJobThread 一个并启动,把调用参数放到 JobThread TriggerQueue 中。
JobThread
每 3 秒获取阻塞队列任务的线程。
任务执行
1. 超时执行:
FutureTask<ReturnT<String>> futureTask = new FutureTask<ReturnT<String>>(new Callable<ReturnT<String>>() {
@Override
public ReturnT<String> call() throws Exception {
return handler.execute(triggerParamTmp.getExecutorParams());
}
});
futureThread = new Thread(futureTask);
futureThread.start();
executeResult = futureTask.get(triggerParam.getExecutorTimeout(), TimeUnit.SECONDS);
2. 普通执行:
handler.execute(triggerParamTmp.getExecutorParams());
pushCallBack 任务执行完把执行结果放入回调线程的执行队列中,90 秒内没有触发 handler 的执行,则该线程停止运行。
XxlJobExecutor.removeJobThread(jobId, "excutor idel times over limit.");
public static void removeJobThread(int jobId, String removeOldReason){
JobThread oldJobThread = jobThreadRepository.remove(jobId);
if (oldJobThread != null) {
oldJobThread.toStop(removeOldReason);
oldJobThread.interrupt();
}
}