源碼分析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)