源碼分析ElasticJob 選舉及分片

源碼分析ElasticJob 選舉及分片

elastic-job是將分片項分配至各個運行中的作業服務器,需自行處理實現分片項和數據的關係,分片策略包括(平均分配算法策略,作業名哈希值奇偶數算法策略,輪轉分片策略。同時也提供了自定義分片策略的接口)
elastic-job 的分片邏輯是用一臺主節點服務器觸發,服務器之間需要進行選主,選主的過程就是通過註冊中心zookeeper來實現

選主實現

調用源碼發現 其調用LeaderService的electLeader方法

public void electLeader() {
    jobNodeStorage.executeInLeader(LeaderNode.LATCH, new LeaderElectionExecutionCallback());
}

主節點選舉時採用先獲取先得到方式,其它節點會在主節點選舉時,統一等待,選主分佈式鎖節點目錄 /{namespace}/{jobName}/leader/election/latch

LeaderNode.LATCH 節點選舉
LeaderElectionExecutionCallback回調會在拿到leader鎖的節點觸發

接下來分析 jobNodeStorage.executeInLeader()方法

public void executeInLeader(final String latchNode, final LeaderExecutionCallback callback) {
    try (LeaderLatch latch = new LeaderLatch(getClient(), jobNodePath.getFullPath(latchNode))) {
		//開始
        latch.start();
        //等待鎖釋放
        latch.await();
        callback.execute();
    } catch (final Exception ex) {
        handleException(ex);
    }
}

latchNode: 分佈式鎖使用的作業節點名稱
callback: 執行後回調函數
同時有很多服務器都會執行選主,爲了保證有序性,拿到主節點服務器會執行await 釋放鎖,執行callback邏輯,判斷masterNode.INSTANCE是否被設置 在此期間,其它服務器同樣也會獲取鎖之後執行callback,主節點服務器設置狀態後,其它服務器不參與處理
獲取分佈式鎖之後,會判斷{namespace}/{jobname}/leader/election/in-stance節點是否存在,不存在則創建臨時節點並存儲內容

問題思考

一次完整選舉成功之後,如果主服務器在此期間出現宕機,ElasticJob 如何剔除節點,讓從服務器升級主節點並接管?

  • 監控主服務器宕機後,將其節點剔除
  • 再觸發一次選主過程

接下來重點分析事件監控

 public void start() {
      addDataListener(new LeaderElectionJobListener());
      addDataListener(new LeaderAbdicationJobListener());
    }

選主管理器在啓動時會添加兩個事件監聽器

  • LeaderElectionJobListener 當主節點宕機後觸發重新選主監聽器
  • LeaderAbdicationJobListener 主節點退位監聽器。當通過配置方式在線設置主節點狀態爲disabled時需要刪除主節點信息從而再次激活選主事件

LeaderElectionJobListener 監聽節點master.INSTANCE,對應路徑爲{namespace}/{jobname}/leader/election/in-stance,如果主節點宕機,zookeeper心跳檢測到後,會刪除此節點及其內容,並且服務器監控到事件後會觸發調用

LeaderAbdicationJobListener 如果通過後臺管理更改主節點狀態爲 ‘disabled’ ,此時會觸發將節點信息刪除,並且觸發一次選主。

分片實現

分片

接下來分析 ShardingService.setReshardingFlag()

public void setReshardingFlag() {
    jobNodeStorage.createJobNodeIfNeeded(ShardingNode.NECESSARY);
}

job初始化時調用並設置分片標記,如分片狀態存在,等待主節點分片完成後再執行對應分片邏輯

分片邏輯 :ShardingService.shardingIfNecessary

/**
 * 如果需要分片且當前節點爲主節點, 則作業分片
 * 
 * 如果當前無可用節點則不分片
 */
public void shardingIfNecessary() {
    //獲取可分片的作業運行實例
    List<JobInstance> availableJobInstances = instanceService.getAvailableJobInstances();
    //判斷是否需要進行分片
    if (!isNeedSharding() || availableJobInstances.isEmpty()) {
        return;
    }
    //判斷是否是主節點,不是主節點進入if邏輯,同時內部會等待主節點選舉完成
    if (!leaderService.isLeaderUntilBlock()) {
        //等待主節點分片完成
        blockUntilShardingCompleted();
        return;
    }
    //等待當前任務的其他分片運行結束
    waitingOtherShardingItemCompleted();
    LiteJobConfiguration liteJobConfig = configService.load(false);
    int shardingTotalCount = liteJobConfig.getTypeConfig().getCoreConfig().getShardingTotalCount();
    log.debug("Job '{}' sharding begin.", jobName);
    //設置分片運行標誌
    jobNodeStorage.fillEphemeralJobNode(ShardingNode.PROCESSING, "");
    //清空之前的sharding節點
    resetShardingInfo(shardingTotalCount);
    //獲取配置的分片策略類,不存在使用默認的AverageAllocationJobShardingStrategy
    JobShardingStrategy jobShardingStrategy = JobShardingStrategyFactory.getStrategy(liteJobConfig.getJobShardingStrategyClass());
    //執行分片邏輯
    jobNodeStorage.executeInTransaction(new PersistShardingInfoTransactionExecutionCallback(jobShardingStrategy.sharding(availableJobInstances, jobName, shardingTotalCount)));
}

執行job時候會觸發分片,查看分片標誌是否重新被設置,沒設置觸發分片邏輯
執行分片具體方法sharding

public interface JobShardingStrategy {

/**
 * 作業分片.
 * 
 * @param jobInstances 所有參與分片的單元列表
 * @param jobName 作業名稱
 * @param shardingTotalCount 分片總數
 * @return 分片結果
 */
Map<JobInstance, List<Integer>> sharding(List<JobInstance> jobInstances, String jobName, int shardingTotalCount);
}

public final class AverageAllocationJobShardingStrategy implements JobShardingStrategy {
    
    @Override
    public Map<JobInstance, List<Integer>> sharding(final List<JobInstance> jobInstances, final String jobName, final int shardingTotalCount) {
        if (jobInstances.isEmpty()) {
            return Collections.emptyMap();
        }
        Map<JobInstance, List<Integer>> result = shardingAliquot(jobInstances, shardingTotalCount);
        addAliquant(jobInstances, shardingTotalCount, result);
        return result;
    }
    
    private Map<JobInstance, List<Integer>> shardingAliquot(final List<JobInstance> shardingUnits, final int shardingTotalCount) {
        Map<JobInstance, List<Integer>> result = new LinkedHashMap<>(shardingTotalCount, 1);
        //分片數/作業實例數,得到每個作業實例最少得到的分片數
        int itemCountPerSharding = shardingTotalCount / shardingUnits.size();
        int count = 0;
        for (JobInstance each : shardingUnits) {
            List<Integer> shardingItems = new ArrayList<>(itemCountPerSharding + 1);
            //給每個作業實例分配最少得到的分片數
            for (int i = count * itemCountPerSharding; i < (count + 1) * itemCountPerSharding; i++) {
                shardingItems.add(i);
            }
            result.put(each, shardingItems);
            count++;
        }
        return result;
    }
    
    private void addAliquant(final List<JobInstance> shardingUnits, final int shardingTotalCount, final Map<JobInstance, List<Integer>> shardingResults) {
        //分片數%作業實例數,得到多餘的分片數
        int aliquant = shardingTotalCount % shardingUnits.size();
        int count = 0;
        for (Map.Entry<JobInstance, List<Integer>> entry : shardingResults.entrySet()) {
            //把多餘的分片數,按順序分給作業服務器
            if (count < aliquant) {
                entry.getValue().add(shardingTotalCount / shardingUnits.size() * shardingUnits.size() + count);
            }
            count++;
        }
    }
}

通過JobShardingStrategy得到分片結果之後,通過PersistShardingInfoTransactionExecutionCallback將結果持久化到zookeeper

  @RequiredArgsConstructor
    class PersistShardingInfoTransactionExecutionCallback implements TransactionExecutionCallback {
        
    private final Map<JobInstance, List<Integer>> shardingResults;
    
    @Override
    public void execute(final CuratorTransactionFinal curatorTransactionFinal) throws Exception {
        for (Map.Entry<JobInstance, List<Integer>> entry : shardingResults.entrySet()) {
            for (int shardingItem : entry.getValue()) {
                //設置節點/namespace/jobName/sharding/shardingItem/instance = jobinstanceid
                curatorTransactionFinal.create().forPath(jobNodePath.getFullPath(ShardingNode.getInstanceNode(shardingItem)), entry.getKey().getJobInstanceId().getBytes()).and();
            }
        }
        //清除分片標記
        curatorTransactionFinal.delete().forPath(jobNodePath.getFullPath(ShardingNode.NECESSARY)).and();
        //清除分片進行中標記
        curatorTransactionFinal.delete().forPath(jobNodePath.getFullPath(ShardingNode.PROCESSING)).and();
    }
}

本文詳細描述了ElasticJob 的選舉及分片實現

1.通過先獲先得 獲取分佈式鎖成爲主節點,創建masterNode.INSTANCE節點並記錄節點的信息,監聽節點及其目錄
2.主服務區宕機後,移除節點,通過事件監聽觸發選主
3.後臺管理設置主服務器爲disabled,移除節點,觸發選主
4.服務器啓動會觸發檢測分片標記,未標記觸發分片邏輯
5.分片邏輯執行完後會持久化到zookeeper

作者簡介:張程 技術研究

更多文章請關注微信公衆號:zachary分解獅 (frankly0423)
my

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章