Hadoop(四) MapReduce 原理

目錄

 MapReduce的核心思想

Hadoop MapReduce

InputFormat

TextInputFormat

KeyValueTextInputFormat

NLineInputFormat

自定義InputFormat

Job提交過程

FileInputFormat切片大小的參數配置

獲取切片信息API

MapTask的個數

Partitioner

默認是HashPartitioner

 

 自定義Partitioner

排序

Combiner

Reduce

Reduce的過程

OutputFormat

TextOutputFormat

SequenceFileOutputFormat

自定義OutputFormat

MapReduce開發

輸入數據接口InputFormat

邏輯處理接口:Mapper

Partitioner分區

Comparable排序

Combiner合併

reduce端分組:Groupingcomparator

邏輯處理接口:Reducer

輸出數據接口:OutputFormat


 MapReduce的核心思想

上面的圖片展示了四個階段:Input, Map, Reduce, Output,這四個階段就是MapReduce的核心過程

Input/Output只是任何系統都有的階段,現只看Map/Reduce

Map階段將輸入的文件按照業務規則解析爲一個個的k-v對,Reduce階段在Map計算輸出的k-v對上進行繼續計算,

比方說彙總這些k-v對,計算出有多少個這樣的k-v對。它所做的事就是把一件分爲多個階段來做,把一個大的問題分成很多的

小問題,可以將mapreduce看作是一種設計模式,一種解決解決某些場景問題的編程方法。比如在python中就提供了mapreuce

的編程模式

# map/filter 的使用
# 對傳入的參數,每一個應用傳入的運算
def fun1(x):
    return x*2 + 5
list1 = [4, 8, 9, 1, 3]
list2 = map(fun1, list1)

# reduce的應用,reduce函數需要單獨導入
# 化簡函數
from functools import reduce

f = lambda x, y : x + y

total = reduce(f, list2)

如果說這個問題不是很大,那麼利用這個模式在一臺計算機上就可以解決這個問題了。

但如果這個如果很大,我們還是要使用這個mapreduce模式,那麼這時我們就需要使用很多臺計算機去解決這個問題了,

這就要使用到Hadoop MapReduce(分佈式的計算框架)

 

Hadoop MapReduce

Hadoop MapReduce是一個分佈式的計算框架,它的核心功能還是利用mapreduce的編程模式計算解決計算的問題。

但是因爲它是分佈式的,因而引入很多的輔助功能去解決因爲分佈式所帶來的問題,比如文件的存儲,網絡I/O通信等。

具體過程如下

InputFormat

public abstract class InputFormat<K, V> {

  /** 
   * Logically split the set of input files for the job.  
   * 
   * <p>Each {@link InputSplit} is then assigned to an individual {@link Mapper}
   * for processing.</p>
   *
   * <p><i>Note</i>: The split is a <i>logical</i> split of the inputs and the
   * input files are not physically split into chunks. For e.g. a split could
   * be <i>&lt;input-file-path, start, offset&gt;</i> tuple. The InputFormat
   * also creates the {@link RecordReader} to read the {@link InputSplit}.
   * 
   * @param context job configuration.
   * @return an array of {@link InputSplit}s for the job.
   */
  public abstract 
    List<InputSplit> getSplits(JobContext context
                               ) throws IOException, InterruptedException;
  
  /**
   * Create a record reader for a given split. The framework will call
   * {@link RecordReader#initialize(InputSplit, TaskAttemptContext)} before
   * the split is used.
   * @param split the split to be read
   * @param context the information about the task
   * @return a new record reader
   * @throws IOException
   * @throws InterruptedException
   */
  public abstract 
    RecordReader<K,V> createRecordReader(InputSplit split,
                                         TaskAttemptContext context
                                        ) throws IOException, 
                                                 InterruptedException;

}

 InputFormat 常見的接口實現類

TextInputFormat、KeyValueTextInputFormat、NLineInputFormat、CombineTextInputFormat和自定義InputFormat等

 

TextInputFormat

TextInputFormat 是默認的 InputFormat,每條記錄是一行輸入。鍵是LongWritable 類型,存儲該行在整個文件中的字節偏移量。 值是這行的內容,不包括任何行終止符(換行符合回車符),它被打包成一個 Text 對象

 

KeyValueTextInputFormat

每一行均爲一條記錄, 被分隔符(缺省是tab(\t))分割爲key(Text),value(Text)。

可以通過 mapreduce.input.keyvaluelinerecordreader.key.value,separator屬性來設定分隔符。 它的默認值是一個製表符

 

NLineInputFormat

通過 TextInputFormat 和 KeyValueTextInputFormat,每個 Mapper 收到的輸入行數不同。行數取決於輸入分片的大小和行的長度。 如果希望 Mapper 收到固定行數的輸入,需要將 NLineInputFormat 作爲 InputFormat。與 TextInputFormat 一樣, 鍵是文件中行的字節偏移量,值是行本身。N 是每個 Mapper 收到的輸入行數。N 設置爲1(默認值)時,每個 Mapper 正好收到一行輸入。 mapreduce.input.lineinputformat.linespermap 屬性實現 N 值的設定

 

自定義InputFormat

1-自定義一個類繼承FileInputFormat

2-改寫RecordReader,實現一次讀取一個完整文件封裝爲KV

3-在輸出時使用SequenceFileOutPutFormat輸出合併文件

 

Job提交過程

org.apache.hadoop.mapreduce.Job

      waitCompletion(true)

      submit() --> new JobSubmitter()

                      submitJobInternal()

                          //創建提交數據數據的臨時目錄

                          JobSubmissionFiles.getStagingDir(cluster, conf);

                                   
                        // 獲取jobid,並創建job路徑
                        JobID jobId = submitClient.getNewJobID();
                        job.setJobID(jobId);
                        Path submitJobDir = new Path(jobStagingArea, jobId.toString());
                        
                        //
                        copyAndConfigureFiles(job, submitJobDir);

                        // 計算切片,並形成邏輯切分計劃
                        int maps = writeSplits(job, submitJobDir);
                          // FileInputFormat
                          List<InputSplit> splits = input.getSplits(job);
                          //計算切片大小
                          long splitSize = computeSplitSize(blockSize, minSize, maxSize);
                                           Math.max(minSize, Math.min(maxSize, blockSize));

                        //將job信息(切片計劃xml)寫入提交目錄
                        writeConf(conf, submitJobFile);
                                                    
                        // 向yarn提交job
                        submitClient.submitJob(jobId, submitJobDir.toString(), job.getCredentials());

FileInputFormat切片大小的參數配置

在FileInputFormat中,計算切片大小的邏輯:Math.max(minSize, Math.min(maxSize, blockSize));

切片主要由這幾個值來運算決定

mapreduce.input.fileinputformat.split.minsize=1 默認值爲1

mapreduce.input.fileinputformat.split.maxsize= Long.MAXValue 默認值Long.MAXValue

因此,默認情況下,切片大小=blocksize。

maxsize(切片最大值):參數如果調得比blocksize小,則會讓切片變小,而且就等於配置的這個參數的值。

minsize(切片最小值):參數調的比blockSize大,則可以讓切片變得比blocksize還大。

FileInputFormat在切分文件時是按文件來切分的,因此不論這個文件的大小,該文件都至少有一個split,但文件小時極易產生

小文件問題這些就會產生大量很小的split,可以通過預處理將小文件合併爲大文件或使用CombineTextInputFormat

 

獲取切片信息API

// 根據文件類型獲取切片信息,可用於在map中判斷讀入的是哪個文件
FileSplit inputSplit = (FileSplit) context.getInputSplit();
// 獲取切片的文件名稱
String name = inputSplit.getPath().getName();

MapTask的個數

MapTask的個數由split的個數決定,因此要特別注意小文件問題

 

Partitioner

默認是HashPartitioner

public class HashPartitioner<K2, V2> implements Partitioner<K2, V2> {
    public HashPartitioner() {
    }

    public void configure(JobConf job) {
    }

    public int getPartition(K2 key, V2 value, int numReduceTasks) {
        return (key.hashCode() & 2147483647) % numReduceTasks;
    }
}

 

 自定義Partitioner

 */
public class YearPartitioner extends Partitioner<CompKey, NullWritable> {
    @Override
    /**
     * @param numPartitions the total number of partitions.
     */
    public int getPartition(CompKey compKey, NullWritable nullWritable, int numPartitions) {
        int year = compKey.getYear();
        if(year < 1993) {
            return 0;
        }
        else if(year < 1996) {
            return 1;
        }
        else {
            return 2;
        }
    }
}

在job驅動中,設置自定義partitioner:

job.setPartitionerClass(CustomPartitioner.class);

自定義partition後,要根據自定義partitioner的邏輯設置相應數量的reduce task

job.setNumReduceTasks(3);

注意:

如果reduceTask的數量> getPartition的結果數,則會多產生幾個空的輸出文件part-r-000xx;

如果1<reduceTask的數量<getPartition的結果數,則有一部分分區數據無處安放,會Exception;

如果reduceTask的數量=1,則不管mapTask端輸出多少個分區文件,最終結果都交給這一個reduceTask,最終也就只會產生一個結果文件 part-r-00000;

示例

(1)job.setNumReduceTasks(1);會正常運行,只不過會產生一個輸出文件

(2)job.setNumReduceTasks(2);會報錯

(3)job.setNumReduceTasks(6);大於5,程序會正常運行,會產生空文件

 

排序

1.部分排序(分區內有序,這是mapreduce框架默認的)
        key自動排序


2.全排序
        自定義分區(在某個範圍內的數據就放到一個分區內)
        對key的排序 
        

3.二次排序(定義組合key,  定義新的Map端排序,定義新的Reduce端的 GroupingComparator)
        對value的排序。
        將value整合到key

 

Combiner

可以簡單的理解爲是map端的reduce操作,使用的也是Reducer,可以減少map端輸出文件的大小

class WordCountReducerWithCombiner1 extends Reducer<Text, IntWritable, Text, IntWritable> {
    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
        int amount = 0;
        for(IntWritable iw : values) {
            amount += iw.get();
        }
        context.write(key, new IntWritable(amount));
    }
}

Reduce

 // reduceTask的個數可以在Driver中直接設置

 job.setNumReduceTasks(4);

注意

(1)reducetask=0 ,表示沒有reduce階段,輸出文件個數和map個數一致。

(2)reducetask默認值就是1,所以輸出文件個數爲一個。

(3)如果數據分佈不均勻,就有可能在reduce階段產生數據傾斜

(4)reducetask數量並不是任意設置,還要考慮業務邏輯需求,有些情況下,需要計算全局彙總結果,就只能有1個reducetask。

(5)具體多少個reducetask,需要根據集羣性能而定。

(6)如果分區數不是1,但是reducetask爲1,是否執行分區過程。答案是:不執行分區過程。因爲在maptask的源碼中,執行分區的前提是先判斷reduceNum個數是否大於1。不大於1肯定不執行

 

Reduce的過程

Copy階段(shuffle 階段)

ReduceTask從各個MapTask上遠程拷貝一片數據,並針對某一片數據,如果其大小超過一定閾值,則寫到磁盤上,否則直接放到內存中。

Merge階段

在遠程拷貝數據的同時,ReduceTask啓動了兩個後臺線程對內存和磁盤上的文件進行合併,以防止內存使用過多或磁盤上文件過多。

Sort階段

按照MapReduce語義,用戶編寫reduce()函數輸入數據是按key進行聚集的一組數據。爲了將key相同的數據聚在一起,Hadoop採用了基於排序的策略。由於各個MapTask已經實現對自己的處理結果進行了局部排序,因此,ReduceTask只需對所有數據進行一次歸併排序即可。

Reduce階段

reduce()函數將計算結果寫到HDFS上。

 

OutputFormat

OutputFormat是MapReduce輸出的基類,所有實現MapReduce輸出都實現了 OutputFormat接口,常用實現類

TextOutputFormat

默認的輸出格式是TextOutputFormat,它把每條記錄寫爲文本行。它的鍵和值可以是任意類型,因爲TextOutputFormat調用toString()方法把它們轉換爲字符串。

SequenceFileOutputFormat

SequenceFileOutputFormat將它的輸出寫爲一個順序文件。如果輸出需要作爲後續 MapReduce任務的輸入,這便是一種好的輸出格式,因爲它的格式緊湊,很容易被壓縮。

自定義OutputFormat

爲了實現控制最終文件的輸出路徑,可以自定義OutputFormat。

要在一個mapreduce程序中根據數據的不同輸出兩類結果到不同目錄,這類靈活的輸出需求可以通過自定義outputformat來實現。

自定義OutputFormat步驟

(1)自定義一個類繼承FileOutputFormat。

(2)改寫recordwriter,具體改寫輸出數據的方法write()。

 

MapReduce開發

輸入數據接口InputFormat

默認使用的實現類是:TextInputFormat

TextInputFormat的功能邏輯是:一次讀一行文本,然後將該行的起始偏移量作爲key,行內容作爲value返回

CombineTextInputFormat可以把多個小文件合併成一個切片處理,提高處理效率。

用戶還可以自定義InputFormat。

邏輯處理接口:Mapper

用戶根據業務需求實現其中三個方法:map() setup() cleanup () 

Partitioner分區

有默認實現 HashPartitioner,邏輯是根據key的哈希值和numReduces來返回一個分區號;key.hashCode()&Integer.MAXVALUE % numReduces

如果業務上有特別的需求,可以自定義分區。 

Comparable排序

當我們用自定義的對象作爲key來輸出時,就必須要實現WritableComparable接口,重寫其中的compareTo()方法。

部分排序:對最終輸出的沒一個文件進行內部排序。

全排序:對所有數據進行排序,通常只有一個Reduce。

二次排序:排序的條件有兩個。 

Combiner合併

Combiner合併可以提高程序執行效率,減少io傳輸。但是使用時必須不能影響原有的業務處理結果。

reduce端分組:Groupingcomparator

reduceTask拿到輸入數據(一個partition的所有數據)後,首先需要對數據進行分組,其分組的默認原則是key相同,然後對每一組kv數據調用一次reduce()方法,並且將這一組kv中的第一個kv的key作爲參數傳給reduce的key,將這一組數據的value的迭代器傳給reduce()的values參數。

利用上述這個機制,我們可以實現一個高效的分組取最大值的邏輯。

自定義一個bean對象用來封裝我們的數據,然後改寫其compareTo方法產生倒序排序的效果。然後自定義一個Groupingcomparator,將bean對象的分組邏輯改成按照我們的業務分組id來分組(比如訂單號)。這樣,我們要取的最大值就是reduce()方法中傳進來key。

邏輯處理接口:Reducer

用戶根據業務需求實現其中三個方法: reduce() setup() cleanup ()

輸出數據接口:OutputFormat

默認實現類是TextOutputFormat,功能邏輯是:將每一個KV對向目標文本文件中輸出爲一行。

自定義OutputFormat。

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