MapReduce
目錄
一、概述
- 是Hadoop中的一套分佈式的計算框架
- 將整個計算過程拆分爲2個階段:Map階段和Reduce階段
- Map階段負責數據的整理,Reduce階段負責數據的彙總
- Reducer中的迭代器採用的是地址複用機制
- Reducer中的迭代器只能遍歷一次
- 如果Mapper和Reducer的結果類型一致,可以只設置一個
- 如果輸入路徑是一個文件,則MapReduce只處理這個文件;如果輸入的是一個目錄,則處理這個目錄下的所有的文件 --- 注意:如果文件以_開頭,則該文件會被跳過。在MapReduce中,_開頭的文件被認爲是隱藏文件不需要處理
- 在MapReduce中,如果不指定,默認鍵值之間用\t分隔
- 如果Reducer沒有業務邏輯,可以省略Reducer
二、序列化機制
- 在MapReduce中,要求被傳輸的數據必須能夠被序列化
- Hadoop中,序列化機制默認使用AVRO,但是Hadoop對AVRO的序列化機制進行了進一步的封裝,提供了更簡單的序列化機制
- 在Hadoop要想實現序列化,需要實現Writable,重寫其中的方法
- 在Hadoop中,序列化的時候,要求屬性不能爲null
- 案例:按人統計每一個的總流量 - cn.tedu.flow
- 練習:score.txt - 計算每一個人的總成績 - cn.tedu.serialscore
三、分區
- 分區在MapReduce中用於進行數據的分類
- 在MapReduce中,如果不指定,則默認只有1個分區
- 每一個分區都必須對應一個ReduceTask,每一個ReduceTask都會產生一個結果文件
- 在MapReduce對分區進行了編號,編號默認從0開始遞增
- 分區的頂級父類是Partitioner
- 在MapReduce中,默認使用的HashPartitioner
- 案例:分地區統計不同的人花費的總流量 - cn.tedu.partflow
四、排序
- 在MapReduce中,會對鍵做自動的排序 - 自然排序
- 如果自定義一個類產生的對象要想作爲鍵,那麼這個對象必須要允許被排序 - WritableComparable
- 多屬性排序的場景稱之爲二次排序
- 練習:profit2.txt - 先按照月份進行升序,如果月份一樣,則按照業績降序 - cn.tedu.sortprofit
五、合併
- 在MapReduce中,因爲默認只有1個Reduce,所以所有的計算壓力都會落在Reduce上,導致Reduce的計算性能成了整個MapReduce的瓶頸,所以爲了提高計算效率,可以在數據傳輸到Reduce之前先對數據進行一次整合,這個過程稱之爲合併 - Combine
- 合並不會影響最終的計算結果
- 一般而言,Combine和Reduce的邏輯是一致的,只需要在Driver中添加job.setCombinerClass(class);
六、數據本地化策略
- 當JobTracker接收到應用之後,會去訪問NameNode獲取要處理的文件信息
- NameNode將文件信息返回給JobTracker
- JobTracker收到文件信息之後會將文件進行切片(只包含切片信息不包含實際數據),一般習慣上,會將切片和切塊設置的一樣大。每一個切片會分給一個MapTask
- JobTracker會MapTask分發到TaskTracker來執行。在分發的時候,哪一個DataNode上有對應的Block,那麼MapTask就會分發這個節點上 - 數據本地化
- 數據本地化的目的:減少對網絡資源的消耗
七、job的執行流程
- 客戶端提交一個job任務到JobTracker: hadoop jar xxx.jar
- JobTracker收集環境信息:
- 檢測類型是否匹配
- 檢測輸入/輸出路徑是否合法
- JobTracker給job分配一個全局遞增的jobid,然後將jobid返回給客戶端
- 客戶端收到jobid之後,將jar包提交到HDFS上
- 準備執行job任務
- JobTracker會將job任務進行劃分,劃分爲MapTask和ReduceTask,其中MapTask的數量由切片數量決定,ReduceTask的數量由Partitioner/numReduceTask決定
- JobTracker等待TaskTracker的心跳。一般TaskTracker和DataNode會設置爲同一個節點。當TaskTracker發送心跳信息的時候,這個時候JobTracker就會給TaskTracker分配任務。注意:在分配的時候,MapTask符合數據本地化策略(當TaskTracker上有這個數據的時候纔會將MapTask分給它),ReduceTask分配到相對空閒的節點上
- TaskTracker領取到任務之後,到對應的節點下載jar包 - 體現的思想:邏輯移動而數據固定
- TaskTracker會在本節點內啓動JVM子進程執行MapTask或者是ReduceTask - 注意:每一個MapTask或者ReduceTask都會啓動一次JVM子進程
八、Shuffle過程
1.Map端的Shuffle
- 每一個Split會分給一個MapTask來處理
- MapTask默認是對數據進行按行讀取,每讀取一行調用一次map方法
- map方法在處理完一行數據的時候會將數據寫出到緩衝區中
- 每一個MapTask都自帶了一個緩衝區,緩衝區維繫在內存中
- 緩衝區默認大小是100M
- 當緩衝區達到條件的時候,將緩衝區中的數據寫到本地磁盤上,這個過程稱之爲溢寫(Spill),產生的文件稱之爲溢寫文件
- 溢寫之後,後續產生的數據會繼續寫到緩衝區中
- 溢寫過程可能發生不止一次
- 當map將結果放到緩衝區中之後,結果在緩衝區中會進行分區和排序過程;如果指定了Combiner,那麼數據在緩衝區中還會進行合併
- 如果所有數據處理完成,但是有一部分數據在緩衝區中,那麼將緩衝區中的數據沖刷到上一次的溢寫文件中
- 因爲可能會產生多次溢寫,那麼會產生多個溢寫文件,在結果發往Reduce之前,會將多個溢寫文件進行合併(merge),將多個溢寫文件合併成一個結果文件
注意:
- 溢寫過程不一定會發生。如果沒有發生溢寫過程,則將緩衝區中的數據直接沖刷到最後的結果文件中;如果只有一次溢寫,那麼這個溢寫文件就是最後的結果文件
- merge過程也不一定發生
- 如果溢寫文件個數>=3個,那麼在merge過程中,自動進行一次combine(指定了Combiner)
- 在merge過程中,結果會進行整體的分區和排序
- 初始數據量並不能決定溢寫次數
- 閾值默認是0.8 - 即緩衝區的使用達到這個大小的80%的時候,就開始溢寫
- 緩衝區本質上是一個字節數組,而且是一個環形的緩衝區,目的是爲了重複利用緩衝區
2.Reduce端的Shuffle
- ReduceTask啓動fetch線程去MapTask上抓取數據,只抓取當前要處理的分區的數據,將抓取的數據放到文件中,每從一個MapTask中抓取到數據就會產生一個數據文件
- 抓取完數據之後,將這個數據文件再次進行merge
- 在merge過程中,對數據再次進行排序
- merge完成之後,將相同的鍵所對應的值放入一個List集合,然後利用List集合去產生迭代器,這個過程稱之爲分組(group)
- 每一組會調用一次reduce方法
- 每一個ReduceTask會產生一個結果文件
注意:
- 默認fetch線程的數量爲5,表示每一個ReduceTask會啓動5個線程去抓取數據
- fetch線程通過http請求去抓數據
- merge因子默認爲10,表示每10個數據文件進行一次合併,最後合成1個數據文件
- reduce閾值默認爲0.05,表示當有5%的MapTask結束,那麼啓動ReduceTask開始抓取數據
3.Shuffle調優
- 調大緩衝區,實際開發中,一般會將緩衝區設置爲250~400M左右
- 儘量添加Combiner,不是所有場景下都可以添加Combiner
- 調大緩衝區的閾值 - 這種方式不推薦
- 將MapTask的結果進行壓縮 - 這種方式能夠有效的減少網絡的傳輸時間 - 這種方式不算優化,只是在節點資源和網絡資源之間進行取捨
- 增加fetch線程的數量 - 考慮服務器的線程承載量
- 增大merge因子 - 不推薦
- 降低Reduce的閾值 - 不推薦
九、小文件處理
- 危害:
- 存儲:每一個小文件在存儲的時候都會產生一條元數據,如果存儲大量的小文件,會產生大量的元數據,導致NameNode的效率變低;如果小文件數量過多,可能會導致NameNode的內存崩潰
- 計算:每一個小文件都會作爲一個切片來處理,每一個切片要對應一個MapTask,意味着如果產生了大量的小文件,會對應大量的切片,則需要大量的MapTask來處理。導致整個集羣的併發量要增加,如果超過集羣的承載能力導致服務器宕機
- 處理手段:合併(將多個小文件合併成一個大文件、利用一個任務處理多個小文件)、壓縮
- 合併:
- 手動合併:手動編碼、合併工具
- har包 - Hadoop Archive
- CompositeInputFormat - 多源輸入
- har:將多個小文件合併成一個文件,並且達成一個har包