1. 程式人生 > >Hadoop的磁碟寫入策略引發的問題

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的磁碟寫入策略進行了調研,瞭解問題產生的原因,才能更好的解決問題。