Elastic-Job-Lite作为一个分布式任务调度组件,当某个执行的机器出现宕机故障时,需要将这个机器上没有执行的任务分配到其它机器上执行,这就是Elastic-job的失效转移机制。下面详细介绍一下Elastic-job-lite的失效转移机制
Elastic-Job-Lite处理失效机制的几个类
- FailoverService,作业失效转移服务。
- FailoverNode,作业失效转移数据存储路径。
- FailoverListenerManager,作业失效转移监听管理器。
一、作业节点失效监听
失效转移的第一步是崩溃节点的监听,代码如下:
class JobCrashedJobListener extends AbstractJobListener {
//监听节点是否改变
@Override
protected void dataChanged(final String path, final Type eventType, final String data) {
failover(shardingNode.getItemByRunningItemPath(path), eventType);
}
}
监听器 JobCrashedJobListener 会时刻监听节点的改变,如果改变发生,会触发dataChanged函数,然后进入函数failover
/*
@param items 分片的编号
eventType 发生的节点修改事件
*/
private void failover(final Integer item, final Type eventType) {
//判断是否有节点被移除以及是否需要失效转移
if (!isJobCrashAndNeedFailover(item, eventType)) {
return;
}
//设置失效的分片项标记
failoverService.setCrashedFailoverFlag(item);
//判断分片项中是否还有执行中的作业.如果当前节点有正在执行的作业,则不会处理失效转移的分片
if (!executionService.hasRunningItems(shardingService.getLocalShardingItems())) {
failoverService.failoverIfNecessary();
}
}
函数failover会先判断判断是否有节点被移除以及是否需要失效转移,如果不是节点崩溃被移除并且需要处理失效转移,会立即结束;否则会进入下面的流程,设置失效的分片项标记。
接下来判断分片项中是否还有执行中的作业,如果当前实例正在处理自己的分片作业,则不会处理失效分片的作业。如果当前没有正在处理的作业,则进入failoverIfNecessary().
/**
* 如果需要失效转移, 则设置作业失效转移.
*/
public void failoverIfNecessary() {
if (needFailover()) {
jobNodeStorage.executeInLeader(FailoverNode.LATCH, new FailoverLeaderExecutionCallback());
}
}
在failoverIfNecessary中,先判断当前分片是否有失效转移的标志(在上个流程failoverService.setCrashedFailoverFlag(item)中设置好的)。如果有则进入到函数jobNodeStorage.executeInLeader
//当前节点是否有设置分片失效的标记,是否有作业正在运行
private boolean needFailover() {
return jobNodeStorage.isJobNodeExisted(FailoverNode.ITEMS_ROOT) && !jobNodeStorage.getJobNodeChildrenKeys(FailoverNode.ITEMS_ROOT).isEmpty()
&& !JobRegistry.getInstance().isJobRunning(jobName);
}
/**
* 在主节点执行操作.
*
* @param latchNode 分布式锁使用的作业节点名称
* @param callback 执行操作的回调
多个节点通过分布式锁的方式竞争,得到锁的节点执行分片作业,即执行LeaderExecutionCallback中的回调
*/
public void executeInLeader(final String latchNode, final LeaderExecutionCallback callback) {
try (LeaderLatch latch = new LeaderLatch(getClient(), jobNodePath.getFullPath(latchNode))) {
latch.start();
latch.await();
callback.execute();
//CHECKSTYLE:OFF
} catch (final Exception ex) {
//CHECKSTYLE:ON
handleException(ex);
}
}
在executeInLeader中,多个实例通过竞争分布式锁的方式来决定谁来执行这个分片,得到锁的实例将回调FailoverLeaderExecutionCallback 中的execute函数
class FailoverLeaderExecutionCallback implements LeaderExecutionCallback {
@Override
public void execute() {
if (JobRegistry.getInstance().isShutdown(jobName) || !needFailover()) {
return;
}
int crashedItem = Integer.parseInt(jobNodeStorage.getJobNodeChildrenKeys(FailoverNode.ITEMS_ROOT).get(0));
log.debug("Failover job '{}' begin, crashed item '{}'", jobName, crashedItem);
jobNodeStorage.fillEphemeralJobNode(FailoverNode.getExecutionFailoverNode(crashedItem), JobRegistry.getInstance().getJobInstance(jobName).getIp());
jobNodeStorage.removeJobNodeIfExisted(FailoverNode.getItemsNode(crashedItem));
//获取当前job的任务调度器,在任务调度器中执行触发函数triggerJob(),从而执行任务。
JobScheduleController jobScheduleController = JobRegistry.getInstance().getJobScheduleController(jobName);
if (null != jobScheduleController) {
jobScheduleController.triggerJob();
}
}
}
最终,在函数execute中,会获取当前job的任务调度器,在任务调度器中执行触发函数triggerJob(),从而执行任务。
在上述流程中,如果有节点崩溃,其它节点处理完成了自己的任务,就会处理失效转移的分片任务。如果其它节点都正在处理自己的任务,那么岂不是 FailoverLeaderExecutionCallback 一直无法被回调?答案当然不是的。作业在执行完分配给自己的作业分片项,会调用 LiteJobFacade#failoverIfNecessary()
方法,进行失效转移的作业分片项抓取:
public final void execute() {
// ... 省略无关代码
// 执行 普通触发的作业
execute(shardingContexts, JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER);
// 执行 被跳过触发的作业
while (jobFacade.isExecuteMisfired(shardingContexts.getShardingItemParameters().keySet())) {
jobFacade.clearMisfire(shardingContexts.getShardingItemParameters().keySet());
execute(shardingContexts, JobExecutionEvent.ExecutionSource.MISFIRE);
}
// 执行 作业失效转移
jobFacade.failoverIfNecessary();
// ... 省略无关代码
}
// LiteJobFacade.java
@Override
public void failoverIfNecessary() {
if (configService.load(true).isFailover()) {
failoverService.failoverIfNecessary();
}
}
// FailoverService.java
public void failoverIfNecessary() {
if (needFailover()) {
jobNodeStorage.executeInLeader(FailoverNode.LATCH, new FailoverLeaderExecutionCallback());
}
}
可见,每个节点在处理完自己的分片作业后,会通过调用 jobFacade.failoverIfNecessary()来处理转移过来的分片作业。