Elastic-Job-Lite 的失效转移机制

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()来处理转移过来的分片作业。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值