}
}
代码@1:如果配置文件中设置开启故障失效转移机制,监听到${namespace}/jobname/instances节点下子节点的删除事件时,则被认为有节点宕机,将执行故障失效转移相关逻辑。
代码@2:获取被宕机的任务实例ID(jobInstanceId)。
代码@3:如果被删除的任务节点ID与当前实例的ID相同,则忽略。
代码@4:根据jobInstanceId获取作业服务器的失效转移分片项集合。
其实现逻辑如下:FailoverService#getFailoverItems
/**
-
获取作业服务器的失效转移分片项集合.
-
@param jobInstanceId 作业运行实例主键
-
@return 作业失效转移的分片项集合
*/
public List getFailoverItems(final String jobInstanceId) {
List items = jobNodeStorage.getJobNodeChildrenKeys(ShardingNode.ROOT);
List result = new ArrayList<>(items.size());
for (String each : items) {
int item = Integer.parseInt(each);
String node = FailoverNode.getExecutionFailoverNode(item);
if (jobNodeStorage.isJobNodeExisted(node) && jobInstanceId.equals(jobNodeStorage.getJobNodeDataDirectly(node))) {
result.add(item);
}
}
Collections.sort(result);
return result;
}
首先获取 n a m e s p a c e / j o b n a m e / s h a r d i n g 目录下的直接子节点 ( 当前的分片信息 ) ,判断 {namespace}/jobname/sharding目录下的直接子节点(当前的分片信息),判断 namespace/jobname/sharding目录下的直接子节点(当前的分片信息),判断{namespace}/jobname/sharding/{item}/failover节点是否存在,如果存在判断该分片是否为当前任务的分片节点,如果是,则返回。该方法的主要目的就是获取已经转移到当前任务节点的分片信息。
代码@5,判断是否有失败分片转移到当前节点,初始状态肯定为空,将执行代码@6,设置故障转移相关准备环境。
代码@6,获取分配给Crashed(宕机的job实例)的所有分片节点,遍历已发生故障的分片,将这些分片设置为故障,待故障转移,设置为故障的实现方法为:创建${namespace}/jobname/leader/failover/items/{item}。
代码@7:执行FailoverService#failoverIfNecessary是否执行故障转移。
/**
- 如果需要失效转移, 则执行作业失效转移.
*/
public void failoverIfNecessary() {
if (needFailover()) {
jobNodeStorage.executeInLeader(FailoverNode.LATCH, new FailoverLeaderExecutionCallback());
}
}
private boolean needFailover() {
return jobNodeStorage.isJobNodeExisted(FailoverNode.ITEMS_ROOT) && !jobNodeStorage.getJobNodeChildrenKeys(FailoverNode.ITEMS_ROOT).isEmpty()
&& !JobRegistry.getInstance().isJobRunning(jobName);
}
其实现思路:【needFailover方法】首先判断是否存在 n a m e s p a c e / j o b n a m e / l e a d e r / f a i l o v e r / i t e m s 节点是否存在,并且其节点下是否有子节点,并且节点也运行该任务,则需要执行故障失效转移。执行失效转移的逻辑也是进行失效转移选主,其分布式锁节点为: {namespace}/jobname/leader/failover/items节点是否存在,并且其节点下是否有子节点,并且节点也运行该任务,则需要执行故障失效转移。执行失效转移的逻辑也是进行失效转移选主,其分布式锁节点为: namespace/jobname/leader/failover/items节点是否存在,并且其节点下是否有子节点,并且节点也运行该任务,则需要执行故障失效转移。执行失效转移的逻辑也是进行失效转移选主,其分布式锁节点为:{namespace}/jobname/leader/failover/latch,谁先获得锁,则执行失效故障转移具体逻辑(FailoverLeaderExecutionCallback),具体的失效转移算法为:
FailoverService#FailoverLeaderExecutionCallback:
class FailoverLeaderExecutionCallback implements LeaderExecutionCallback {
@Override
public void execute() {
if (JobRegistry.getInstance().isShutdown(jobName) || !needFailover()) { // @1
return;
}
int crashedItem = Integer.parseInt(jobNodeStorage.getJobNodeChildrenKeys(FailoverNode.ITEMS_ROOT).get(0)); // @2
log.debug(“Failover job ‘{}’ begin, crashed item ‘{}’”, jobName, crashedItem);
jobNodeStorage.fillEphemeralJobNode(FailoverNode.getExecutionFailoverNode(crashedItem), JobRegistry.getInstance().getJobInstance(jobName).getJobInstanceId()); // @3
jobNodeStorage.removeJobNodeIfExisted(FailoverNode.getItemsNode(crashedItem)); // @4
// TODO 不应使用triggerJob, 而是使用executor统一调度
JobScheduleController jobScheduleController = JobRegistry.getInstance().getJobScheduleController(jobName); // @5
if (null != jobScheduleController) {
jobScheduleController.triggerJob();
}
}
}
代码@1:如果当前实例停止运行该job或无需执行失效故障转移,则返回。
代码@2:获取第一个待故障转移的分片,获取${namespace}/jobname/leader/failover/items/{itemnum,获取分片序号itemnum。
代码@3:创建临时节点${namespace}/jobname/sharding/itemnum/failover节点。
代码@4:删除${namespace}/jobname/leader/failover/items/{itemnum}节点。
代码@5:触发任务调度,并结束当前节点的故障失效转移,然后释放锁,下一个节点获取锁,进行转移${namespace}/jobname/leader/failover/items目录下的失效分片。
PS:故障实现转移基本实现思路为:当一个任务节点宕机后,其他节点会监听到实例删除事件,从实例目录中获取其实例ID,并从ZK中获取原先分配故障实例的分片信息,并将这些分片标记为需要故障转移(创建${namespace}/jobname/leader/failover/items/{item}持久节点),然后判断是否需要执行故障转移操作。
执行故障转移操作的前提条件是:
-
当前任务实例也调度该job;
-
存在 n a m e s p a c e / j o b n a m e / l e a d e r / f a i l o v e r / i t e m s 节点并有子节点。如果满足上述两个条件,则执行失效转移,多个存活节点进行选主 ( L e a d e r L a t c h ) , 创建分布式锁节点 ( {namespace}/jobname/leader/failover/items节点并有子节点。如果满足上述两个条件,则执行失效转移,多个存活节点进行选主(LeaderLatch),创建分布式锁节点( namespace/jobname/leader/failover/items节点并有子节点。如果满足上述两个条件,则执行失效转移,多个存活节点进行选主(LeaderLatch),创建分布式锁节点({namespace}/jobname/leader/failover/latch),获取锁的节点优先执行获取分片节点,其具体过程如上述所示,每个存活节点一次故障转移只竞争一个分片。
上述事件监听器主要的作用是当任务节点失效后,其他存活节点“瓜分”失效节点的分片,创建${namespace}/jobname/sharding/{item}/failover节点。但这些分片的任务并没有真正执行,本小结将梳理故障节点分片的执行。
可以看得出来,分片故障转移,就是在对应的故障分片下创建了failover节点,在获取分片信息上下文时会优先处理,这也是在分析分片流程时并未重点讲解的。因此,在进入下述内容之前,请先阅读 源码分析ElasticJob的分片机制。
回到定时任务调度执行入口:AbstractElasticJobExecutor#execute
/**
- 执行作业.
*/
public final void execute() {
try {
jobFacade.checkJobExecutionEnvironment();
} catch (final JobExecutionEnvironmentException cause) {
jobExceptionHandler.handleException(jobName, cause);
}
ShardingContexts shardingContexts = jobFacade.getShardingContexts(); // 获取分片上下文环境
…
}
LiteJobFacade#getShardingContexts
技术学习总结
学习技术一定要制定一个明确的学习路线,这样才能高效的学习,不必要做无效功,既浪费时间又得不到什么效率,大家不妨按照我这份路线来学习。
最后面试分享
大家不妨直接在牛客和力扣上多刷题,同时,我也拿了一些面试题跟大家分享,也是从一些大佬那里获得的,大家不妨多刷刷题,为金九银十冲一波!
4205257624)]
[外链图片转存中…(img-K2WKFGIo-1714205257625)]
[外链图片转存中…(img-Jz5Br06c-1714205257625)]
最后面试分享
大家不妨直接在牛客和力扣上多刷题,同时,我也拿了一些面试题跟大家分享,也是从一些大佬那里获得的,大家不妨多刷刷题,为金九银十冲一波!
[外链图片转存中…(img-RDMBaajW-1714205257625)]
[外链图片转存中…(img-D2ZQn4Lz-1714205257625)]