Hadoop原理與原始碼

簡相傑發表於2021-11-10

hadoop概述

Hadoop 歷史

  1. Hadoop最早起源於Nutch。Nutch的設計目標是一個網路爬蟲引擎,但隨著抓取網頁資料量的增大,Nutch遇到了嚴重的效能擴充套件問題。2003年,2004年穀歌釋出兩篇論文為該問題提供了一個解決方案一個是HDFS的前身GFS,用於海量網頁的儲存;另一個是分散式計算框架MAPERDUCE。Nutch的創始人根據論文的指導用了·兩年的時間實現了HDFS和MapReduce程式碼。並將其從Nutch剝離出來,成為獨立專案Hadoop。2008年Hadoop成為Apache頂級專案。
  2. 早期的Hadoop並非現在大家所熟悉的Hadoop分散式開源軟體,而是指代大資料的一個生態圈,包括很多其他的軟體。在2010前後,Hbase,HIVE,Zookeeper等依次脫離Hadoop專案成為Apache的頂級專案。2011年,Hadoop釋出2.0,在架構上進行了重大更新,引入Yarn框架專注於資源的管理精簡了MapReduce的職責。同時Yarn框架作為一個通用的資源排程和管理模組,同時支援多種其他的程式設計模型,比如最出名的Spark。
  3. 由於HADOOP的版本管理複雜,複雜叢集的部署安裝配置等需要編寫大量的配置檔案然後分發到每一臺節點上,容易出錯效率低下。所以很多公司會在基礎的Hadoop進行商業化後進行再發行。目前Hadoop發行版非常多,有華為發行版、Intel發行版、Cloudera發行版(CDH)等。

image

Hadoop(2.0)的組成

image

HDFS

HDFS的組成

NameNode:是整個HDFS叢集的管理者,

  1. 管理HDFS的名稱空間
  2. 管理副本策略
  3. 管理資料塊在DataNode的位置對映資訊
  4. 與客戶端互動處理來自於客戶端的讀寫操作。

DataNode:實際檔案的儲存者。

  1. 儲存實際的資料塊
  2. 執行資料塊的讀、寫操作
  3. DataNode啟動後向NameNode註冊並每6小時向NameNode上報所有的塊資訊
  4. DataNode的心跳是每3秒一次,心跳返回結果帶有NameNode給該DataNode的命令,例如:複製資料到另一臺機器;刪除某個資料塊。
  5. 若NameNode超過10分鐘沒有收到某個DataNode的心跳,則認為該節點不可用。

客戶端

  1. HDFS提供的工具包,面向開發者,封裝了對HDFS的操作呼叫。
  2. 負責檔案切分:檔案上傳到HDFS時,Client負責與NameNode和DataNode互動,將檔案切分為一個一個的block進行上傳。

Secondary NameNode:輔助NameNode,分擔其工作量。

  1. 定期合併Fsimage和Edits,並推送給NameNode。
  2. 輔助恢復NameNode。
  3. 並非NameNode的熱備。當NameNode掛掉的時候,它並不能馬上替換NameNode並提供服務

HDFS讀寫流程

HDFS上傳檔案

  1. client向NameNode請求上傳檔案,NameNode進行合規性檢測,並建立相應的目錄後設資料。並返回是否可以上傳。
  2. client將檔案切分,再次詢問NameNode第一個Block需要上傳到哪幾個DataNode服務端上。NameNode 返回 3 個 DataNode 節點,分別為 dn1、dn2、dn3。
  3. client將第一個block資料上傳給dn1,dn1 收到請求會繼續呼叫dn2,然後 dn2 呼叫 dn3,將這個通訊管道建立完成。
  4. client開始往 dn1 上傳第一個 Block(先從磁碟讀取資料放到一個本地記憶體快取),以 Packet 為單位,dn1 收到一個 Packet 就會傳給 dn2,dn2 傳給 dn3。
  5. 當一個Block傳輸完成之後,客戶端再次請求NameNode上傳第二個Block,然後重複1~4步驟。

image

NameNode節點選擇策略-節點距離計算

  1. 如果Client與HADOOP在同一個叢集,則NameNode會選擇距離待上傳資料最近距離的DataNode接收資料。節點距離:兩個節點到達最近的共同祖先的距離總和。
  2. 如果Client與HADOOP不在同一個叢集,則NameNode隨機選一個機架上的一個節點。第二個副本在另一個機架的隨機的一個節點,第三個副本在第二個副本所在的機架的隨機節點。

HDFS讀流程

  1. client向NameNode請求下載檔案,NameNode返回檔案塊所在的DataNode地址。
  2. client根據節點距離挑選一臺最近的DataNode伺服器,然後開始讀取資料。DataNode以Packet來傳輸資料。
  3. client接收到一個packet資料後,並進行校驗。校驗通過後,將packet寫入目標檔案,再請求第二個packet。整個過程為序列的過程。(因為IO本身就是速度最慢的流程)
  4. 讀取資料的過程中,如果client端與dn資料結點通訊時出現錯誤,則嘗試連線包含此資料塊的下一個dn資料結點。失敗的dn資料結點將被client記錄,並且以後不再連線。
    image

SecondaryNameNode

NameNode是機器的檔案管理容易造成單點讀寫效能問題與資料儲存安全問題。

SecondaryNameNode與Name協助解決讀寫效能問題:NameNode的資料既儲存在記憶體中又儲存在磁碟中

  1. Fsimage檔案:HDFS檔案系統後設資料的一個永久性的檢查點,其中包含HDFS檔案系統的所有目錄和檔案inode的序列化資訊。
  2. Edits檔案:存放HDFS檔案系統的所有更新操作的路徑,檔案系統客戶端執行的所有寫操作首先會被記錄到Edits檔案中。Edits檔案只進行追加操作,效率很高。每當後設資料有更新或者新增後設資料時,修改記憶體中的後設資料並追加到Edits中。
  3. NameNode啟動的時候都會將Fsimage檔案讀入記憶體,載入Edits裡面的更新操作,保證記憶體中的後設資料資訊是最新的、同步的。
  4. 長時間新增資料到 Edits 中,會導致該檔案資料過大,效率降低,而且一旦斷電,恢復後設資料需要的時間過長。因此2NN,專門用於FsImage和Edits的合併。

SecondaryNameNode 工作機制

  1. NameNode啟動後,會建立FsImage和Edits檔案。如果不是第一次啟動,則直接載入FsImage和Edits檔案。

    1. fsimage_0000000000000000002 檔案最新的FsImage。
    2. edits_inprogress_0000000000000000003 正在進行中的edits。
    3. seen_txid 是一個txt的文字檔案,記錄是最新的edits_inprogress檔案末尾的數字。
  2. Secondary NameNode 工作

    1. 2NN詢問NN是需要CheckPoint。
    2. NN將現在進行中的edits檔案和最新的fsimage檔案拷貝到2NN並更新seen_txid裡的數字,然後重新生成edits檔案。
    3. 2NN載入編輯日誌和映象檔案到記憶體,併合並生成新的映象檔案fsimage.chkpoint。然後拷貝回NN。NN將fsimage.chkpoint重新命名為fsImage完成一次滾寫。

image

HDFS總結

  1. 優點

    1. 高容錯性:資料自動儲存多個副本,通過增加副本的形式,提高容錯性。某一個副本丟失後,可以自動恢復。
    2. 適合處理大資料:能夠處理的資料規模達到GB,TB甚至PB級別,檔案數量可以達到百萬規模以上。
    3. 可構建在廉價的機器上:通過多副本機制,提高可靠性。
    
  1. 缺點

     1. 不適合低時延的資料訪問
     2. 無法高效的對大量小檔案進行儲存,大量的小檔案會佔用NameNode大量的記憶體來儲存檔案目錄和塊資訊,同時小檔案的定址時間會超過讀取時間,它違反了HDFS的設計目標
     3. 不支援併發寫入,檔案隨機修改。僅支援資料追加的。
    

DataNode與NameNode原始碼導讀

程式碼閱讀前的準備工作:Hadoop Rpc框架指南

Rpc 協議


public interface MyInterface {
    Object versionID = 1;

    boolean demo();

}

Rpc provider


public class MyHadoopServer implements MyInterface {
    @Override
    public boolean demo() {
        return false;
    }

    public static void main(String[] args) {
        Server server = new RPC.Builder(new Configuration())
                .setBindAddress("localhost")
                .setPort(8888)
                .setProtocol(MyInterface.class)
                .setInstance(new MyHadoopServer())
                .build();

        server.start();
    }
}

Rpc consuemr

public class MyHadoopClient {

    public static void main(String[] args) throws Exception {
        MyInterface client = RPC.getProxy(
                MyInterface.class,
                MyInterface.versionID,
                new InetSocketAddress("localhost", 8888),
                new Configuration());
        
        client.demo();
    }
    
}

NameNode 啟動原始碼

  1. 啟動9870埠服務
  2. 載入映象檔案和編輯日誌 
  3. 初始化NN的RPC服務端:用於接收DataNode的RPC請求
  4. NN啟動資源檢測
  5. NN對心跳超時判斷(啟動一個執行緒去判斷DataNode是否超時)

    1. HDFS預設DataNode掉線容忍時間未 timeout = 2 heartbeat.recheck.interval + 10 dfs.heartbeat.interval(2*5+30)超過這個時間會被認為DataNode超時

image

DataNode 啟動原始碼

工作流程

image

原始碼圖示

image

MapReduce

MapReduce示例

  • 需求:有一個大小為300M的存文字檔案。統計其每一個字母出現的總次數。要求:[a-p]一個結果檔案,[q-z]一個結果檔案。
  • 實現:image

    public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
      private Text outK = new Text();
      private IntWritable outV = new IntWritable(1);
    
      @Override
      protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
    
          // 1 獲取一行
          String line = value.toString();
    
          // 2 切割
          String[] words = line.split(" ");
    
          // 3 迴圈寫出
          for (String word : words) {
              // 封裝outk
              outK.set(word);
    
              // 寫出
              context.write(outK, outV);
          }
      }
    }
    
public class WordCountReducer extends Reducer<Text, IntWritable,Text,IntWritable> {
    private IntWritable outV = new IntWritable();

    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {

        int sum = 0;
        
        // 累加
        for (IntWritable value : values) {
            sum += value.get();
        }

        outV.set(sum);

        // 寫出
        context.write(key,outV);
    }
}

切片規則決定MapTask的數量。

  1. MapReduce將資料的讀取抽象為一個InputFormat。常用的FileInputFormat是針對檔案讀取的一個具體實現。
  2. FileInputFormat的預設分片規則為:將待處理的資料檔案進行邏輯分片,每128M為一個資料切片。一個資料切片交給一個MapTask進行並行處理。
  3. 分片數量過大,開啟過多的MapTask浪費資源。分片數量過小,MapTask階段處理慢。

    ReduceTask的數量需要手工指定。

  4. 在Map階段需要對每一個輸出資料會通過分割槽演算法計算分割槽。
  5. 若ReduceTask=0,則表示沒有Reduce階段,輸出檔案個數和Map個數一致。
  6. 若ReduceTask=1,所有輸出檔案為一個。
  7. ReduceTask數量要大於分割槽的結果不同值的數,否則資料無法被消費會產生異常。
  8. 若ReduceTask的數量大於分割槽數量,則會有部分reduceTask處於閒置狀態。

MapReduce詳細的工作流程。

image

Map階段

  • read階段:通過RecordReader從InputFormat分片中將讀取資料並將資料解析成一個個key/value。
  • map階段:使用者自定義的Mapper.map方法執行。將輸入的key/value轉為輸出的key/value。
  • collect階段:接收輸出Key/Value 並呼叫分割槽演算法,將輸出資料寫入對應的分割槽的環形記憶體快取區中。

    • spill階段:當記憶體快取區的使用率超過一定的閾值後,將觸發溢寫執行緒。該執行緒現在記憶體中進行快速排序,然後將資料溢寫到磁碟上。
  • Combine階段:當所有資料處理完成後,MapTask對所有臨時檔案進行一次合併,以確保最終只會生成一個資料檔案。

image

Reduce階段

  • Copy階段:ReduceTask從所有的MapTask中拷貝同一分割槽的資料,每一個ReduceTask負責處理一個分割槽,互不影響。如果檔案大小超過一定的閾值,則溢寫到磁碟上,否則儲存在記憶體中。
  • Merge階段:ReduceTask將所有相同分割槽的資料合併成一個大檔案,
  • sort階段:將合併後的大檔案進行歸併排序。由於mapTask本身保證了分割槽內區內有序,因此ReduceTask只需要對所有資料進行一次歸併排序即可。
  • reduce階段:執行使用者的reduce方法,並將結果寫到HDFS。

iamge

MapReduce優缺點

優點

  1. 實現簡單,封裝度高。
  2. 擴充套件性強:可以快速增加機器來擴充套件它的計算能力。
  3. 高容錯性:當某個節點掛掉,它會自動將任務轉移到領一個節點執行,中間不需要人工參與。
  4. 適合PB級別以上海量資料的離線處理。

缺點

  1. 不擅長實時計算。
  2. 不擅長流式計算:MapReduce的輸入資料集是靜態的。
  3. 重IO:每個 MapReduce 作業的輸出結果都會寫入到磁碟,會造成大量的磁碟IO。

MapTask原始碼導讀

  1. MapTask.run方法是MapTask的入口.

    1. 讀取配置,初始化MapTask生成jobId
    2. 判斷使用的api選擇使用runNewMapper或者runOldMapper,並執行。
    3. MapTask執行結束,做一些清理工作。
  2. runNewMapper

    1. 例項化預設的inputFormat,計算切片。根據設定的reduceTask數量例項化輸出物件。例項化輸入物件。
    2. mapper.run(mapperContext)執行

      1. 迴圈確認每組kv,執行使用者的map邏輯。
      2. map方法中呼叫collector.collect方法。將資料寫入環形緩衝區。
    3. output.close(mapperContext)執行最終呼叫MapTask的close方法

      1. 呼叫MapTask的flush方法。

        1. sortAndSpill 記憶體排序,並溢寫到檔案。每一個分割槽一個臨時檔案。區內有序
        2. mergeParts 歸併排序,將多個臨時檔案合併成一個檔案。
      2. 呼叫MapTask的close方法,該方法是一個空方法。

ReduceTask原始碼導讀:其入口為RecuceTask的run方法。

  1. 首先初始化copy,sort,reduce的狀態器。
  2. initialize初始化outputformat為TextOutputFormat。
  3. shuffleConsumerPlugin.init(shuffleContext)方法執行。初始化inMemoryMerger和onDiskMerger。
  4. shuffleConsumerPlugin.run();

    1. 建立Fetcher抓取資料,資料抓取完成後,將狀態切為sort
    2. merger.close();裡的finanlMerge執行,將記憶體中的資料和磁碟內的資料合併。
  5. 將狀態切為reduce。runNewReducer。使用者自定義的Reduce方法執行。

    1. 使用者自定義的reduce方法執行,並呼叫context.write方法寫資料。最終呼叫TextOutputFormat的write方法。

      1. 先寫key
      2. 再寫value
      3. 最後寫入一個換行

Yarn

Yarn組成

  1. ResourceManager(RM):全域性資源的管理者

    1. 由兩部分組成:一個是可插拔式的排程Scheduler,一個是ApplicationManager
    2. Scheduler :一個存粹的排程器,不負責應用程式的監控
    3. ApplicationManager:主要負責接收job的提交請求,為應用分配第一個Container來執行ApplicationMaster,還有就是負責監控ApplicationMaster,在遇到失敗時重啟ApplicationMaster執行的Container
    4. NodeManager(NM)
    5. 接收ResourceManager的請求,分配Container給應用的某個任務
    6. 和ResourceManager交換資訊以確保整個叢集平穩執行。ResourceManager就是通過收集每個NodeManager的報告資訊來追蹤整個叢集健康狀態的,而NodeManager負責監控自身的健康狀態。
    7. 管理每個Container的生命週期
    8. 管理每個節點上的日誌
    9. 執行Yarn上面應用的一些額外的服務,比如MapReduce的shuffle過程
    10. Container

      1. 是Yarn框架的計算單元,是具體執行應用task
      2. 是一組分配的系統資源記憶體,cpu,磁碟,網路等
      3. 每一個應用程式從ApplicationMaster開始,它本身就是一個container(第0個),一旦啟動,ApplicationMaster就會根據任務需求與Resourcemanager協商更多的container,在執行過程中,可以動態釋放和申請container。
    11. ApplicationMaster(AM)

      1. ApplicationMaster負責與scheduler協商合適的container,跟蹤應用程式的狀態,以及監控它們的進度
      2. 每個應用程式都有自己的ApplicationMaster,負責與ResourceManager協商資源(container)和NodeManager協同工作來執行和監控任務
      3.  當一個ApplicationMaster啟動後,會週期性的向resourcemanager傳送心跳報告來確認其健康和所需的資源情況

Yarn執行過程

  1. 客戶端程式向ResourceManager提交應用並請求一個ApplicationMaster例項,ResourceManager在應答中給出一個applicationID
  2. ResourceManager找到可以執行一個Container的NodeManager,並在這個Container中啟動ApplicationMaster例項
  3. ApplicationMaster向ResourceManager進行註冊,註冊之後客戶端就可以查詢ResourceManager獲得自己ApplicationMaster的詳細資訊
  4. 在平常的操作過程中,ApplicationMaster根據resource-request協議向ResourceManager傳送resource-request請求,ResourceManager會根據排程策略儘可能最優的為ApplicationMaster分配container資源,作為資源請求的應答發給ApplicationMaster
  5. ApplicationMaster通過向NodeManager傳送container-launch-specification資訊來啟動Container
  6. 應用程式的程式碼在啟動的Container中執行,並把執行的進度、狀態等資訊通過application-specific協議傳送給ApplicationMaster,隨著作業的執行,ApplicationMaster將心跳和進度資訊發給ResourceManager,在這些心跳資訊中,ApplicationMaster還可以請求和釋放一些container。
  7. 在應用程式執行期間,提交應用的客戶端主動和ApplicationMaster交流獲得應用的執行狀態、進度更新等資訊,交流的協議也是application-specific協議

Yarn排程器與排程演算法

Yarn Scheduler 策略

  1. FIFO:將所有的Applications放到佇列中,先按照作業的優先順序高低、再按照到達時間的先後,為每個app分配資源

    1. 優點:簡單,不需要配置
    2. 缺點:不適合共享叢集
  2. Capacity Scheduler:用於一個叢集中執行多個Application的情況,目標是最大化吞吐量和叢集利用率

    1. CapacityScheduler允許將整個叢集的資源分成多個部分,每個組織使用其中的一部分,即每個組織有一個專門的佇列,每個組織的佇列還可以進一步劃分成層次結構(Hierarchical Queues),從而允許組織內部的不同使用者組的使用。每一個佇列指定可以使用的資源範圍.
    2. 每一個佇列內部,按照FIFO的方式排程Application.當某個佇列的資源空閒時,可以將它的剩餘資源共享給其他佇列.

image

Yarn原始碼

image

參考

Hadoop(2.0)的組成

image

相關文章