HDFS的滾動升級: Rolling Upgrade
前言
目前Hadoop版本更新迭代的速度還是比較快的,每次新版本的釋出,都是一件令人期待的事情.因為這意味著使用者可以使用新的功能特性,又或者說在新版中某某模組效能得到了巨大提升等等.現在問題來了,如果我們想用新的Hadoop版本,那麼我們必須對現有版本進行升級.在一定程度上來講,Hadoop的升級說白了其實就是HDFS的升級,也就是資料上的升級.在傳統的升級方案中,我們往往需要停止叢集服務來做這樣的操作.如果叢集規模已經是非常大的情況下時,這樣做的代價實在太高.為了解決這類的問題,HDFS在2.4.0以及以上版本中引入了Rolling Upgrade的概念,也就是滾動升級的意思.Rolling Upgrade升級方式最重要的一點是它可以保證在升級過程中叢集依然可以對外提供服務
Rolling Upgrade相關指令以及原理
在講述Rolling Upgrade實際操作步驟之前,我們有必要事先了解其中相關的使用命令,以及這些命令的作用和原理.主要是下面這6個:
- 1.hdfs dfsadmin下的子命令
- 1.1 hdfs dfsadmin -rollingUpgrade prepare
- 1.2 hdfs dfsadmin -rollingUpgrade query
- 1.3 hdfs dfsadmin -rollingUpgrade finalize
- 2.hdfs namenode下的子命令
- 2.1 hdfs namenode -rollingUpgrade rollback
- 2.2 hdfs namenode -rollingUpgrade started
- 3.hdfs datanode下的子命令
- 3.1 hdfs datanode rollback
下面我們對上面的命令一一做分析.
hdfs dfsadmin下的升級命令
首先是hdfs dfsadmin下的3條命令,這是官方文件對這3條命令的解釋:
query: Query the current rolling upgrade status.
prepare: Prepare a new rolling upgrade.
finalize : Finalize the current rolling upgrade.
可能看完官方文件的解釋,我們還是會有點迷惑,沒有關係,下面我們直接定位到相關程式碼,來深入瞭解其背後的操作原理.
hdfs dfsadmin的入口類為DFSAdmin,順著此類,我們可以找到對應服務端的處理方法,在NameNodeRpcServer類中,如下:
public RollingUpgradeInfo rollingUpgrade(RollingUpgradeAction action) throws IOException {
checkNNStartup();
LOG.info("rollingUpgrade " + action);
// 不同hdfs dfsadmin -rollingUpgrade操作對應不同action處理
switch(action) {
case QUERY:
// 對應hdfs dfsadmin -rollingUpgrade query
return namesystem.queryRollingUpgrade();
case PREPARE:
// 對應hdfs dfsadmin -rollingUpgrade prepare
return namesystem.startRollingUpgrade();
case FINALIZE:
// 對應hdfs dfsadmin -rollingUpgrade finalize
return namesystem.finalizeRollingUpgrade();
default:
throw new UnsupportedActionException(action + " is not yet supported.");
}
}
NameNodeRpcServer類的rollingUpgrade方法繼而呼叫FSNamesystem的方法完成最後的操作.我們首先分析Prepare的處理邏輯,程式碼如下:
RollingUpgradeInfo startRollingUpgrade() throws IOException {
checkSuperuserPrivilege();
checkOperation(OperationCategory.WRITE);
writeLock();
try {
checkOperation(OperationCategory.WRITE);
// 判斷是否正處於rolling upgrade狀態,如果是,則返回相關資訊
if (isRollingUpgrade()) {
return rollingUpgradeInfo;
}
// 如果不是,則執行rolling upgrade開始動作,分為HA和非HA模式
long startTime = now();
if (!haEnabled) { // for non-HA, we require NN to be in safemode
startRollingUpgradeInternalForNonHA(startTime);
} else { // for HA, NN cannot be in safemode
checkNameNodeSafeMode("Failed to start rolling upgrade");
startRollingUpgradeInternal(startTime);
}
// 執行editlog相關記錄操作
getEditLog().logStartRollingUpgrade(rollingUpgradeInfo.getStartTime());
if (haEnabled) {
// roll the edit log to make sure the standby NameNode can tail
getFSImage().rollEditLog();
}
} finally {
writeUnlock();
}
...
// 返回升級物件資訊
return rollingUpgradeInfo;
}
在startRollingUpgrade執行方法中,主要建立了將來用於rollback的映象檔案,但是在實現方式上,HA與非HA模式並不完全相同,以非HA模式為例:
private void startRollingUpgradeInternalForNonHA(long startTime)
throws IOException {
Preconditions.checkState(!haEnabled);
if (!isInSafeMode()) {
throw new IOException("Safe mode should be turned ON "
+ "in order to create namespace image.");
}
checkRollingUpgrade("start rolling upgrade");
getFSImage().checkUpgrade();
// 做一次checkpoint,生成一個新的用於rollback的映象
// in non-HA setup, we do an extra checkpoint to generate a rollback image
getFSImage().saveNamespace(this, NameNodeFile.IMAGE_ROLLBACK, null);
LOG.info("Successfully saved namespace for preparing rolling upgrade.");
// 離開安全模式,並設定啟動rolling upgrade啟動時間和建立映象標識
// leave SafeMode automatically
setSafeMode(SafeModeAction.SAFEMODE_LEAVE);
setRollingUpgradeInfo(true, startTime);
}
以上非HA模式的操作非常直觀,直接建立rollback映象,這時我們來看HA模式將執行的startRollingUpgradeInternal方法:
void startRollingUpgradeInternal(long startTime)
throws IOException {
checkRollingUpgrade("start rolling upgrade");
getFSImage().checkUpgrade();
setRollingUpgradeInfo(false, startTime);
}
這時我們可能就比較疑惑了,此時並沒有建立新fsimage的方法,本人在剛開始閱讀的時候,也感到比較疑惑,後來通過追蹤建立fsimage的動作方法最終明白了其中十分隱蔽的原理.下面是非HA模式下建立rollback映象的原理過程:
1.startRollingUpgradeInternal的確是不會建立快照,但是在執行完startRollingUpgradeInternal方法後,還會執行關鍵的一步操作:
getEditLog().logStartRollingUpgrade(rollingUpgradeInfo.getStartTime());
2.此操作會觸發needRollbackFsImage標識的改變,程式碼如下(請留意此方法的註釋說明),位於FSNamesystem類:
/**
* Called when the NN is in Standby state and the editlog tailer tails the
* OP_ROLLING_UPGRADE_START.
*/
void triggerRollbackCheckpoint() {
setNeedRollbackFsImage(true);
if (standbyCheckpointer != null) {
standbyCheckpointer.triggerRollbackCheckpoint();
}
}
3.此標識繼而在StandbyCheckpointer的doWork方法中被呼叫,程式碼如下:
private void doWork() {
final long checkPeriod = 1000 * checkpointConf.getCheckPeriod();
// Reset checkpoint time so that we don't always checkpoint
// on startup.
lastCheckpointTime = monotonicNow();
while (shouldRun) {
boolean needRollbackCheckpoint = namesystem.isNeedRollbackFsImage();
...
// 通過needCheckpoint標識判斷是否需要進行checkpoint操作
if (needCheckpoint) {
doCheckpoint();
// reset needRollbackCheckpoint to false only when we finish a ckpt
// for rollback image
if (needRollbackCheckpoint
&& namesystem.getFSImage().hasRollbackFSImage()) {
namesystem.setCreatedRollbackImages(true);
namesystem.setNeedRollbackFsImage(false);
}
lastCheckpointTime = now;
...
4.最後在doCheckpoint方法中我們終於找到了另外一處建立rollback映象的操作,
private void doCheckpoint() throws InterruptedException, IOException {
assert canceler != null;
final long txid;
final NameNodeFile imageType;
...
// 如果正處於rollUpgrade狀態,並且沒有建立rollback映象
if (namesystem.isRollingUpgrade()
&& !namesystem.getFSImage().hasRollbackFSImage()) {
// if we will do rolling upgrade but have not created the rollback image
// yet, name this checkpoint as fsimage_rollback
imageType = NameNodeFile.IMAGE_ROLLBACK;
} else {
imageType = NameNodeFile.IMAGE;
}
...
OK,執行完startRollingUpgrade操作,表明HDFS已處於rolling upgrade狀態了.
接著是query命令,query命令查詢的主要目的是NameNode是否已經建立完rollback的映象檔案.程式碼如下:
RollingUpgradeInfo queryRollingUpgrade() throws IOException {
checkSuperuserPrivilege();
checkOperation(OperationCategory.READ);
readLock();
try {
// 如果rollingUpgrade資訊不為空
if (rollingUpgradeInfo != null) {
// 判斷是否已經存在rollback的映象檔案,並設定入rollingUpgrade資訊中
boolean hasRollbackImage = this.getFSImage().hasRollbackFSImage();
rollingUpgradeInfo.setCreatedRollbackImages(hasRollbackImage);
}
// 返回更新過後的rollingUpgradeInfo
return rollingUpgradeInfo;
} finally {
readUnlock();
}
}
最後一個命令是finalize,finalize做的是最終升級確認的事情,此命令的執行意味著Rolling Upgrade過程徹底完成,相關程式碼如下:
RollingUpgradeInfo finalizeRollingUpgrade() throws IOException {
checkSuperuserPrivilege();
checkOperation(OperationCategory.WRITE);
writeLock();
final RollingUpgradeInfo returnInfo;
try {
checkOperation(OperationCategory.WRITE);
if (!isRollingUpgrade()) {
return null;
}
...
if (haEnabled) {
// roll the edit log to make sure the standby NameNode can tail
getFSImage().rollEditLog();
}
getFSImage().updateStorageVersion();
// 將之前建的用於rollback的映象檔案重新命名為當前的映象
getFSImage().renameCheckpoint(NameNodeFile.IMAGE_ROLLBACK,
NameNodeFile.IMAGE);
} finally {
writeUnlock();
}
...
return returnInfo;
}
從上面的程式碼中,我們可以看出finalize動作主要是將rollback映象重新命名為當前fsimage.
綜合上述3個命令的呼叫過程,我們可以看到基本是圍繞著rollback fsimage做相關的操作,過程呼叫簡圖如下:
hdfs namenode下的子命令
hdfs namenode -rollingUpgrade rollback或started命令的本質是NameNode啟動控制相關指令.我們輸入hdfs namenode -help命令可以看到全部的startup option:
$ hdfs namenode -help
Usage: java NameNode [-backup] |
[-checkpoint] |
[-format [-clusterid cid ][-force][-nonInteractive] ] |
[-upgrade [-clusterid cid][-renameReserved<k-v pairs>] ] |
[-upgradeOnly [-clusterid cid][-renameReserved<k-v pairs>] ] |
[-rollback] |
[-rollingUpgrade <rollback|downgrade|started> ] |
[-finalize] |
[-importCheckpoint] |
[-initializeSharedEdits] |
[-bootstrapStandby] |
[-recover [ -force] ] |
[-metadataVersion ] ]
我們輸入的額外引數將會被解析到StartupOption內,rollback和started將分別對應到列舉值RollingUpgradeStartupOption.ROLLBACK和RollingUpgradeStartupOption.STARTED.那麼這2個引數到底會如何影響NameNode的啟動邏輯呢?這才是我們最關心的內容.
通過尋找列舉值RollingUpgradeStartupOption.STARTED的呼叫方,我們可以找到其中一處呼叫場景:
private void updateStorageVersionForRollingUpgrade(final long layoutVersion,
StartupOption startOpt) throws IOException {
// 判斷是否為rolling upgrade方式的啟動
boolean rollingStarted = RollingUpgradeStartupOption.STARTED
.matches(startOpt) && layoutVersion > HdfsConstants
.NAMENODE_LAYOUT_VERSION;
boolean rollingRollback = RollingUpgradeStartupOption.ROLLBACK
.matches(startOpt);
// 對fsImage所在儲存目錄更新版本
if (rollingRollback || rollingStarted) {
fsImage.updateStorageVersion();
}
}
上述方法將在loadFSImage方法中被呼叫,同理我們可以找到rollback啟動引數對應的處理邏輯,程式碼如下:
private boolean loadFSImage(FSNamesystem target, StartupOption startOpt,
MetaRecoveryContext recovery)
throws IOException {
final boolean rollingRollback
= RollingUpgradeStartupOption.ROLLBACK.matches(startOpt);
final EnumSet<NameNodeFile> nnfs;
// 判斷是否為rollback方式的啟動,如果是,則從rollback映象中載入
if (rollingRollback) {
// if it is rollback of rolling upgrade, only load from the rollback image
nnfs = EnumSet.of(NameNodeFile.IMAGE_ROLLBACK);
} else {
// otherwise we can load from both IMAGE and IMAGE_ROLLBACK
nnfs = EnumSet.of(NameNodeFile.IMAGE, NameNodeFile.IMAGE_ROLLBACK);
}
final FSImageStorageInspector inspector = storage
.readAndInspectDirs(nnfs, startOpt);
...
後面還有rollback情況下對應的editlog的處理邏輯,感興趣的同學可自行研究學習.
hdfs datanode下的子命令
最後一個相關命令是hdfs datanode的啟動命令,我們同樣輸入hdfs datanode -h來檢視其可選引數.
$ hdfs datanode -h
Usage: java DataNode [-regular | -rollback]
-regular : Normal DataNode startup (default).
-rollback : Rollback a standard or rolling upgrade.
Refer to HDFS documentation for the difference between standard
and rolling upgrades.
這裡主要關注rollback引數,因為regular其實就是我們平常正常啟動的預設引數.啟動DataNode之後,依次經過initStorage,initBlockPool等方法,最終呼叫到下面的doTransition方法,在這個方法內部,我們可以看到此引數控制的邏輯程式碼:
private void doTransition( DataNode datanode,
StorageDirectory sd,
NamespaceInfo nsInfo,
StartupOption startOpt
) throws IOException {
// 判斷DataNode是否為rollback方式的啟動
if (startOpt == StartupOption.ROLLBACK) {
doRollback(sd, nsInfo); // rollback if applicable
}
readProperties(sd);
checkVersionUpgradable(this.layoutVersion);
assert this.layoutVersion >= HdfsConstants.DATANODE_LAYOUT_VERSION :
"Future version is not allowed";
...
進入doRollback方法:
void doRollback( StorageDirectory sd,
NamespaceInfo nsInfo
) throws IOException {
File prevDir = sd.getPreviousDir();
// This is a regular startup or a post-federation rollback
...
File tmpDir = sd.getRemovedTmp();
assert !tmpDir.exists() : "removed.tmp directory must not exist.";
// rename current to tmp
File curDir = sd.getCurrentDir();
assert curDir.exists() : "Current directory must exist.";
rename(curDir, tmpDir);
// rename previous to current
rename(prevDir, curDir);
// delete tmp dir
deleteDir(tmpDir);
LOG.info("Rollback of " + sd.getRoot() + " is complete");
}
在doRollback方法中,主要做了以下3個關於rollback的步驟:
- 1.重新命名目錄current到removed.tmp.
- 2.重新命名目錄previous到current.
- 3.刪除目錄remove.tmp.
在上面的操作中有一點需要注意:
在DataNode的doRollback操作中,並不會涉及到真實資料的儲存/恢復等操作,其間只是檔案目錄連結指向的修改.
HDFS Rolling Upgrade實際操作
下半部分我們將重點關注HDFS Rolling Upgrade部分的操作.HDFS Rolling Upgrade部分的操作並不是就幾條命令的事情,它還要考慮叢集是否是HA配置的,HA裡又分為是否為federated cluster和非federated cluster.我們知道在federated cluster會存在多個namespace的,也就是說在叢集中會有多對Active-Standby NameNode.這種情況下,我們應該怎麼升級?答案將在後面的內容中揭曉.
說到HDFS的Rolling Upgrade實際操作,如果只有Upgrade的操作,當然不能算是完整的,還應該有Downgrade和Rollback的對應操作.綜合這3類操作,才能算是一個系統的Rolling Upgrade方案.下面將會根據叢集型別(HA或非HA等)以及升級模組(Upgrade或Downgrade等)進行詳細的操作步驟闡述.
Upgrade操作
在HDFS的升級操作過程之前,需要留意部分功能屬性的開啟/關閉的變更.因為可能部分功能屬性在新的釋出版本中是預設被開啟了,直接由老版本升級後可能會造成功能的不可用,對於這種情況,比較穩妥的做法如下:
1.先關閉此功能
2.執行升級過程
3.重新啟用此功能
根據叢集型別,我們可以將Upgrade過程分為如下幾類:
- HA型別
- Federated Clusters
- Non-Federated Clusters
- Non-HA型別
後面Downgrade和Rollingback過程的分類同上.
Upgrade for Non-Federated Clusters
Non-Federated Clusters的意思就是普通HA模式的叢集,一個Active NameNode,一個Standby NameNode.下面是主要的升級步驟:
- 1.Rolling Upgrade前期準備操作
- 1.1 執行hdfs dfsadmin -rollingUpgrade prepare命令,此命令將會建立一個新的fsImage檔案用於rollback.
- 1.2 執行hdfs dfsadmin -rollingUpgrade query檢查rollback映象檔案是否已經建立完畢,如果出現了”Proceed with rolling upgrade”這樣的提示則表明上一步prepare步驟操作完成.
- 2.升級Active/Standby的NameNode,暫且稱之為NN1(Active),NN2(Standby).
- 2.1 Shutdown NN2,準備升級NN2.
- 2.2 執行-rollingUpgrade started命令啟動NN2,使之成為Standby節點.
- 2.3 做一次failover切換,使得NN2成為Active節點,NN1變為Standby節點.
- 2.4 Shutdown NN1節點.
- 2.5 在NN1節點上同樣執行-rollingUpgrade started,使之成為Standby節點.
- 3.升級叢集DataNode節點
- 3.1 選擇少部分DataNode節點(你可以按照DataNode所在的不同機架來篩選).
- 3.1.1 執行hdfs dfsadmin -shutdownDatanode upgrade命令停止選中的DataNode節點,如果你沒有額外設定DataNode的IPC_PORT埠的話,則為預設值50020.
- 3.1.2 執行hdfs dfsadmin -getDatanodeInfo 命令檢查目標命令下線DataNode是否已經停止服務.如果通過這個命令,你還是能得到節點資訊,意味著此節點還未真正被關閉.
- 3.1.3 升級並重啟上步驟中停止的DataNode,這裡的升級意味著你需要在節點上用新版本的安裝包替換掉老的.
- 3.1.4 重複以上步驟,直到將選中集合中剩餘的DataNode節點都升級完畢,如果此部分節點規模足夠大的話,我們最好採用並行的方式執行.
- 3.2 在叢集剩餘其他節點執行3.1步驟所述操作,直到叢集中所有節點都執行完upgrade操作.
- 3.1 選擇少部分DataNode節點(你可以按照DataNode所在的不同機架來篩選).
- 4.Rolling Upgrade操作完成的確認
- 4.1 執行hdfs dfsadmin -rollingUpgrade finalize來確認此次Rolling Upgrade的過程.一旦執行finalize動作,意味著將不允許rollback.
Upgrade for Federated Clusters
Federated Clusters是擁有多namespace的叢集.每個namespace對應一對主備NameNode節點.每個NameNode掌握著一部分的元資料資訊.Federated Clusters的升級過程與非Federated Clusters的升級過程比較相似,升級步驟如下:
- 1.執行-rollingUpgrade prepare命令在每個namespace下.
- 2.升級每個namespace下的Active/Standby節點,過程同上.
- 3.升級每個DataNode節點,過程同上.
- 4.升級過程執行完畢,在每個namespace下執行finalize確認命令.
上述過程其實與非Federated Clusters的升級過程沒有什麼本質區別,只是需要為不同的namespace多重複執行幾遍升級操作.
Upgrade for Non-HA Clusters
前面2類都是HA模式下的升級過程,那麼非HA模式下的升級過程又是怎樣的呢?首先它會有一個明顯的不同點:
在升級的過程中,勢必會存在服務短暫停止的時間,因為NameNode需要重啟,而這段時間並沒有備用節點可選.
當然DataNode還是可以一個個升級的.非HA模式的結構與HA模式相比還是有比較大的區別,首先它不再是Active/Standby結構,取而代之的是一個NameNode(NN),一個SecondaryNameNode(SNN).在這裡SNN的角色並不是備份節點的意思.非HA模式的升級步驟如下(整體過程同上述普通HA模式的4個步驟,不過上述步驟二的過程要略微修改):
- 升級NN和SNN
- 1.停止SNN
- 2.停止並升級NN
- 3.執行-rollingUpgrade started命令啟動NN
- 4.升級並重啟SNN
Downgrade操作
當我們在升級的過程中發現了一些異常,或者說我們覺得升級後的版本並不是我們預期所想要的情況下時,我們可以選擇downgrade(降級)或rollback(回滾).這2個操作可能有人會覺得二者意思比較接近,下面列出其中幾個異同點:
共同點:
- 1.都會將版本退回到升級前的版本
- 2.在Upgrade的finalize動作執行之後,將不允許再執行downgrade和rollback.
不同點:
- 1.Downgrade能支援rollling的方式,可以滾動降級,而rollback需要停止服務一段時間.
- 2.Downgrade過程只會將軟體版本還原成升級前的,會保留使用者現有的資料狀態,而rollback則還會將使用者資料還原成升級前的狀態模式.
在HDFS升級操作過後,並不是所有新版本都是可降級的,如果在新舊版本中NameNode或DataNode的layout version不一致,則downgrade操作將會失敗.下面是HA模式下的downgrade降級步驟:
1.Downgrade降級DataNode
- 1.1 選中部分集合DataNode節點(可以按照機架進行區分)
- 1.1.1 執行hdfs dfsadmin -shutdownDatanode upgrade命令停止其中一個選中的節點.
- 1.1.2 執行hdfs dfsadmin -getDatanodeInfo 命令檢查節點是否完全停止.
- 1.1.3 降級並重啟DataNode.
- 1.1.4 在選中集合內的其他DataNode節點上重複執行上述操作.
- 1.2 在叢集中所有的DataNode節點上重複執行1.1的操作.
- 1.1 選中部分集合DataNode節點(可以按照機架進行區分)
2.Downgrade降級Active NameNode和Standby NameNode
- 2.1 停止並降級NN2.
- 2.2 正常啟動NN2,使之為Standby節點.
- 2.3 觸發failover切換,使得NN2為Active節點,NN1為Standby節點.
- 2.3 停止並降級NN1.
- 2.4 正常啟動NN1,作為Standby節點.
3.Downgrade降級操作的確認
- 3.1 執行hdfs dfsadmin -rollingUpgrade finalize命令進行downgrade操作完畢的確認.
Downgrade與Upgrade在HA模式下操作有一個共同點:
在操作NameNode時,都是先從Standby節點開始操作,等Standby節點升/降結束,做一次切換,使另外一個節點得以進行升/降操作.在全程中,始終保持一個Active節點對外提供服務.
但是如果我們仔細觀察Downgrade的過程,我們可以發現其中NameNode與DataNode的操作步驟被顛倒了一下.在Downgrade操作時,變成了Downgrade DN在前,Downloadgrade NN在後,這樣的操作步驟是有必要的嗎?背後的原因是什麼呢?答案如下:
新的軟體版本釋出一般在協議,API是相容老版本的,如果先降級NN,那麼則會造成DN是新版,NN是舊版,新版DN中的許多協議將會在舊版NN中可能不相容.
所以這裡必須要先降級客戶端的DN,然後再把服務端NN進行降級.看似簡單的一次順序顛倒,背後其實是有更深層的原因的.Federated Cluster和非HA模式下的降級操作與升級操作相對應,照著上面HA模式的downgrade步驟,進行相應操作命令的替換即可.
Rollback
最後一部分的操作是rollback回滾操作.在前小節講述downgrade部分內容的時候已經提到過rollback比較特殊的2點:
- Rollback不支援滾動操作的方式,在操作期間,它需要叢集對外停止提供服務.
- Rollback操作不僅會將軟體版本退回到升級前的版本,還會將使用者資料退回到升級前的狀態.
以下是rollback操作步驟:
- 1.停止所有的NameNode和DataNode節點.
- 2.在所有的節點機器上恢復升級前的軟體版本.
- 3.在NN1節點上執行-rollingUpgrade rollback命令來啟動NN1,將NN1作為Active節點.
- 4.在NN2上執行-bootstrapStandby命令並正常啟動NN2,將NN2作為Standby節點.
- 5.以-rollback引數啟動所有的DataNode.
以上就是本文所要講述的HDFS Rolling Upgrade滾動升級的全部內容了,大家可以與傳統的升級方案做對比,體會二者之間細微的差異.