Hadoop的磁碟寫入策略引發的問題
DataNode掛載的磁碟或者DataNode節點掛載多個磁碟,如果存在一些磁碟大小不一樣,資料在落盤時就可能會出現磁碟使用率不均勻的情況,容量較小的盤容易被寫滿,而容量大的盤還剩很多空間。磁碟寫滿後,影響Hadoop叢集的正常工作。國慶第一天,線上叢集就報出了JournalNode掛掉的異常情況,經查是由於2T的磁碟被寫滿,JournalNode無法再寫入資料。當時採取了臨時的措施,刪掉HBase和Hive中不用,佔大量空間的表。磁碟使用率下降一部分後,重新啟動JournalNode。
叢集中每個DataNode都掛載了兩個硬碟,分別為2T和4T的,2T基本都被寫滿,而4T的才50%多。是什麼造成了這種資料落盤時的不均勻情況?本主要文調研了Hadoop的資料兩種寫入磁碟的策略,並分析了兩種策略的主要原始碼實現,最後總結解決此次異常的經驗。
兩種寫入策略
迴圈選取
迴圈選取策略是在hfds1.0中實現的,hdfs2.x預設沿用hfds1.x的方式
hdfs2.0預設沿用hfds1.0的方式,按照迴圈的策略,資料會均勻的落在不同大小的盤上,大磁碟和小磁碟儲存的塊是一樣的,導致小的磁碟最先被寫滿。
可用空間策略
hdfs2.0也提供了另一種策略,將資料優先寫入具有最大可用空間。通過一個概率計算出選擇寫入的磁碟,磁碟剩餘空間大的將會獲得更大的寫入概率,這樣磁碟的使用率就會相對均勻。
兩種方案的對比圖如下,圖來源於連結,能夠很清楚的看出兩種策略的不同。
hdfs3.0提供了一個線上磁碟均衡器diskbalancer ,能在不停機的情況下,對資料進行均衡操作。但是hadoop3.0仍是一個測試版本,因此不可能進行升級。
原始碼分析
迴圈選取策略
迴圈選取的策略很簡單,迴圈掃描整個Volumes,如果availableVolumeSize大於blockSize ,即返回該volume。為了保證每次選擇的起點都不是從頭開始,導致資料寫滿一個盤後再寫另一個盤,使用了一個curVolumes定位器來防止這個問題。
int curVolume = curVolumes[curVolumeIndex] < volumes.size()
? curVolumes[curVolumeIndex] : 0;
int startVolume = curVolume;
long maxAvailable = 0 ;
while (true) {
final V volume = volumes.get(curVolume);
curVolume = (curVolume + 1) % volumes.size();
long availableVolumeSize = volume.getAvailable();
if (availableVolumeSize > blockSize) {
curVolumes[curVolumeIndex] = curVolume;
return volume;
}
if (availableVolumeSize > maxAvailable) {
maxAvailable = availableVolumeSize;
}
if (curVolume == startVolume) {
throw new DiskOutOfSpaceException("Out of space: "
+ "The volume with the most available space (=" + maxAvailable
+ " B) is less than the block size (=" + blockSize + " B).");
}
}
可用空間策略
1、通過計算最大剩餘空間與最小剩餘空間的差值,然後與閾值dfs.datanode.available-space-volume-choosing-policy.balanced-space-threshold
進行對比,預設為10G,如果小於該值,將使用迴圈寫入策略,如果不小於該值,則使用最大可用空間策略。
public boolean areAllVolumesWithinFreeSpaceThreshold() {
long leastAvailable = Long.MAX_VALUE;
long mostAvailable = 0;
for (AvailableSpaceVolumePair volume : volumes) {
leastAvailable = Math.min(leastAvailable, volume.getAvailable());
mostAvailable = Math.max(mostAvailable, volume.getAvailable());
}
return (mostAvailable - leastAvailable) < balancedSpaceThreshold;
}
2、通過與leastAvailable + balancedSpaceThreshold
比較,將volume劃分為兩類集合。一類lowAvailableVolumes相對最小,一類highAvailableVolumes相對最大。
public List<AvailableSpaceVolumePair> getVolumesWithHighAvailableSpace() {
long leastAvailable = getLeastAvailableSpace();
List<AvailableSpaceVolumePair> ret = new ArrayList<AvailableSpaceVolumePair>();
for (AvailableSpaceVolumePair volume : volumes) {
//leastAvailable為所有Volume中容量最小的
if (volume.getAvailable() > leastAvailable + balancedSpaceThreshold) {
ret.add(volume);
}
}
return ret;
}
3、根據dfs.datanode.available-space-volume-choosing-policy.balanced-space-preference-fraction
的大小(預設為0.75f)和lowAvailableVolumes,highAvailableVolumes的大小計算出一個兩類Volumes選取的概率。這裡沒有直接使用0.75f,而是考慮到了兩類Volume的數量的影響,如果highAvailableVolumes的數量大於lowAvailableVolumes,則計算出的Volume選取概率將大於0.75。反之則小。
// 獲得相對最大和相對最小的磁碟集合,將volume劃分為兩類
List<V> highAvailableVolumes = extractVolumesFromPairs(
volumesWithSpaces.getVolumesWithHighAvailableSpace());
List<V> lowAvailableVolumes = extractVolumesFromPairs(
volumesWithSpaces.getVolumesWithLowAvailableSpace());
// 算出一個相對概率
float preferencePercentScaler =
(highAvailableVolumes.size() * balancedPreferencePercent) +
(lowAvailableVolumes.size() * (1 - balancedPreferencePercent));
float scaledPreferencePercent =
(highAvailableVolumes.size() * balancedPreferencePercent) /
preferencePercentScaler;
4、最後隨機生成一個概率與scaledPreferencePercent對比,從而決定從highAvailableVolumes,還是lowAvailableVolumes中選擇Volume。這裡同樣使用了迴圈順序選擇策略。
if (mostAvailableAmongLowVolumes < replicaSize ||
random.nextFloat() < scaledPreferencePercent) {
// 在high volume中迴圈選擇一個
volume = roundRobinPolicyHighAvailable.chooseVolume(
highAvailableVolumes, replicaSize);
} else {
// 在low volume中迴圈選擇一個
volume = roundRobinPolicyLowAvailable.chooseVolume(
lowAvailableVolumes, replicaSize);
}
彙總:程式根據設定的閾值判斷使用迴圈順序策略還是最大可用空間策略,如果使用最大可用空間策略,將所有的Volume分為兩類,根據設定的選取概率和每一類的數量計算出每一類的選取概率,然後在選取到的集合中再使用迴圈順序策略。
解決方法
由於迴圈策略造成磁碟不均的解決方法如下:
1、資料清理:此方法屬於緊急措施:清理掉hdfs中不用的資料
2、資料壓縮:手動壓縮部分資料,對於HBase可使用GZ壓縮方式,能快速有效的降低磁碟使用率
3、資料移盤:手動進行資料的移動,將部分資料由寫滿的盤移動到其它盤中
主要有三步操作:
1、關閉DataNode節點
2、使用mv命令移動資料,要絕對保證移動後的資料相對目錄與移動前一致,如移動前data/1/dfs/dn/current/BP-1788246909-172.23.1.202-1412278461680/current/finalized/subdir0/subdir1/
,移動後為data/5/dfs/dn/current/BP-1788246909-172.23.1.202-1412278461680/current/finalized/subdir0/subdir1/
3、重啟DataNode
可以參考 https://wiki.apache.org/hadoop/FAQ#On_an_individual_data_node.2C_how_do_you_balance_the_blocks_on_the_disk.3F
4、通過上述步驟後,可以選擇切換到可用空間策略上。
總結
經過此次異常情況,我重新梳理了問題的過程,分析薄弱的環節,加強的對Hadoop磁碟的監控,增加對異常的處理手段。同時也對Hadoop的磁碟寫入策略進行了調研,瞭解問題產生的原因,才能更好的解決問題。