HA簡介
HA(High Available),高可用性集羣,是保證單點故障問題(指單點故障會引起系統整體故障的問題)的有效解決方案,一般有兩個或兩個以上的節點,且分爲活動節點(active)及備用(standby)節點。當活動節點出現問題,導致正在運行的業務(任務)不能正常運行時,備用節點此時就會偵測到,並立即接續活動節點來執行業務,保證集羣的穩定性。
Yarn HA -- 集羣的初始化
1. 需要在主備機都執行start-yarn.sh,來啓動主備機的RM,誰優先搶注在zookeeper上,誰就作爲active節點,那在後文,將針對RM的啓動,從源碼層面做一個剖析,本文側重的HA模塊的啓動,所以在整個RM的啓動,也只介紹HA的相關模塊。
2. 那執行start-yarn.sh腳本後,就直接從ResourceManager的main()方法開始看
ResourceManager resourceManager = new ResourceManager();
ShutdownHookManager.get().addShutdownHook(
new CompositeServiceShutdownHook(resourceManager),
SHUTDOWN_HOOK_PRIORITY);
/* 會實現本類中的serviceInit方法 */
resourceManager.init(conf);
resourceManager.start();
3. main方法中執行了一個初始化方法和啓動方法,那我們優先來看init()方法,Yarn中所有的init和start方法都是重寫AbstractService類中的方法來實現的,最終分別由匹配的serviceInit(config)和serviceStart()來實現。初始化方法自然是在服務啓動前,讀取一些相關的配置文件,而這個方法就是去讀取與Yarn相關的配置文件,並保存下來,內部代碼很簡單,就不做介紹了。
//初始化啓動前,設置HA配置
this.rmContext.setHAEnabled(HAUtil.isHAEnabled(this.conf));
if (this.rmContext.isHAEnabled()) {
HAUtil.verifyAndSetConfiguration(this.conf);
}
4. 接下來來看比較重要的AdminService()類,在createAdminService()方法內就是new了一個AdminService(),ResourceManager實際上是由許多子服務組成的一個大服務,在RM的初始化中都使用addService(adminService)方法來添加子服務,最終統一調用所有子服務的初始化方法,來初始化各個子服務,本篇文章中與HA交互最深的就是AdminService(),所以接下來看一下它的init()方法
adminService = createAdminService();
addService(adminService);
rmContext.setRMAdminService(adminService);
5. 雖然這裏對HA的一些配置文件做了判斷,首先是做了是否開啓故障轉移的判斷,默認是開啓的,如果關閉了在RM的主機故障後需要使用yarn rmadmin -transitionToActive rm2命令來切換主備機,其次判斷了是否使用內置的故障轉移,但是並沒有看到不使用內置的自動故障轉移後有別的邏輯,所以從這裏可以看出yarn ha與hdfs ha有所區別,Yarn的HA應該算是一種輕量級的HA,也發現Yarn HA的機制似乎不夠完善。那這裏自然要去分析Yarn內部的故障轉移服務,createEmbeddedElectorService()裏new了一個new EmbeddedElectorService(rmContext),那來看它的init方法
//判斷是否是HA
if (rmContext.isHAEnabled()) {
//是否開啓自動故障切換模式,默認是true
autoFailoverEnabled = HAUtil.isAutomaticFailoverEnabled(conf);
if (autoFailoverEnabled) {
//是否開啓內置的自動故障切換模式,默認是true
if (HAUtil.isAutomaticFailoverEmbedded(conf)) {
//使用此故障轉移服務
embeddedElector = createEmbeddedElectorService();
addIfService(embeddedElector);
}
}
}
6. init方法裏就是一些配置文件的讀取,以及註冊至zk的一些路徑的設置,而內部最重要的就是下面這個new ActiveStandbyElector,這個類就是真正來用來實現主備機切換的,結構體內部創建了一個zkClient。至此,相關服務的初始化方法接介紹完畢了,接下來就要看RM處執行start()後,這幾類相關的服務都做了什麼事情。
elector = new ActiveStandbyElector(zkQuorum, (int) zkSessionTimeout,
electionZNode, zkAcls, zkAuths, this, maxRetryNum);
7. 在將RM的start方法之前,我想再講一下RM的初始化方法中的一個重要的方法,這個方法內部new RMActiveServices(this),而RMActiveServices子服務中又添加了許多的子服務,而這些子服務纔是ResourceManager能夠運行起來的重要服務,爲什麼在這裏提及這個,是因爲在HA模式下,RMActiveServices並不是由Yarn自己來控制start的,詳細見後文對RM serviceStart()的分析
//重要的服務在此方法內部
createAndInitActiveServices();
Yarn HA -- 集羣的啓動和異常切換
1. 從這個判斷和方法名能看到,如果是HA模式,就將RM設置成standby的狀態,這裏提及一下非HA模式下transitionToActive(),這方法內部就是將RM設置成active狀態,並去啓動初始化方法中RMActiveServices添加的子服務,那接下來我們詳細的看一下HA模式下transitionToStandby(true)內部是怎麼實現的
if (this.rmContext.isHAEnabled()) {
transitionToStandby(true);
} else {
transitionToActive();
}
2. 在入口處如果是做了判斷standby模式,則直接返回,這裏我也有點奇怪,服務是剛剛啓動的,爲何會在這裏做這個判斷,猜測是HA的自動故障轉移與啓動是異步進行的,有可能在RM初始化啓動的過程中,自動故障轉移機制已經設置好RM的狀態了,又或者是Yarn的恢復功能會對狀態有存儲,這裏可以記一下,後續分析yarn恢復功能的時候可以一起考慮進去。到這爲止,主備機的RM啓動就返回了,後面就是對其餘一些非RMActiveServices的子服務做啓動,這就不做分析了。那在後文將要分析,Yarn內置的故障轉移是怎麼去設置主備機RM的狀態的。
synchronized void transitionToStandby(boolean initialize)
throws Exception {
//如果已經是standby模式,則返回
if (rmContext.getHAServiceState() ==
HAServiceProtocol.HAServiceState.STANDBY) {
LOG.info("Already in standby state");
return;
}
LOG.info("Transitioning to standby state");
HAServiceState state = rmContext.getHAServiceState();
rmContext.setHAServiceState(HAServiceProtocol.HAServiceState.STANDBY);
//如果是active模式,就停掉所有的服務,RM HA的狀態不是由自己啓動過程來控制的
if (state == HAServiceProtocol.HAServiceState.ACTIVE) {
stopActiveServices();
reinitialize(initialize);
}
LOG.info("Transitioned to standby state");
}
3. 上文的分析中,知道了RM的重要服務在HA模式下並沒有去啓動,那這些服務是怎麼啓動起來的呢?這就要從AdminService()作爲入口去分析了,從Yarn HA集羣初始化那一節已經分析過AdminService添加了一個子服務EmbeddedElectorService,這個子服務內部初始化了Yarn HA故障恢復的實例ActiveStandbyElector,那我們來看一下EmbeddedElectorService的start()方法,非常的簡單,elector就是ActiveStandbyElector的實例對象
protected void serviceStart() throws Exception {
elector.joinElection(localActiveNodeInfo);
super.serviceStart();
}
4. 那我們來看一下這個傳參變量是什麼意思,可以看到是由yarn.resourcemanager.ha.id讀出來的rmId(通常主備機以rm1和rm2命名)和yarn.resourcemanager.cluster-id獲取到的clusterId來構造的節點信息
String rmId = HAUtil.getRMHAId(conf);
String clusterId = YarnConfiguration.getClusterId(conf);
localActiveNodeInfo = createActiveNodeInfo(clusterId, rmId);
5. 接着看 elector.joinElection(localActiveNodeInfo)方法,保存複製了一份appData,然後調用joinElectionInternal()
public synchronized void joinElection(byte[] data)
throws HadoopIllegalArgumentException {
//複製一份節點信息的數組
appData = new byte[data.length];
System.arraycopy(data, 0, appData, 0, data.length);
LOG.debug("Attempting active election for " + this);
joinElectionInternal();
}
6. 在初始化時就創建了一個ZK的客戶端,這裏對ZK客戶端做了一個非空的判斷,最後調用createLockNodeAsync()
private void joinElectionInternal() {
Preconditions.checkState(appData != null,
"trying to join election without any app data");
if (zkClient == null) {
if (!reEstablishSession()) {
fatalError("Failed to reEstablish connection with ZooKeeper");
return;
}
}
createRetryCount = 0;
wantToBeInElection = true;
createLockNodeAsync();
}
7. 可以看到,這個方法內部就是將RM的信息註冊到ZK上
private void createLockNodeAsync() {
zkClient.create(zkLockFilePath, appData, zkAcl, CreateMode.EPHEMERAL,
this, zkClient);
}
8. RM註冊到ZK上時,會觸發ZK的回調函數(涉及ZK的機制此處不做詳解),最終調用了一個重要的方法processResult(我刪掉了一些不影響邏輯的註釋和非空的判斷),那我們來看看方法裏都做了些什麼,會從ZK去獲取RM註冊的情況,如果註冊成功的話,就會進入becomeActive(),來做RM節點是否作爲活動機的判斷,如果返回true就調用 monitorActiveStatus();爲這個節點創造監控,未成功就嘗試重新嘗試成爲活動節點,那毋庸置疑,這裏需要分析的重要的方法就是becomeActive()
@Override
public synchronized void processResult(int rc, String path, Object ctx,
String name) {
Code code = Code.get(rc);
if (isSuccess(code)) {
//判斷是否成功成爲active節點
if (becomeActive()) {
monitorActiveStatus();
} else {
reJoinElectionAfterFailureToBecomeActive();
}
return;
}
}
9. 直接看關鍵方法appClient.becomeActive();這個方法是回調函數的接口,註冊到ZK必須重寫一個回調函數,這裏由Yarn的故障轉移服務來實現
private boolean becomeActive() {
assert wantToBeInElection;
if (state == State.ACTIVE) {
// 如果已經是active則返回
return true;
}
try {
//對失效的active節點的節點信息做一些校驗和處理
Stat oldBreadcrumbStat = fenceOldActive();
writeBreadCrumbNode(oldBreadcrumbStat);
LOG.debug("Becoming active for " + this);
//將此節點的RM設置成active
appClient.becomeActive();
state = State.ACTIVE;
return true;
} catch (Exception e) {
LOG.warn("Exception handling the winning of election", e);
// Caller will handle quitting and rejoining the election.
return false;
}
}
10. 在EmbeddedElectorService中實現該接口,調用了transitionToActive(req);
@Override
public void becomeActive() throws ServiceFailedException {
try {
rmContext.getRMAdminService().transitionToActive(req);
} catch (Exception e) {
throw new ServiceFailedException("RM could not transition to Active", e);
}
}
11. 這個方法內部有三個方法(我刪除了try catch),會重新獲取一些信息,最終在transitionToActive()方法內部啓動RMActiveServices中重要的子服務,成功啓動這些服務後,該節點就會成爲active節點,另外一個節點就會作爲rm的備機
//故障轉移過來後,需要
refreshAdminAcls(false);//重新獲取用戶的權限控制
refreshAll();//重新獲取節點、隊列、用戶等信息
rm.transitionToActive();//啓動RMActiveServices中重要的子服務
12. 至此Yarn HA集羣的啓動就介紹完畢了,從上文的分析來看,可以得知兩臺RM誰先註冊到ZK就優先成爲active節點,RM的啓動也是由ZK的回調函數來控制的
13. 那當作爲active的節點因異常而down調時,ZK會檢測到這個情況,在創建ZK客戶端後,ZK每隔10s會檢測一次(這意味着不是RM down掉後10s會被檢測到,而是在10s之內),當檢測到這個情況的時候,就會調用另一臺機器的回調函數去切RM爲active狀態
14. 此外,需要補充說明的是,ResourceManager只負責ApplicationMaster的狀態維護和容錯,ApplicationMaster內部管理和調度的任務,比如MapTask和ReduceTask,亦或是spark內部的計算則需要由AppicationMaster自己容錯,這不屬於YARN HA這個系統管理的範疇。再一次證明了Yarn HA的機制是粗粒度的,仍不夠完善。
小結
總的來看,Yarn HA的故障恢復並不完善,RM的切換會導致正在運行的任務重啓或者重算,並且故障恢復的機制(Yarn 內部自帶的故障恢復)也比較簡陋,從以上的分析可以得知,Yarn只是去解決在另一臺RM down掉的情況下,去將備機的RM拉起來,來有效避免單點故障的問題,卻沒有對正在運行的任務的容錯做有效的保護,這一點可能是Yarn仍需要完善的地方。如果有興趣瞭解Yarn資源調度相關的模塊可以參考我的這篇博文Yarn源碼剖析(零) --- spark任務提交到yarn的流程
作者:蛋撻
日期:2018.10.07