elastic-job 是如何保证分布式环境下任务只有一个实例运行

本文适合使用过elastic-job的人,不会介绍如何使用elastic-job,因为如何使用已经有官方文档介绍了。

最近研究了几个分布式作业调度框架,最终从xxl-job elastic-job light-task-scheduler, 最终选择了elastic-job。这是一个去中心话设计,跟其他产品反着来,感觉挺有趣的。

我在使用elastic-job过程中,很想知道它是如何保证多个实例的情况下只有一个实例在运行任务,于是看源码,慢慢的找到了答案。

作业是如何初始化的

  1. 任务通过spring-xml的方式配置,通过源码找到spring.handler文件
  2. spring.handler中找到配置解析器JobNamespaceHandler,然后找init方法,最终定位到JobScheduler.init()
  3. JobScheduler.init()干了很多事情,大致有以下这些:
    • 更新Zookeeper的节点数据
    • 构建quartz任务
    • 初始化一堆Zookeeper Listeners,创建一堆Zookeeper节点信息
    • 开始quartz任务
到了执行时间点,如何决定该作业是否要执行
  1. 经过前面的分析找到任务执行方法AbstractElasticJobExecutor.execute()
  2. 仔细研究哪个方法会让任务不执行:
// 这个方法是判断任务十分在执行中
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;
        }
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;
        }

也就这两个方法会判断任务是否应该执行,我想找的方法应该是后一种,所以我下一步应该找shardingContexts.getShardingItemParameters()是如何获取。

  1. 中间我省略了一些步骤,直接放出最关键的部分
 /**
     * 获取运行在本作业实例的分片项集合.
     * 
     * @return 运行在本作业实例的分片项集合
     */
    public List<Integer> getLocalShardingItems() {
        if (JobRegistry.getInstance().isShutdown(jobName) || !serverService.isAvailableServer(JobRegistry.getInstance().getJobInstance(jobName).getIp())) {
            return Collections.emptyList();
        }
        return getShardingItems(JobRegistry.getInstance().getJobInstance(jobName).getJobInstanceId());
    }

/**
     * 判断作业服务器是否可用.
     * 
     * @param ip 作业服务器IP地址
     * @return 作业服务器是否可用
     */
    public boolean isAvailableServer(final String ip) {
        return isEnableServer(ip) && hasOnlineInstances(ip);
    }
    
    private boolean hasOnlineInstances(final String ip) {
        for (String each : jobNodeStorage.getJobNodeChildrenKeys(InstanceNode.ROOT)) {
            if (each.startsWith(ip)) {
                return true;
            }
        }
        return false;
    }

判断Zookeeper中的instance节点是否是本实例,如果是才会执行任务。

那instance节点是如何创建的呢?这又回到了初始化任务阶段,接续找。。。

发现了~ 在instanceService.persistOnline()这个方法中,发现instance节点中存的是最后一个启动的机器的信息。

那某台机器挂掉怎么办?
在初始化任务的时候不是有一堆的Zookeeper listener吗?某个实例下线后,触发监听器,然后重新选个新的实例就好了。

你可能感兴趣的:(Java)