Hadoop MR Shuffle

shuffle 的過程是在 MapTask 之後 reducerTask 之前的這麼一段對數據處理傳遞的過程

分區

mapTask 執行數據操作後, 將輸出數據存儲到 環形緩衝區 中, 當環形緩衝區內數據量達到最大量(默認 100M)的 80%時, 將內部數據溢寫到磁盤中存儲,然後環形緩衝區再進行反向寫入剩餘數據; 寫入磁盤時會對數據進行分區,默認分區爲 0(不分區),分區數,會影響最終reduceTask 寫入的文件數

使用場景-希望將分析後的數據根據不同的屬性, 寫到多個文件中

例如: 分析 2020 年疫情, 各省市區疫情情況, 將分析結果按照省份分別寫入不同的文件

  • 自定義分區器

    驅動類設置自定義分區器生效

            job.setNumReduceTasks(3);//reduceTasks 數不能小於分區數
            job.setPartitionerClass(CustomerPartitioner.class;
    

    實現自定義分區器

    1. 繼承Partitioner抽象類
    2. 指定泛型 key- value 爲 mapper 輸出 key-value 類型
    3. 實現方法 getPartition 內部是分區邏輯
    /**
     * <h3>study-all</h3>
     *
     * <p>自定義分區器實現</p>
     *
     * @Author zcz
     * @Date 2020-04-05 11:29
     */
    public class CustomerPartitioner extends Partitioner<Comparable, Writable> {
        @Override
        public int getPartition(WritableComparable key, Writable value, int i) {
    
            job.setNumReduceTasks();
            job.setPartitionerClass();
            //key 是 mapper 的輸出 key
            //value 是 mapper 的輸出 value
            //根據具體需求完成分區,
            //返回值是分區位置, 例如三個分區0,1,2   邏輯上應該是分區 0 的 return 0; 應該是分區 1 的 return 1; 應該是分區 2 的 return 2;
            return 0;
        }
    }
    

排序

  • 分類

    部分排序: 各分區進行排序

    全排序: 保證最後輸出只有一個文件, 並且內部是有順序的

    輔助排序(分組排序):在 reduce 階段, 對 key 進行一定規則的分組排序

    二次排序: 排序方法 compareTo 中的判斷條件爲兩個以上

    MapTask和 reduceTask 都會對輸入數據的 key 進行排序, 默認使用字典順序排序, 算法使用快排

  • MapReduce 過程中的排序

    上面分區中描述到, mapTask將輸出數據寫入到 環形緩衝區 中, 當緩衝區中的數據達到閾值後, 會對緩衝區內的數據進行分區計算, 並進行分區內排序(默認字典順序快排), 然後將緩衝區內經過分區計算的數據溢寫到磁盤中存儲, 在 mapper 階段會有多個 MapTask執行, 所以會有多組分區, (可選操作)當所有 MapTask 執行完後, 將所有mapTask 的分區, 按各個分區規則進行歸併排序

    mapTask執行結束後, reduceTask 階段會到 mapTask 下讀取各組分區數據到reduceTask 內存中, 當讀入的文件大小到達一定的閾值後, 會將文件溢寫到磁盤中, 當磁盤中的文件達到一定數量, 會對數據進行按分區歸併排序, 當所有數據拷貝完後,reduceTask 會將內存中和磁盤中的數據進行按分區歸併排序

  • 自定義排序

    首先說在排序中都是對輸入或輸出的 key 進行排序的, 所有 key 都需要是可排序的, 實現 WritableComparable接口, 實現 compareTo 方法, 就可以排序了

    /**
     * <h3>study-all</h3>
     * <p>自定義可排序類</p>
     *
     * @Author zcz
     * @Date 2020-04-05 18:51
     */
    public class CustomerCompareBean implements WritableComparable<CustomerCompareBean> {
        private Integer column1;
        private Integer column2;
    
        public CustomerCompareBean(Integer column1, Integer column2) {
            this.column1 = column1;
            this.column2 = column2;
        }
    
        public CustomerCompareBean() {
        }
    
        public Integer getColumn1() {
            return column1;
        }
    
        public void setColumn1(Integer column1) {
            this.column1 = column1;
        }
    
        public Integer getColumn2() {
            return column2;
        }
    
        public void setColumn2(Integer column2) {
            this.column2 = column2;
        }
    
        @Override
        public void write(DataOutput out) throws IOException {
            out.writeInt(column1);
            out.writeInt(column2);
        }
    
        @Override
        public void readFields(DataInput in) throws IOException {
            this.column1 = in.readInt();
            this.column2 = in.readInt();
        }
        
    
    		/**
         * 實現比較方法可進行排序
         * */
        @Override
        public int compareTo(CustomerCompareBean o) {
            
            return (this.column1 != o.getColumn1())?
                    (this.column1 - o.getColumn1()):
                    (this.column2 - o.getColumn2());
        }
    }
    
  • 全排序: 只需要 mapper 輸出 key 和 reducer 讀取 key 可排序

  • 分區排序: 在全排序的基礎上, 增加分區設置後可分區排序(自定義分區見分區章節)

  • 分組排序

    • 自定義分組排序類CustomerGroupingComparator繼承 WritableComparator,

    • 重寫compare 方法

      @Override
      public int compare(WritableComparable a, WritableComparable b) {
      		// 比較的業務邏輯
      		return result;
      }
      
    • 重寫無參構造方法, 調用 super(OrderBean.class, , true)

      protected OrderGroupingComparator() {
      		super(OrderBean.class, true);
      }
      

      例:

    public class CustomerGroupingComparator extends WritableComparator {
    	//重寫無參構造
    	protected OrderGroupingComparator() {
    		super(OrderBean.class, true);
    	}
    
    	@Override
    	public int compare(WritableComparable a, WritableComparable b) {
    
    		//a與b的比較邏輯
    
    		return result;
    	}
    }
    

    Driver 設置分組排序

    job.setGroupingComparatorClass(OrderGroupingComparator.class);
    

Combiner 合併

  • Combiner 不是必須的

  • Combiner 本質就是 reducer的子類

  • Combiner 和 reducer 的區別是執行位置:

    Combiner 在每個 MapTask 後面執行

    reducer 是在所有 MapTask 後面執行

  • Combiner 的意義是爲每個 MapTask 的輸出進行彙總, 減少reducerTask讀取數據的網絡傳輸量

  • 使用 Combiner 不能影響最終結果

  • Combiner 的key-value輸入類型是Mapper的輸出類型, Combiner 的輸出類型是 Reducer 的輸出類型

自定義 Combiner

/**
 * <h3>study-all</h3>
 *
 * <p>自定義 Combiner</p>
 *
 * @Author zcz
 * @Date 2020-04-05 19:27
 */
public class CustomerCombiner extends Reducer<Text, IntWritable, Text, IntWritable> {
    private IntWritable value = new IntWritable();

    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
        int sum = 0;
        for (IntWritable i : values) {
            sum = sum+ i.get();
        }
        value.set(sum);
        context.write(key, value);

    }
}

Driver 中設置啓動 Combiner

conf.setConbinerClass(CustomerCombiner.class);

Shuffle 過程總結

  1. MapTask 對數據進行讀取, 並操作數據對數據進行整理後, 將整理後的數據調用 Partitioner 進行數據分區, 將分區後的數據寫入環形緩衝區環形緩衝區內對數據分區排序, 並對區內部數據進行以 key 值排序(快排)
  2. 當數據緩衝區內數據達到閾值(100M*80%), 如果設置了 Combiner, 先對分區數據執行 Combiner 合併, 然後將各分區合併後的數據溢寫到磁盤(分區數據, 區內有序)
  3. 如果有需要可以對寫入到磁盤的分區數據進行 Combiner 操作, 並執行歸併排序後再寫入磁盤
  4. 將分區數據的元信息寫到內存索引數據結構SpillRecord中,其中每個分區的元信息包括在臨時文件中的偏移量、壓縮前數據大小和壓縮後數據大小。如果當前內存索引大小超過1MB,則將內存索引寫到文件output/spillN.out.index中
  5. 如果設置了 Combiner , mapTask對所有數據進行一次 Combiner 合併, 保證每個 mapTask 只生成一個文件
  6. mapTask 全部結束後開始啓動執行 reducerTask
  7. reducerTask將 mapTask生成的數據拷貝到 reducerTask 執行節點內存中,如果拷貝數據超過閾值, 將數據寫到磁盤中
  8. reducerTask 將內存中和磁盤中的數據進行一次合併, 防止內存中數據過多, 和防止磁盤中文件過多
  9. reducerTask 將數據進行一次全局排序(歸併)
  10. shuffle 過程結束, reducer 將處理數據寫到 HDFS

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章