Elastic-Job源码分析之AbstractElasticJobExecutor分析
还记得我们在JobScheduler中,在创建任务详情时,会调用一个建造器JobBuilder来创建一个Job,类型是LiteJob。
LiteJob.java
/**
* Lite调度作业.
*
* @author zhangliang
*/
public final class LiteJob implements Job {
@Setter
private ElasticJob elasticJob;
@Setter
private JobFacade jobFacade;
@Override
public void execute(final JobExecutionContext context) throws JobExecutionException {
JobExecutorFactory.getJobExecutor(elasticJob, jobFacade).execute();
}
}
进入到LiteJob,我们可以看到,它继承自quartz中的Job,同时新增了两个属性elasticJob和jobFacade,这个我们后续分析。我们关注的是execute方法。首先通过工厂模式,确定了执行器,我们可以看到有三种执行器,分别是ScriptJobExecutor、SimpleJobExecutor和DataflowJobExecutor,分别对应了三种job类型。由于SimpleJob覆盖了80%的使用场景,我们主要来分析一下SimpleJobExecutor。
SimpleExecutor.java
public final class SimpleJobExecutor extends AbstractElasticJobExecutor {
private final SimpleJob simpleJob;
public SimpleJobExecutor(final SimpleJob simpleJob, final JobFacade jobFacade) {
super(jobFacade);
this.simpleJob = simpleJob;
}
@Override
protected void process(final ShardingContext shardingContext) {
simpleJob.execute(shardingContext);
}
}
这个执行器继承自AbstractElasticJobExecutor,然后里面实现的内容也很简单,子类需要实现父类的方法process,其他的方法在父类中执行。我们重点看一下AbstractElasticJobExecutor这个基础执行器。
AbstractElasticJobExecutor.java
从代码结构看,主要看几个方法,execute()和process(),这边都是前后依赖的,所以我们顺序看一下。
execute()
try {
jobFacade.checkJobExecutionEnvironment();
} catch (final JobExecutionEnvironmentException cause) {
jobExceptionHandler.handleException(jobName, cause);
}
首先检查运行环境信息。跟进去,我们可以发现,检查的内容是本机与注册中心的时间误差秒数是否在允许范围,就是我们配置的max-time-diff-seconds,超过范围就直接抛出异常。
ShardingContexts shardingContexts = jobFacade.getShardingContexts();
if (shardingContexts.isAllowSendJobEvent()) {
jobFacade.postJobStatusTraceEvent(shardingContexts.getTaskId(), State.TASK_STAGING, String.format("Job '%s' execute begin.", jobName));
}
接着,首先获取分片上下文信息。获取分片上下文的具体执行内容为:
@Override
public ShardingContexts getShardingContexts() {
boolean isFailover = configService.load(true).isFailover();
if (isFailover) {
List<Integer> failoverShardingItems = failoverService.getLocalFailoverItems();
if (!failoverShardingItems.isEmpty()) {
return executionContextService.getJobShardingContext(failoverShardingItems);
}
}
shardingService.shardingIfNecessary();
List<Integer> shardingItems = shardingService.getLocalShardingItems();
if (isFailover) {
shardingItems.removeAll(failoverService.getLocalTakeOffItems());
}
shardingItems.removeAll(executionService.getDisabledItems(shardingItems));
return executionContextService.getJobShardingContext(shardingItems);
}
首先根据配置判断是否开启失效转移failover,开启表示如果作业在一次任务执行中途宕机,允许将该次未完成的任务在另一作业节点上补偿执行。
下一步,判断是否需要分片。
public void shardingIfNecessary() {
List<JobInstance> availableJobInstances = instanceService.getAvailableJobInstances();//获取可用任务节点
if (!isNeedSharding() || availableJobInstances.isEmpty()) {
return;
}
if (!leaderService.isLeaderUntilBlock()) {//判断当前节点是否是主节点,如果主节点正在选举,则阻塞至主节点选举完成后再返回
blockUntilShardingCompleted();//阻塞至分片完成
return;
}
waitingOtherJobCompleted();
LiteJobConfiguration liteJobConfig = configService.load(false);//从配置文件中获取任务配置信息
int shardingTotalCount = liteJobConfig.getTypeConfig().getCoreConfig().getShardingTotalCount();
log.debug("Job '{}' sharding begin.", jobName);
jobNodeStorage.fillEphemeralJobNode(ShardingNode.PROCESSING, "");
resetShardingInfo(shardingTotalCount);//重置分片信息
JobShardingStrategy jobShardingStrategy = JobShardingStrategyFactory.getStrategy(liteJobConfig.getJobShardingStrategyClass());//获取配置文件中的分片策略
jobNodeStorage.executeInTransaction(new PersistShardingInfoTransactionExecutionCallback(jobShardingStrategy.sharding(availableJobInstances, jobName, shardingTotalCount)));//根据不同的策略进行分片,这块后续我们再分析
log.debug("Job '{}' sharding complete.", jobName);
}
分片判断完成后,获取本机的分片项,然后如果开启失效转移,删除本地失效转移项。
言归正传,获取到分片上下文shardingContexts后,判断是否允许发送任务事件。
if (jobFacade.misfireIfRunning(shardingContexts.getShardingItemParameters().keySet())) {//设置任务被错过执行的标记
if (shardingContexts.isAllowSendJobEvent()) {
jobFacade.postJobStatusTraceEvent(shardingContexts.getTaskId(), State.TASK_FINISHED, String.format(
"Previous job '%s' - shardingItems '%s' is still running, misfired job will start after previous job completed.", jobName,
shardingContexts.getShardingItemParameters().keySet()));
}
return;
}
分片项被错过执行,发布任务事件。下一步,准备执行。
try {
jobFacade.beforeJobExecuted(shardingContexts);
} catch (final Throwable cause) {
jobExceptionHandler.handleException(jobName, cause);
}
execute(shardingContexts, JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER);
while (jobFacade.isExecuteMisfired(shardingContexts.getShardingItemParameters().keySet())) {
jobFacade.clearMisfire(shardingContexts.getShardingItemParameters().keySet());
execute(shardingContexts, JobExecutionEvent.ExecutionSource.MISFIRE);
}
jobFacade.failoverIfNecessary();
try {
jobFacade.afterJobExecuted(shardingContexts);
} catch (final Throwable cause) {
jobExceptionHandler.handleException(jobName, cause);
}
先做一些执行前准备(清理上次执行信息),然后执行,判断是否开启失效转移,执行成功后做一些执行后处理。
execute(final ShardingContexts shardingContexts, final JobExecutionEvent.ExecutionSource executionSource)
看代码...
if (shardingContexts.getShardingItemParameters().isEmpty()) {
if (shardingContexts.isAllowSendJobEvent()) {
jobFacade.postJobStatusTraceEvent(shardingContexts.getTaskId(), State.TASK_FINISHED, String.format("Sharding item for job '%s' is empty.", jobName));
}
return;
}
jobFacade.registerJobBegin(shardingContexts);
String taskId = shardingContexts.getTaskId();
if (shardingContexts.isAllowSendJobEvent()) {
jobFacade.postJobStatusTraceEvent(taskId, State.TASK_RUNNING, "");
}
try {
process(shardingContexts, executionSource);
} finally {
// TODO 考虑增加作业失败的状态,并且考虑如何处理作业失败的整体回路
jobFacade.registerJobCompleted(shardingContexts);
if (itemErrorMessages.isEmpty()) {
if (shardingContexts.isAllowSendJobEvent()) {
jobFacade.postJobStatusTraceEvent(taskId, State.TASK_FINISHED, "");
}
} else {
if (shardingContexts.isAllowSendJobEvent()) {
jobFacade.postJobStatusTraceEvent(taskId, State.TASK_ERROR, itemErrorMessages.toString());
}
}
}
主要做一些前期准备和后期的方法。重点是process方法,我们继续看。
process(final ShardingContexts shardingContexts, final JobExecutionEvent.ExecutionSource executionSource)
Collection<Integer> items = shardingContexts.getShardingItemParameters().keySet();
if (1 == items.size()) {//一个分片,立即执行
int item = shardingContexts.getShardingItemParameters().keySet().iterator().next();
JobExecutionEvent jobExecutionEvent = new JobExecutionEvent(shardingContexts.getTaskId(), jobName, executionSource, item);
process(shardingContexts, item, jobExecutionEvent);
return;
}
final CountDownLatch latch = new CountDownLatch(items.size());//多个分片,使用CountDownLatch,并行执行
for (final int each : items) {
final JobExecutionEvent jobExecutionEvent = new JobExecutionEvent(shardingContexts.getTaskId(), jobName, executionSource, each);
if (executorService.isShutdown()) {
return;
}
executorService.submit(new Runnable() {
@Override
public void run() {
try {
process(shardingContexts, each, jobExecutionEvent);
} finally {
latch.countDown();
}
}
});
}
try {
latch.await();
} catch (final InterruptedException ex) {
Thread.currentThread().interrupt();
}
private void process(final ShardingContexts shardingContexts, final int item, final JobExecutionEvent startEvent)
if (shardingContexts.isAllowSendJobEvent()) {
jobFacade.postJobExecutionEvent(startEvent);
}
log.trace("Job '{}' executing, item is: '{}'.", jobName, item);
JobExecutionEvent completeEvent;
try {
process(new ShardingContext(shardingContexts, item));
completeEvent = startEvent.executionSuccess();
log.trace("Job '{}' executed, item is: '{}'.", jobName, item);
if (shardingContexts.isAllowSendJobEvent()) {
jobFacade.postJobExecutionEvent(completeEvent);
}
} catch (final Throwable cause) {
completeEvent = startEvent.executionFailure(cause);
jobFacade.postJobExecutionEvent(completeEvent);
itemErrorMessages.put(item, ExceptionUtil.transform(cause));
jobExceptionHandler.handleException(jobName, cause);
}
Elastic-Job源码分析之AbstractElasticJobExecutor分析的更多相关文章
- ArrayList源码和多线程安全问题分析
1.ArrayList源码和多线程安全问题分析 在分析ArrayList线程安全问题之前,我们线对此类的源码进行分析,找出可能出现线程安全问题的地方,然后代码进行验证和分析. 1.1 数据结构 Arr ...
- Okhttp3源码解析(3)-Call分析(整体流程)
### 前言 前面我们讲了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析]( ...
- Okhttp3源码解析(2)-Request分析
### 前言 前面我们讲了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析]( ...
- Spring mvc之源码 handlerMapping和handlerAdapter分析
Spring mvc之源码 handlerMapping和handlerAdapter分析 本篇并不是具体分析Spring mvc,所以好多细节都是一笔带过,主要是带大家梳理一下整个Spring mv ...
- HashMap的源码学习以及性能分析
HashMap的源码学习以及性能分析 一).Map接口的实现类 HashTable.HashMap.LinkedHashMap.TreeMap 二).HashMap和HashTable的区别 1).H ...
- ThreadLocal源码及相关问题分析
前言 在高并发的环境下,当我们使用一个公共的变量时如果不加锁会出现并发问题,例如SimpleDateFormat,但是加锁的话会影响性能,对于这种情况我们可以使用ThreadLocal.ThreadL ...
- 物联网防火墙himqtt源码之MQTT协议分析
物联网防火墙himqtt源码之MQTT协议分析 himqtt是首款完整源码的高性能MQTT物联网防火墙 - MQTT Application FireWall,C语言编写,采用epoll模式支持数十万 ...
- Netty 源码学习——客户端流程分析
Netty 源码学习--客户端流程分析 友情提醒: 需要观看者具备一些 NIO 的知识,否则看起来有的地方可能会不明白. 使用版本依赖 <dependency> <groupId&g ...
- linux源码Makefile的详细分析
目录 一.概述 1.本文的意义 2.Linux内核Makefile文件组成 二.Linux内核Makefile的“make解析”过程 1 顶层Makefile阶段 1.从总目标uImage说起 2.v ...
- lesson8:AtomicInteger源码解析及性能分析
AtomicInteger等对象出现的目的主要是为了解决在多线程环境下变量计数的问题,例如常用的i++,i--操作,它们不是线程安全的,AtomicInteger引入后,就不必在进行i++和i--操作 ...
随机推荐
- redis cluster 使用中出现的问题
问题一 redis.clients.jedis.exceptions.JedisClusterMaxRedirectionsException: Too many Cluster redirectio ...
- CocoaPods:library not found for -lPods
This is my first shot to write a blog in English. Enjoy! ;) CocoaPods is a popular way to control iO ...
- SDJZUOJ迷宫问题
题目描述 小明置身于一个迷宫,请你帮小明找出从起点到终点的最短路程. 小明只能向上下左右四个方向移动. 输入格式 输入包含多组测试数据.输入的第一行是一个整数T,表示有T组测试数据. 每组输入的第一行 ...
- C# 使用ProcessStartInfo调用exe获取不到重定向数据的解决方案
emmmmm,最近在研究WFDB工具箱,C语言写的,无奈本人C语言功底不够,只想直接拿来用,于是打算通过ProcessStartInfo来调取编译出来的exe程序获取输出. 一开始就打算偷懒,从园子里 ...
- C#导出EXCEL,并生成charts表
需要添加引用 Microsoft.Office.Interop.Excel 注意:使用Microsoft.Office.Interop.Excel 非常耗时.对性能有要求建议用其他. 如果要用,把数 ...
- iOS Objective-C 中 bool 与 BOOL 的你不一定知道的事
测试一下这段代码: - (void)test { NSLog(@"this is an attribut: %d", anAttribute); ; i < ; i++) { ...
- uploadPreview 上传图片前预览 IE9 索引无效的问题
最近公司的项目用到比较多的上传图片的操作,所以用到了基于jquery的上传前预览的插件 uploadPreview ,后来测试的时候发现在IE9下报索引无效的问题. 异常的产生方式 放一个file控件 ...
- git删除未监视的文件
新增的文件使用git status查看会提示Untracked files,如果想要删除Untracked files,可以使用如下命令: git clean -f # 删除Untracked fil ...
- Python3.5 学习四
装饰器 定义:本质是函数,装饰其他函数,即为其他函数添加附加功能的 原则: 1 不能修改被装饰函数的源代码 2 不能改变被装饰函数的调用方式(对于被装饰函数来说完全透明,不会受影响) 实现装饰器功能的 ...
- 【文文殿下】[51nod1469] 淋漓尽致子串
SAM的经典应用 一个状态的SIze==1绝对不合法. 一个状态在parent树上有一个Size>1的后继绝对不合法(前面可以再补字符) 一个状态可以转移到Size>1的节点绝对不合法,因 ...