MapReduce流程分析(R1)
接觸Hadoop已經1年了,一直沒時間好好學習下。這幾天打算好好研究下Hadoop.本來是想打算改寫下TextInputFormat。看了原始碼後,反而更迷糊了。所以乾脆連MapReduce的整個流程寫下來。也當為這幾天的學習作個總結。
先來一個我們常寫的main函式。
Configuration conf = new Configuration();
String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
if (otherArgs.length != 2){
System.exit(2);
}
Job job = new Job(conf, "wordcount");
job.setJarByClass(mywordcount.class);
job.setInputFormatClass(TextInputFormat.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
job.setMapperClass(wordcountMapper.class);
job.setReducerClass(wordcountReduce.class);
job.setCombinerClass(wordcountReduce.class);
FileInputFormat.setInputPaths(job, new Path(otherArgs[0]));
FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
job.waitForCompletion(true);
上述程式我就不分析。直接來分析下執行流程。
在master節點的NameNode, SecondedNameNode,JobTracker和slaves節點的DataNode, TaskTracker都已經啟動後,JobTracker一直在等待JobClient透過RPC提交作業,而TaskTracker一直透過RPC向 JobTracker傳送心跳heartbeat詢問有沒有任務可做。
而主程式中透過job.waitForCompletion(true)函式透過呼叫JobClient.runJob()函式將MapReduce作業交與JobTrack進行執行。
1:JobClient.runJob()
runjob會根據使用者的設定(job.setInputFormatClass())來將需要輸入的資料劃分成小的資料集,同時返回劃分後split想的相應資訊(這裡的split路徑資訊我估計應該是hdfs裡的路徑和偏移,因為我們要處理的資料早應該上傳到了hdfs系統)。同時根據split設定MapTask的個數。獲取了上面資訊後,就將執行任務所需要的全部資料、資訊全部上傳至HDFS。上傳的內容主要包括三個包: job.jar, job.split和job.xml
job.xml: 作業配置,例如Mapper, Combiner, Reducer的型別,輸入輸出格式的型別等。
job.jar: jar包,裡面包含了執行此任務需要的各種類,比如 Mapper,Reducer等實現。
job.split: 檔案分塊的相關資訊,比如有資料分多少個塊,塊的大小(預設64m)等。
進行完上述工作後,本地的工作就完成了,函式jobSubmitClient.submitJob(jobId)呼叫真正的JobTracker執行Task。
2:JobTracker.submitJob()
當JobTracker接收到新的job請求(即submitJob()函式被呼叫)後,會建立一個JobInProgress物件並透過它來管理和排程任務。JobInProgress在建立的時候會初始化一系列與任務有關的引數,呼叫FileSystem,把在JobClient端上傳的所有任務檔案下載到本地的檔案系統中的臨時目錄裡。這其中包括上傳的*.jar檔案包、記錄配置資訊的xml、記錄分割資訊的檔案。
在JobTracker的建構函式中,會生成一個taskScheduler成員變數,來進行Job的排程, 預設為JobQueueTaskScheduler,也即按照FIFO的方式排程任務。而在offerService函式中也為JobTracker(也即taskScheduler的taskTrackerManager)註冊了兩個Listener:
- JobQueueJobInProgressListener jobQueueJobInProgressListener用於監控job的執行狀態
- EagerTaskInitializationListener eagerTaskInitializationListener用於對Job進行初始化
EagerTaskInitializationListener的jobAdded函式就是向jobInitQueue中新增一個JobInProgress物件,於是自然觸發了此Job的初始化操作。由JobInProgress的initTasks函式完成:
3:initTasks()
任務Task分兩種: MapTask 和reduceTask,它們的管理物件都是TaskInProgress 。
在initTasks函式,會透過JobClient的readSplitFile()獲得已分解的輸入資料的RawSplit列表,然後透過這個列表建立相應的TaskInProgress(MapTask)同時還會讀取相應資料塊所在DataNode的主機名(透過FileSplit的getLocations()函式獲取)。建立完TaskInProgress後,就會呼叫createCache()方法為這些TaskInProgress物件產生一個未執行任務的Map快取nonRunningMapCache。當TaskTracker向JobTrack中傳送心跳,請求任務時,就會去直接去這個快取中取任務。
JobInProgress也會建立Reduce的監控物件- TaskInProgress,而這個的數量就是根據使用者在程式裡的設定,預設的是一個。同樣地,initTasks()也會透過createCache()方法產生nonRunningReduceCache成員。JobInProgress再接著進行清理工作。最後再記錄一下Job的執行日誌。
至此Job的初始化就全部完成。
4:TaskTracker
TaskTracker從啟動後,就每隔一定的時間向JobTracker傳送一次心跳(預設10s,傳送的內容包括自己的當前狀態,當滿足一定狀態時就可以向JobTracker申請新的任務。如Map Task、 Reduce Task都還有執行的能力)透過transmitHeartBeat()傳送心跳後再接受JobTracker返回的HeartbeatResponse。然後呼叫HeartbeatResponse的getActions()函式獲得JobTracker傳過來的所有指令即一個TaskTrackerAction陣列。再遍歷這個陣列,就可以知道需要完成的事情。(這些事情可能是LaunchTaskAction 執行新任務、 KillTaskAction 結束一個任務)如果有分配好的任務將其加入佇列,呼叫addToTaskQueue,如果是map task則放入mapLancher(型別為TaskLauncher),如果是reduce task則放入reduceLancher(型別為TaskLauncher)
5 JobTracker:heartbeat()
JobTracker是透過heartbeat()函式來接受TaskTracker的心跳,如果TaskTracker是請求任務的指令。Heartbeat()函式就會呼叫預設的任務排程器(JobQueueTaskScheduler)來分配任務。先計算Map和Reduce的剩餘工作量,再計算每個TaskTracker應有的工作量。如果TaskTracker上執行的map task數目小於平均的工作量,則向其分配map task。分配完Map Task後再分配Reduce task.而這裡有一個函式findNewMapTask()就是從nonRunningMapCache和nonRunningReduceCache中查詢出map task的TaskInProgress和Reduce task. 的TaskInProgress再返回給TaskTracker。
findNewMapTask()從近到遠一層一層地尋找,首先是同一節點,然後在尋找同一機櫃上的節點,接著尋找相同資料中心下的節點,直到找了maxLevel層結束。這樣的話,在JobTracker給TaskTracker派發任務的時候,可以迅速找到最近的TaskTracker,讓它執行任務。(透過尋找本任務split所在的DataNode,然後判斷髮送心跳的TaskTracker和本任務split所在的DataNode是不是同一主機。如果是則分配這個MapTask給傳送心跳的Tracker,如果不是者返回null不進行分配。)
再呼叫localizeJob()進行真正的初始化(TaskTracker上的Task)。而localizeJob又呼叫TaskLauncher
6:TaskLauncher
TaskLauncher是一個執行緒就是從上面的佇列中取出TaskInProgress然後呼叫startNewTask(TaskInProgress tip)來啟動一個task。
這裡又會再次將Task有關的資料包、資訊包從HDFS複製回本地檔案系統包括:job.split,job.xml以及job.jar,當所有的資源複製回來後,就呼叫launchTaskForJob()開始執行Task.
launchTaskForJob函式又呼叫launchTask()
7:launchTask()
launchTask()函式首先透過createRunner()函式是建立MapTaskRunner來啟動子程式和建立ReduceTaskRunner來啟動子程式。TaskRunner負責將一個任務放到一個程式裡面來執行。它會呼叫run()函式來處理。run()函式會初始化一系列環境變數等。最後生成一個新程式並執行即runChild。
8 Child程式
真正的map task和reduce task都是在Child程式中執行的。Child程式會執行Task.
9:MapTask
如果是MapTask,MapTask.run()首先向TaskTracker彙報情況,再設定Mapper的輸出格式。接著讀取input split,按照其中的資訊,生成RecordReader來讀取資料。這其中會生成一個MapRunnable
,而MapRunnable要完成的任務就時透過RecordReader的next函式讀取迴圈從split中讀取
10:OutputCollector
OutputCollector的作用是收集每次呼叫map後得到的新的kv對,寧把他們spill到檔案或者放到記憶體,以做進一步的處理,比如排序,combine等。
MapOutputCollector 有兩個子類:MapOutputBuffer和DirectMapOutputCollector。 DirectMapOutputCollector用在不需要Reduce階段的時候。如果Mapper後續有reduce任務,系統會使用MapOutputBuffer做為輸出, MapOutputBuffer使用了一個緩衝區對map的處理結果進行快取,放在記憶體中. 在適當的時機,緩衝區中的資料會被spill到硬碟中。spillThread執行緒實現將緩衝區的資料寫入硬碟。
向硬碟中寫資料的時機:
(1)當記憶體緩衝區不能容下一個太大的kv對時。spillSingleRecord方法。
(2)記憶體緩衝區已滿時。SpillThread執行緒。
(3)Mapper的結果都已經collect了,需要對緩衝區做最後的清理。Flush方法。
11:ReduceTask
ReduceTask .run()函式同樣先進行一系列的初始化工作。之後進入正式的工作,主要有這麼三個步驟:Copy、Sort、Reduce。
11.1:copy
copy就是從執行各個Map任務的伺服器那裡,蒐羅map的輸出檔案。
複製的任務的是由ReduceTask.ReduceCopier 類來負責。ReduceCopier先向父TaskTracker詢問此作業個Map任務的完成狀況,獲取到map伺服器的相關資訊後由執行緒MapOutputCopier做具體的複製工作。在複製過來的同時也會做一些歸併排序以減輕後面sort的負擔。
11.2 Sort
排序工作,就相當於上述排序工作的一個延續。它會在所有的檔案都複製完畢後進行。使用工具類Merger歸併所有的檔案。經過這一個流程,一個合併了所有所需Map任務輸出檔案的新檔案產生了。而那些從其他各個伺服器網羅過來的 Map任務輸出檔案,全部刪除了。
11.3Reduce
Reduce任務的最後一個階段。
輸入方面:他會準備根據自定義或預設的KeyClass、ValueClass構造出Reducer所需的鍵型別, 和值的迭代型別Iterator
輸出方面:它會準備一個OutputCollector收集輸出與MapTask不同,這個OutputCollector更為簡單,僅僅是開啟一個RecordWriter,collect一次(排序完成的那個檔案),write一次(寫往HDFS)。
有了輸入,有了輸出,不斷迴圈呼叫自定義的Reducer,最終,Reduce階段完成。
寫本文之際參看了很多牛人的大作,在這裡一併感謝。
最後,我還有三個問題沒理解,請教高手解答一下。
Des1:當Jobtracker向Tasktracker分配任務時是先判斷Tasktracker上執行的Task是否小於平均工作量,小於者向其分配Task。(假如我們這裡是MapTask。)然後呼叫函式obtainNewMapTask()中的findNewMapTask()來查詢nonRunningMapCache中的TaskInProgress。
findNewMapTask()函式會從近到遠一層一層地尋找。首先是同一節點,然後在尋找同一機櫃上的節點,接著尋找相同資料中心下的節點,直到找了maxLevel層結束。(這段話是我從網上看到的。沒理解這句話的意思。)
Q1:我想請問下, findNewMapTask在這裡尋找的是什麼? (TaskTracker)我的理解是:透過尋找本任務split所在的DataNode,然後判斷髮送心跳的TaskTracker和本任務split所在的DataNode是不是同一主機。如果是則分配這個MapTask給傳送心跳的Tracker,如果不是者返回null不進行分配。如果我理解錯了,請解釋下。謝謝了。
Des2:當客戶端提交任務後,首先會透過使用者設定的InputFormat將檔案進行劃分。而hadoop預設的TextInputFormat.class.檢視原始碼知道。TextInputFormat是繼承的FileInputFormat.並且將isSplitable進行了關閉。所以預設的是不對檔案進行劃分。
Q2:在執行一個MapReduce程式時,原始資料都會提前上傳到HDFS檔案系統,大於64M的檔案都會被劃分儲存到多個DataNode。。假如我有一個128M的檔案上傳到了HDFS,那天檔案應該被劃分成了2份。那麼TextInputFormat在處理輸入時不對檔案進行劃分,在TaskTracker處理檔案時,處理的是64M,還是128M呢?
(這個問題已經解決,應該是64M)
Q3:如果是64M是不是會開啟兩個TaskTracker來處理檔案,又因為TextInputFormat對檔案不進行劃分,所以每個
TaskTracker上只會開啟一個Map Task來處理Map任務。最後兩個TaskTracker啟動兩個Reduce生成兩個檔案?
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/26613085/viewspace-1095679/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Mapreduce原始碼分析分片、處理流程原始碼
- mapreduce job提交流程原始碼級分析(三)原始碼
- MapReduce執行流程
- MapReduce工作流程
- MapReduce程式執行流程
- mapreduce job提交流程原始碼級分析(二)(原創)原始碼
- MapReduce的執行流程概述
- MapReduce工作原理流程簡介
- MapReduce入門及核心流程案例
- MapReduce 詳解與原始碼分析原始碼
- 大資料小白系列 —— MapReduce流程的深入說明大資料
- 使用hadoop mapreduce分析mongodb資料HadoopMongoDB
- 雞蛋挺住體;及MapReduce矩陣分析矩陣
- Hadoop2原始碼分析-MapReduce篇Hadoop原始碼
- MapReduce(一):入門級程式wordcount及其分析
- Mapreduce(二):MR的執行過程分析
- Mapreduce Job提交流程原始碼和切片原始碼詳解原始碼
- printk 流程分析
- printk流程分析
- MapReduce——客戶端提交任務原始碼分析客戶端原始碼
- MapReduce —— MapTask階段原始碼分析(Input環節)APT原始碼
- MapReduce —— MapTask階段原始碼分析(Output環節)APT原始碼
- MapReduce框架Mapper和Reducer類原始碼分析框架APP原始碼
- Flutter setState流程分析Flutter
- Lifecycle 流程分析
- View draw流程分析View
- MapReduce job在JobTracker初始化原始碼級分析原始碼
- CuOI R1 - Split The Crystals
- 堅果R1配置效能全面評測 堅果R1值得買嗎?
- Activity啟動流程分析
- View 繪製流程分析View
- Activity的起步流程分析
- Mysql工作流程分析MySql
- activity 啟動流程分析
- cuttag分析流程(部分存疑)
- MapReduce初探
- MapReduce理解
- oracle merge into用法(R1)Oracle