初識MapReduce的應用場景(附JAVA和Python程式碼)

spacedong發表於2019-02-27

從這篇文章開始,我會開始系統性地輸出在大資料踩坑過程中的積累,後面會涉及到實戰專案的具體操作,目前的規劃是按照系列來更新,力爭做到一個系列在5篇文章之內總結出最核心的乾貨,如果是涉及到理論方面的文章,會以畫圖的方式來講解,如果是涉及到操作方面,會以實際的程式碼來演示。

這篇是MapReduce系列的第一篇,初識MapReduce的應用場景,在文章後面會有關於程式碼的演示。

前言

Hadoop作為Apache旗下的一個以Java語言實現的分散式計算開源框架,其由兩個部分組成,一個是分散式的檔案系統HDFS,另一個是批處理計算框架MapReduce。這篇文章作為MapReduce系列的第一篇文章,會從MapReduce的產生背景、框架的計算流程、應用場景和演示Demo來講解,主要是讓大家對MapReduce的這個批計算框架有個初步的瞭解及簡單的部署和使用。

目錄

MapReduce的產生背景

MapReduce的計算流程

MapReduce的框架架構

MapReduce的生命週期

應用場景

演示Demo

MapReduce的產生背景

Google 在2004年的時候在 MapReduce: Simplified Data Processing on Large Clusters 這篇論文中提出了MapReduce 的功能特性和設計理念,設計MapReduce 的出發點就是為了解決如何把大問題分解成獨立的小問題,再並行解決。例如,MapReduce的經典使用場景之一就是對一篇長文進行詞頻統計,統計過程就是先把文章分為一句一句,然後進行分割,最後進行詞的數量統計。

MapReduce的架構圖

MapReduce的架構圖
這裡的Client和TaskTracker我都使用一個來簡化了,在實際中是會有很個Client和TaskTracker的。

我們來講解下不同的元件作用

Client

Client的含義是指使用者使用MapReduce程式通過Client來提交任務到Job Tracker上,同時使用者也可以使用Client來檢視一些作業的執行狀態。

Job Tracker

這個負責的是資源監控和作業排程。JobTracker會監控著TaskTracker和作業的健康狀況,會把失敗的任務轉移到其他節點上,同時也監控著任務的執行進度、資源使用量等情況,會把這些訊息通知任務排程器,而排程器會在資源空閒的時候選擇合適的任務來使用這些資源。

任務排程器是一個可插拔的模組,使用者可以根據自己的需要來設計相對應的排程器。

TaskTracker

TaskTracker會週期性地通過Hearbeat來向Job Tracker彙報自己的資源使用情況和任務的執行進度。會接受來自於JobTaskcker的指令來執行操作(例如啟動新任務、殺死任務之類的)。

TaskTracker中通過的是slot來進行等量劃分一個節點上資源量,只用Task獲得slot的時候才有機會去執行。排程器的作用就是進行將空閒的slot分配給Task使用,可以配置slot的數量來進行限定Task上的併發度。

Task

Task分為Map TaskReduce Task,在MapReduce中的 split 就是一個 Map Task,split 的大小可以設定的,由 mapred.max.spilt.size 引數來設定,預設是 Hadoop中的block的大小,在Hadoop 2.x中預設是128M,在Hadoop 1.x中預設是64M

Task中的設定可以這麼設定,一般來講,會把一個檔案設定為一個split,如果是小檔案,那麼就會存在很多的Map Task,這是特別浪費資源的,如果split切割的資料塊的量大,那麼會導致跨節點去獲取資料,這樣也是消耗很多的系統資源的。

MapReduce的生命週期

MapReduce的生命週期

一共分為5個步驟:

  1. 作業的提交和初始化

由使用者提交作業之前,需要先把檔案上傳到HDFS上,JobClient使用upload來載入關於打包好的jar包,JobClientRPC建立一個JobInProcess來進行管理任務,並且建立一個TaskProcess來管理控制關於每一個Task

  1. JobTracker排程任務

JobTracker會排程和管理任務,一發現有空閒資源,會按照一個策略選擇一個合適的任務來使用該資源。

任務排程器有兩個點:一個是保證作業的順利執行,如果有失敗的任務時,會轉移計算任務,另一個是如果某一個Task的計算結果落後於同一個Task的計算結果時,會啟動另一個Task來做計算,最後去計算結果最塊的那個。

  1. 任務執行環境

TaskTracker會為每一個Task來準備一個獨立的JVM從而避免不同的Task在執行過程中的一些影響,同時也使用了作業系統來實現資源隔離防止Task濫用資源。

  1. 執行任務

每個Task的任務進度通過RPC來彙報給TaskTracker,再由TaskTracker彙報給JobTracker。

  1. 任務結束,寫入輸出的檔案到HDFS中。

MapReduce 的計算流程

先來看一張圖,系統地瞭解下 MapReduce 的運算流程。

MapReduce的運算流程

為了方便大家理解,重新畫了一張新的圖,演示的是關於如何進行把一個長句進行分割,最後進行詞頻的統計(已忽略掉標點符號)。

簡單的實操例子

整個過程就是先讀取檔案,接著進行split切割,變成一個一個的詞,然後進行 map task 任務,排列出所有詞的統計量,接著 sorting 排序,按照字典序來排,接著就是進行 reduce task,進行了詞頻的彙總,最後一步就是輸出為檔案。例如圖中的 spacedong 就出現了兩次。

其中對應著的是 Hadoop Mapreduce 對外提供的五個可程式設計元件,分別是InputFormatMapperPartitionerReduceOutputFormat,後續的文章會詳細講解這幾個元件。

用一句話簡單地總結就是,Mapreduce的運算過程就是進行拆解-排序-彙總,解決的就是統計的問題,使用的思想就是分治的思想。

MapReduce的應用場景

MapReduce 的產生是為了把某些大的問題分解成小的問題,然後解決小問題後,大問題也就解決了。那麼一般有什麼樣的場景會運用到這個呢?那可多了去,簡單地列舉幾個經典的場景。

計算URL的訪問頻率

搜尋引擎的使用中,會遇到大量的URL的訪問,所以,可以使用 MapReduce 來進行統計,得出(URL,次數)結果,在後續的分析中可以使用。

倒排索引

Map 函式去分析檔案格式是(詞,文件號)的列表,Reduce 函式就分析這個(詞,文件號),排序所有的文件號,輸出(詞,list(文件號)),這個就可以形成一個簡單的倒排索引,是一種簡單的演算法跟蹤詞在文件中的位置。

Top K 問題

在各種的文件分析,或者是不同的場景中,經常會遇到關於 Top K 的問題,例如輸出這篇文章的出現前5個最多的詞彙。這個時候也可以使用 MapReduce來進行統計。

演示Demo

今天的程式碼演示從PythonJava兩個版本的演示,Python版本的話便是不使用封裝的包,Java版本的話則是使用了Hadoop的封裝包。接下來便進行演示一個MapReduce的簡單使用,如何進行詞彙統計。

Java版本程式碼

  • 先是準備一個資料集,包含著已經切割好的詞彙,這裡我們設定檔案的格式是txt格式的。檔名是WordMRDemo.txt,內容是下面簡短的一句話,以空格分割開:

hello my name is spacedong welcome to the spacedong thank you

  • 引入Hadoop的依賴包
  //這裡使用的是2.6.5的依賴包,你可以使用其他版本的
       <dependency>
			<groupId>org.apache.hadoop</groupId>
			<artifactId>hadoop-common</artifactId>
			<version>2.6.5</version>
		</dependency>
		<dependency>
			<groupId>org.apache.hadoop</groupId>
			<artifactId>hadoop-client</artifactId>
			<version>2.6.5</version>
		</dependency>
複製程式碼
  • 新建WordMapper.java檔案,程式碼的作用是進行以空格的形式進行分詞。
public class WordMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
    @Override
    protected void map(LongWritable key, Text value, Mapper.Context context)
            throws java.io.IOException, InterruptedException {
        String line = value.toString();
        //StringTokenizer預設按照空格來切
        StringTokenizer st = new StringTokenizer(line);
        while (st.hasMoreTokens()) {
            String world = st.nextToken();
            //map輸出
            context.write(new Text(world), new IntWritable(1));
        }
    }
}
複製程式碼
  • 新建WordReduce.java檔案,作用是進行詞彙的統計。
public class WordReduce extends Reducer<Text, IntWritable, Text, IntWritable> {

    @Override
    protected void reduce(Text key, Iterable<IntWritable> iterator, Context context)
            throws java.io.IOException ,InterruptedException {
        int sum = 0 ;
        for(IntWritable i:iterator){
            sum+=i.get();
        }
        context.write(key, new IntWritable(sum));
    }
  }
複製程式碼
  • 新建WordMRDemo.java檔案,作用是執行Job,開始分析句子。
public class WordMRDemo {
    public static void main(String[] args) {
        Configuration conf = new Configuration();
        //設定mapper的配置,既就是hadoop/conf/mapred-site.xml的配置資訊
        conf.set("mapred.job.tracker", "hadoop:9000");
        try {
            //新建一個Job工作
            Job job = new Job(conf);
            //設定執行類
            job.setJarByClass(WordMRDemo.class);
            //設定要執行的mapper類
            job.setMapperClass(WordMapper.class);
            //設定要執行的reduce類
            job.setReducerClass(WordReduce.class);
            //設定輸出key的型別
            job.setMapOutputKeyClass(Text.class);
            //設定輸出value的型別
            job.setMapOutputValueClass(IntWritable.class);
            //設定ruduce任務的個數,預設個數為一個(一般reduce的個數越多效率越高)
            //job.setNumReduceTasks(2);
            //mapreduce 輸入資料的檔案/目錄,注意,這裡可以輸入的是目錄。
            FileInputFormat.addInputPath(job, new Path("F:\\BigDataWorkPlace\\data\\input"));
            //mapreduce 執行後輸出的資料目錄,不能預先存在,否則會報錯。
            FileOutputFormat.setOutputPath(job, new Path("F:\\BigDataWorkPlace\\data\\out"));
            //執行完畢退出
            System.exit(job.waitForCompletion(true) ? 0 : 1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
複製程式碼
  • 最後執行WordMRDemo.java檔案,然後得到的結果是out資料夾內的內容,它長這個樣子:
    out的檔案目錄

開啟part-r-00000檔案的內容如下

具體的檔案內容

Python程式碼版本

  • 新建map.py檔案,進行詞彙的切割。
for line in sys.stdin:
    time.sleep(1000)
    ss = line.strip().split(' ')
    for word in ss:
        print '\t'.join([word.strip(), '1'])
複製程式碼
  • 新建red.py檔案,進行詞彙的統計。
cur_word = None
sum = 0

for line in sys.stdin:
	ss = line.strip().split('\t')
	if len(ss) != 2:
		continue
	word, cnt = ss

	if cur_word == None:
		cur_word = word

	if cur_word != word:
		print '\t'.join([cur_word, str(sum)])
		cur_word = word
		sum = 0

	sum += int(cnt)

print '\t'.join([cur_word, str(sum)])
複製程式碼
  • 新建run.sh檔案,直接執行即可。
HADOOP_CMD="/usr/local/src/hadoop-2.6.5/bin/hadoop"
STREAM_JAR_PATH="/usr/local/src/hadoop-2.6.5/share/hadoop/tools/lib/hadoop-streaming-2.6.5.jar"

INPUT_FILE_PATH_1="/test.txt"
OUTPUT_PATH="/output"

$HADOOP_CMD fs -rmr -skipTrash $OUTPUT_PATH

# Step 1.
$HADOOP_CMD jar $STREAM_JAR_PATH \
    -input $INPUT_FILE_PATH_1 \
    -output $OUTPUT_PATH \
    -mapper "python map.py" \
    -reducer "python red.py" \
    -file ./map.py \
    -file ./red.py
複製程式碼

以上的是演示demo的核心程式碼,完整的程式碼可以上github的程式碼倉庫上獲取。 倉庫地址為:https://github.com/spacedong/bigDataNotes

以上的文章是MapReduce系列的第一篇,下篇預告是MapReduce的程式設計模型,敬請期待!

參考資料:

Hadoop的技術內幕:深入解析MapReduce架構設計及實現原理

image

相關文章