談談(詳解!)MapReduce--個人感想。

MapReduce的大致計算處理過程:
數據是經過mapper 然後 通過Shuffle,最後通過Reducer,然後輸出。
Map是映射,負責數據的過濾分發;Reduce是規約,負責數據的計算歸併。

mapper階段:

根據使用者的意願,mapper對輸入的數據進行操作,選取需要的字段(這個字段可能來自原數據中的值,也可能是原數據中的字段,或者是一個新的字段)和值(這個值可能來自原數據,但是更可能來自經新的定義),組成一個key-value的形式輸出返回給reducer,交給reducer後續處理。需要特別注意的是: mapper一次只讀取一行(或者說一個記錄),也就是說在mapper中的代碼邏輯是爲了處理一行(一條記錄)的,MapReduce本身會自動循環多次,從而達到處理所有數據的效果。(處理所有讀入的行的效果)
mapper返回的是 (key1,value1),返回給reducer,這時候reducer接收到一對 (key,value)形式的數據。

shuffle階段(洗牌):

這個階段MapReduce本身已經幫使用者實現了,也就是說使用者只需要實現map和reduce即可完成整個mapreduce過程邏輯,而不需要去開發shuffle程序的邏輯。shuffle實現的是:在map返回的所有數據裏,把key相同的放到一個組裏面(可以把這個組理解成一個類似數組的東西(可迭代對象)),於是,當map返回的各個單條數據組成的全部數據裏有多個不同的key的時候,shuffle裏面就會有多個組。這些個組將會一個一個的給reducer處理!當然,map處理過一行數據之後並不會等待其它行的數據也處理完之後才傳給shuffle、讓shuffle處理劃分成組;而是map處理一條數據之後將結果返回給shuffle,這時候shuffle裏面之後第一條數據,這條數據達到類似tensorflow裏面的佔位的作用,當map處理完下一條或者其它條的時候,map會繼續將結果傳遞給shuffle,只要傳進去的(key,value)在shuffle中已經有了,它就會歸到那個已經有佔位的和它有相同的key的組,如果傳進去的(key,value)在shuffle中沒有,那它將繼續佔位;直到所有map工作任務達到100%。也就是說在map工作的時候shuffle也是在工作的。

reducer階段:

reducer接收到來自shuffle的一組一組的數據,這時候reducer對其進行統計的操作即可達到統計key的個數的效果。當然這看起來所有過程都是在進行統計的過程,但是如果在其中加入一些算法的計算,那麼MapReduce將以其強大的集羣算力幫我實現大數據集的計算。

MapReduce框架的優點:

  1. 易於編程,用戶通常情況下只需要編寫Mapper和Reducer程序即可。
  2. 良好的擴展性,即可以很容易的增加節點
  3. 高容錯性,一個Job默認情況下會嘗試啓動兩次,一個mapper或者reducer默認會嘗試4次,如果一個節點掛了,可以向系統申請新的節點來執行這個mapper或者reducer
  4. 適合PB級別的數據的離線處理

MapReduce框架的缺點

不擅長實時計算,像MySQL一樣能夠立即返回結果
MapReduce的設計本身決定了處理的數據必須是離線數據,因爲涉及到數據切分等等。
不擅長DAG(有向圖)計算,需要一個Job執行完成之後,另一個Job才能使用他的輸出。

MapReduce的總過程可以概括爲:
1.輸入數據:…

2.map階段的輸入:
<偏移量key1,每一行value1,上下文context>
map階段的輸出:
<key2,value2>,其中key2相當於輸入的value1,而value2是經過運算等等得到的。

3.shuffle階段是MapReduce內部完成的。
shuffler接收來自map的輸出,變成shuffler的輸入,然後對其以相同的key3(這裏的key3其實是map的value2)進行分爲一組,然後以<key3,value3>的形式(這裏)傳給reduce。其實就是達到了多個相同的key分爲一組,但是並沒有統計(留給reduce做最後的迭代統計)的作用。

4.reduce階段:
進行一次reduce收到的數據是:多組<key3,value3>組成的一個對象,而且key3是一樣的。
這時候需要對value3進行一個牛頓迭代法求和操作,即可得到key3的總value3。達到一個統計的效果。【當然做其它事情也是可以的,按需】

合併 Combiner

注意: Combiner要慎用,有些場景並不適用Combiner,比如求平均值的場景。
解釋: 假如,有1,3,5,7,9五個數爲一行的數據文件存在HDFS上,計算其和。
沒有Combiner: 首先,讀取一行數據進入計算,Mapper返回(偏移量,1),(偏移量,3),(偏移量,5),(偏移量,7),(偏移量,9),進入reducer前根據key對數據進行一個shuffle並返回(key,[1,3,5,7,9])給reducer,reducer根據key對(1,3,5,7,9)進行求和得到結果。
有Combiner
首先,讀取一行數據進入計算,Mapper返回(偏移量1,1),(偏移量1,3),(偏移量1,5),(偏移量2,7),(偏移量2,9),進入reducer前根據key對數據進行一個shuffle並返回(key1,[1,3,5]),(key2,[7,9]);Combiner根據key1和key2進行對[*]求和,然後Reducer再對求和結果進行最終求和,但是與分區不同,Combiner的作用並不會使得數據處理結果產生n個文件。【其實,加入Combiner就相當於是進行了兩個reducer】

如何實現Combiner:【main函數下】

job.setCombinerClass(Reducer.class)

分區Partitioner

分區其實就是根據需求(某個判斷條件)把最終的輸出數據分發到不同的文件中,其實就是對Mapper劃分數據,而負責劃分數據的類爲Partitioner。

MapReduce默認的Partitioner是HashPartitioner,默認情況下,Partitioner先計算key的散列值(通常爲MD5值),然後通過Reducer個數執行求餘運算----key的hashCode除以Reducer的個數取餘數。
這種方式的好處: 隨機的將整個key空間平均分發給每個Reducer,同時也能確保不同Map產生的相同key能被分發到同一個Reducer。

Partitioner的實現例子:
如下根據數組的長度把數據在Mapper階段劃分爲2個區,

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;

public class Web_toHivePtion extends Partitioner<NullWritable,Text> {

    @Override
    public int getPartition(NullWritable nullWritable, Text text, int numPartition) {
        String[] tmpL = text.toString().split(",");
        if (tmpL.length == 5){
            return 1 % numPartition;
        }else{
            return 2% numPartition;
        }
    }
}

mapreduce經典案例解讀: 單詞統計
題目: 統計文本文件裏面的單詞的個數,每個單詞之間以一個空格分隔。

思路解析: 由mapreduce的基本運作原理,可以得到處理該題目的思路如下:

  • 首先,mapper接收輸入的數據(每次一行),對數據的一行數據,mapper將其以一個空格分隔,得到多個單詞組成的數組。並將其以(單詞,1)的key-value形式寫入到shuffle,因爲以空格分隔之後的到一個單詞計數爲1(這裏不用考慮單詞相同的情況)。
  • shuffle接收到數據之後,將key相同的劃分到一組。傳給reducer。
  • reducer對shuffle傳出的數據進行牛頓迭代計數,reducer接收到來自shuffle的數據時,每一個組裏面的key都是相同的,value都是1,所以只需要把value求和之後得到sum_now,以當前key和sum_now組成一個新的(key,value)輸出即可!

Mapper階段:
這裏的LongWritable,Text 代表了讀入數據之後的key爲偏移量,value爲一行,也就是說一次讀入一行,類型爲Text;map輸出格式爲:key是Text,value是IntWritable。

package com.mytest.wordcount;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

/**
 * Created by ua07 on 9/20/19.
 */
public class WordCountMapper extends Mapper<LongWritable,Text,Text,IntWritable> {
//其實這裏LongWritable,Text的LongWritable,Text 代表了讀入數據之後的key爲偏移量,value爲一行,也就是說一次讀入一行,類型爲Text,概括的話就是mapper的輸入格式(後兩個);mapper輸出格式爲:key是Text,value是IntWritable。


    /**
     *
     * @param key1
     * @param value
     * @param context
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    protected void map(LongWritable key1,Text value,Context context)
            throws IOException,InterruptedException{
        String data = value.toString();
        String[] words = data.split(" ");

        for(String w:words){
            context.write(new Text(w),new IntWritable(1));
        }

    }
}

Reducer階段:

package com.mytest.wordcount;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

/**
 * Created by ua07 on 9/20/19.
 */
public class WordCountReducer extends Reducer<Text,IntWritable,Text,IntWritable> {
//這裏前面的的Text,IntWritable是reducer的輸入,也就是mapper的輸出。後面的Text,IntWritable是reducer的輸出。

    /**
     *
     * @param k3
     * @param v3
     * @param context
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    protected void reduce(Text k3,Iterable<IntWritable> v3,Context context)
            throws IOException,InterruptedException{
        int total = 0;
        for (IntWritable v:v3){
            total += v.get();
        }
        context.write(k3,new IntWritable(total));
    }
}

主函數:

package com.mytest.wordcount;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

/**
 * Created by ua07 on 9/20/19.
 */
public class WordCountMain {
    public static void main(String[] args) throws Exception{
        Job job = Job.getInstance(new Configuration());
        job.setJarByClass(WordCountMain.class);

        job.setMapperClass(WordCountMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

        job.setReducerClass(WordCountReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        FileInputFormat.setInputPaths(job,new Path(args[0]));
        FileOutputFormat.setOutputPath(job,new Path(args[1]));

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