MapReduce基礎原理:
MapReduce(起源於Google):
MapReduce是一種計算模型,它將大型數據操作作業分解爲可以跨服務器集羣並行執行的單個任務。用於管理DataNode
用於大規模數據處理:每個節點處理存儲在該節點上的數據
每個MapReduce工作由兩個階段組成:Map;Reduce
自動MapReduce計算:
MapReduce計算是並行和自動分佈的
開發人員只需要專注於實現映射和reduce功能
M/R可以用Java編寫的,但也支持Python流
使用MapReduce的原因:
MapReduce是一種模式,適合解決並行計算的問題,比如TopN、貝葉斯分類等;而非迭代計算,如涉及到層次聚類的問題就不太適合了。
這種模式有兩個步驟,Map和Reduce。Map即數據的映射,用於把一組鍵值對映射成另一組新的鍵值對; Reduc以Map階段的輸出結果作爲輸入,對數據做化簡,合併等操作。
MapReduce是Hadoop生態系統中基於底層HDFS的一個計算框架,它的上層又可以是Hive、Pig等數據倉庫框架,也可以是Mahout這樣的數據挖掘工具。
由於MapReduce依賴於HDFS,其運算過程中的數據等會保存到HDFS上,把對數據集的計算分發給各個節點,並將結果進行彙總,再加上各種狀態彙報、心跳彙報等,其只適合做離線計算。MapReduce和實時計算框架Storm、Spark等相比,速度上沒有優勢。
Hadoop v1生態幾乎是以MapReduce爲核心的,但是隨着發展,其逐漸出現擴展性差、資源利用率低、可靠性等問題,於是產生了Yarn;所以Hadoop v2生態都是以Yarn爲核心。Storm、Spark等都可以基於Yarn使用。
怎麼運行MapReduce:
在寫完代碼後,打包成jar,在HDFS的客戶端運行:yarn jar hadoop-1.0-SNAPSHOT.jar WordCount /little_prince.txt /user/bda/output即可。當然,也可以在IDE(如IDEA)中,進行遠程運行、調試程序。
如何編寫MapReduce程序:
MapReduce中有Map和Reduce,在實現MapReduce的過程中,主要分爲這兩個階段,分別以兩類函數進行展現,一個是map函數,一個是reduce函數。map函數的參數是一個<key,value>鍵值對,其輸出結果也是鍵值對,reduce函數以map的輸出作爲輸入進行處理。
Map類:
創建Map類和map函數,map函數是org.apache.hadoop.mapreduce.Mapper類中的定義的,當處理每一個鍵值對的時候,都要調用一次map方法,用戶需要覆寫此方法。此外還有setup方法和cleanup方法。map方法是當map任務開始運行的時候調用一次,cleanup方法是整個map任務結束的時候運行一次。
Map介紹:
Mapper類是一個泛型類,帶有4個參數(輸入的鍵,輸入的值,輸出的鍵,輸出的值)。在這裏輸入的key爲Object(默認是行),輸入的值爲Text(hadoop中的String類型),輸出的key爲Text(關鍵字)和輸出的值爲IntWritable(hadoop中的int類型)。以上所有hadoop數據類型和java的數據類型都很相像,除了它們是針對網絡序列化而做的特殊優化。
MapReduce中的類似於IntWritable的類型還有如下幾種:
BooleanWritable:標準布爾型數值、ByteWritable:單字節數值、DoubleWritable:雙字節數值、FloatWritable:浮點數、IntWritable:整型數、LongWritable:長整型數、Text:使用UTF8格式存儲的文本(類似java中的String)、NullWritable:當<key, value>中的key或value爲空時使用。
這些都是實現了WritableComparable接口:
·
Map任務是一類將輸入記錄集轉換爲中間格式記錄集的獨立任務。 Mapper類中的map方法將輸入鍵值對(key/value pair)映射到一組中間格式的鍵值對集合。這種轉換的中間格式記錄集不需要與輸入記錄集的類型一致。一個給定的輸入鍵值對可以映射成0個或多個輸出鍵值對。
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
word.set(itr.nextToken());
context.write(word, one);
}
這裏將輸入的行進行解析分割之後,利用Context的write方法進行保存。而Context是實現了MapContext接口的一個抽象內部類。此處把解析出的每個單詞作爲key,將整形1作爲對應的value,表示此單詞出現了一次。map就是一個分的過程,reduce就是合的過程。Map任務的個數和前面的split的數目對應,作爲map函數的輸入。Map任務的具體執行見下一小節。
代碼構成:
實際的代碼中,需要三個元素,分別是Map、Reduce、運行任務的代碼。這裏的Map類是繼承了org.apache.hadoop.mapreduce.Mapper,並實現其中的map方法;而Reduce類是繼承了org.apache.hadoop.mapreduce.Reducer,實現其中的reduce方法。至於運行任務的代碼,就是我們程序的入口。
MapReduce計算單詞數量:
先進性分割分開一段一段的用物理塊分開,先進行分詞處理,第一個Map進行全文統計,進行詞頻統計分開成爲key-value對的形式,每遇到一個詞就形成一個鍵值對,輸出計算結果聯合情況,先進行局部統計,再進行全局統計進行數據遷移和排序,最後送給Reduce進行最後統計。
Shuffle:數據遷移,很慢需要很多時間,要儘量避免,或者縮短時間,shuffle是一個臨界點前面是Map後面是Reduce
局部統計的好處:如果沒有Combine送到Shuffle和Reduce的數量就太多了,減少遷移數據量可以加快處理速度,應該把更多的工作放在Map裏
Text是Hadoop創造的一個類
StringTokenizer是指定使用分詞的方法進行計算
將想要計數的文本文件放進Hadoop中,編寫的程序直接到虛擬機的yarn中運行
計算的原則:
- 儘量減少數據遷移
- 儘量提升並行處理
- 都是在DataNode裏做,NameNode從來不做這些操作
算法:解決問題的方法和步驟
不可分割,可以執行,算法描述有文字版、圖形版、代碼版以及僞代碼版
查找算法的時間複雜度,時間和空間之間的關係
常數複雜度,線性複雜度,指數複雜度
MapReduce框架:
Mapper一定要有:
Combiner可有可無:只能用在交換率(a.b = b.a)和結合律{a.(b.c) =(a.b).c},比如在計算所有人平均年齡的時候不能使用combiner進行除法,只能進行加減法,因爲使用除法時候不適用結合律
Partitioner一定要有:分區處理器,判斷哪個key-value送到哪個Reduce裏,把不同的key-value送到不同的Reduce裏,默認值是HashPartitioner中的key是用hashCode拿到的是整形的,有幾個Reduce就進行%幾的計算,餘數是幾就到哪個Reduce裏,它對numOfPartitions執行一個模塊化操作以返回分區號
Shuffle和Sort:Map到Reduce的過程是push的過程,要進行重新洗牌,並根據Reduce進行重新排序
Reducer在一定情況下可能沒有:
InputSplit是邏輯塊,只是一個概念:
HDFS切割時候不是用邏輯塊切分,使用的是物理塊,而InputSplit就是用邏輯塊分割,一般邏輯塊都比物理塊數量多,在記錄的邊界處劃分
Mapper的數量是由InputSplit決定的,一定要訂了邏輯結構和物理大小才能決定Mapper的數量
InputSplit表示要由單個Mapper處理的數據:
塊是數據的物理表示。分割是塊中數據的邏輯表示。
InputSplit涉及邏輯記錄邊界:
在MapReduce執行期間,Hadoop掃描這些塊,並創建inputsplit並且每個InputSplit將分配給各個映射器進行處理。
MapReduce編程模型:
Map必須有,拿到的值是key-value;Combiner可有可無;某些MapReduce沒有Reduce,比如往HDFS裏寫文件就沒有Reduce,只要分塊寫就好,
如果想指定Map的個數,就要在文件送進HDFS中時規定好文件的大小
InputFormat是一個接口:調用getInputSplit方法知道有幾個InputSplit,計算得到需要啓動幾個Map,在數據所在地產生Map的實例。
InputFormat再調用create RecordReader裏的read方法,從裏面讀取數據送給K-V。
OutputFormat調用write方法把K-V寫出去,數據格式會保留;用一個check進行一個校驗,實現接口把他穿進去
Key和Value的類型都是可序列化的:
因爲Object是不可以通過網絡傳遞的,而我們計算時要通過網絡進行傳輸,所以key-value一定是可序列化的
利用Hadoop基於可寫的序列化機制向網絡進行寫入和寫出數據的操作
優化的網絡序列化
能夠通過重用可寫對象來減少對象創建開銷
提供了一組基本類型:
IntWritable,LongWriteable,FloatWritable,DoubleWritable,BooleanWritable,Text,NullWritable(沒有內容要寫出的時候使用NullWritable)等等
易於創建自定義Key-Value類型
Values必須繼承Writable接口:
void readFields(DataInput in)——反序列化字段
void write(DataOutput out)——序列化字段
Key必須實現WritableComparable接口:
Key是排序優先減少階段,因爲有排序所以Key一定是可比較的
void readFields(DataInput in)——反序列化字段
void write(DataOutput out)——序列化字段
int compareTo(T T)——將該對象與指定對象進行比較
int hashCode()——分區程序用於決定發送哪個還原程序的鍵-值對。
InputFormat接口:
InputFormat定義如何將數據從輸入讀入Mapper實例。
InputSplit[] getSplits(JobConf job, int numSplits) throws IOException
然後將每個InputSplit分配給一個單獨的Mapper進行處理
RecordReader<K,V> getRecordReader(InputSplit split, JobConf job,Reporter reporter) throws IOException
獲取給定InputSplit的RecordReader
RecordReader有責任在處理邏輯拆分時遵守記錄邊界,以便向單個工作提供record-oriented視圖。
輸入格式:
TextInputFormat使用最多,NLineInputFormat是讀取多行記錄,
Mapper類:
protected void setup(org.apache.hadoop.mapreduce.Mapper.Context context) throws IOException, InterruptedException
初始化在任務開始時調用一次,mapreduce框架調用,可以進行一些設置。
protected void map(KEY key, VALUE value, Context context) throws IOException, InterruptedException
對輸入分割中的每個鍵/值對調用一次。大多數應用程序都應該覆蓋它,但是默認情況下是同一的函數
protected void cleanup(Context context) throws IOException, InterruptedException
在任務結束時調用一次,清除
public void run(Context context) throws IOException, InterruptedException
專家用戶可以重寫此方法,以便更完整地控制Mapper的執行,可以進行處理
示例:
public class WCMapper extends Mapper<LongWritable, Text, Text, IntWritable>{
// LongWritable, Text是輸入,Text, IntWritable是輸出
private final static IntWritable one = new IntWritable(1);
//每次多一個對象就是1
private Text word = new Text();
//提前創造好對象
public void map(LongWritable key, Text value, Context ctx) throws IOException, InterruptedException{
// LongWritable是輸入Key,Text是輸入value,Context是和Reduce框架進行溝通的
StringTokenizer itr = new StringTokenizer( value.toString() );
//把文本分成一行一行的集合
while ( itr.hasMoreTokens() ){
word.set( itr.nextToken() );
//判斷如果有下一個Token就設置進Text中並且設一個值爲1
ctx.write( word, one); } } }
Combiner(可選的):
Combiner是MapReduce中的一種優化,它允許在洗牌和排序階段之前進行本地聚合。
Combiner只能用於以下函數:
交換律:a.b = b.a
結合律:a.(b.c) = (a.b).c
Combiner與Reducer具有相同的接口,因此在大多數情況下只要滿足2號要求,Reducers就可以作爲Combiner使用。
job.setCombinerClass( WCReducer.class )
Partitioner類:
當Mapper輸出被收集時,它將被分區,這意味着它將被寫入Partitioner指定的輸出
Partitioners負責劃分中間鍵空間,並將中間鍵值對分配給reducers
默認的Partitioner包括計算鍵的哈希值,然後使用該值與reducers數量的模。
Reducer類:
protected void setup(org.apache.hadoop.mapreduce.Mapper.Context context) throws IOException, InterruptedException
在任務開始時調用一次。
protected void reduce(KEY key, Iterable<VALUE> values, Context context) throws IOException, InterruptedException
爲每個鍵調用一次。大多數應用程序都應該覆蓋它,但是默認情況下是同一的函數
protected void cleanup(Context context) throws IOException, InterruptedException
在任務結束時調用一次,清除
public void run(.Context context) throws IOException, InterruptedException
高級應用程序編寫人員可以使用run方法來控制reduce任務的工作方式
示例:
public class WCReducer extends Reducer< Text , IntWritable, Text, IntWritable > {
private IntWritable result = new IntWritable();
public void reduce(Text key, Iterable< IntWritable > values, Context ctx) throws IOException, InterruptedException {
int sum = 0;
for ( IntWritable value : values ) { sum += value.get(); }
//把所有的數據加進來
result.set( sum );
//把整數寫進IntWirtable中,在寫到OutputFormat裏
ctx.write( key, result ); } }
輸出格式:
連接一個M/R工作
Configuration cfg = new Configuration();
Job job = Job.getInstance(getConf(), "WordCountMR" );
//告訴jar包是哪一些
job.setJarByClass( getClass() );
//初始輸入文件
FileInputFormat.setInputDirRecursive(job, true);
FileInputFormat.addInputPath(job, new Path(args[0]));
//初始輸入格式
job.setInputFormatClass( TextInputFormat.class );
//最終輸出路徑
FileOutputFormat.setOutputPath( job, new Path(args[1]) );
//最終輸出格式
job.setOutputFormatClass( TextOutputFormat.class );
//map任務處理類
job.setMapperClass( WCMapper.class );
//map輸出類型.
job.setMapOutputKeyClass( Text.class );
job.setMapOutputValueClass( IntWritable.class );
//reduce任務處理類
job.setReducerClass( WCReducer.class );
// reduce統一輸出類型
job.setOutputKeyClass( Text.class );
job.setOutputValueClass( IntWritable.class );
執行M/R工作
yarn jar hadoop-1.0-SNAPSHOT.jar WordCount /little_prince.txt /user/bda/output
設置Mapper的數量
java WCMR.jar /user/data /user/out –D mapred.map.tasks=5
設置reducer的數量
yarn jar WCMR.jar /user/data /user/out –D mapred.reduce.tasks=2
Job.setNumReduceTasks( 3 );
MapReduce有兩種方法:
Map-Side Join適用於一個大表和一個小表的情況A big table + a small table
Reduce-Side Join適用於兩個大表的情況two tables are big
分佈式緩存:
分佈式緩存有助於在執行M/R作業時向Hadoop集羣中的任務節點發送只讀文件。
文件可以是查找表、文本文件、jar等
文件的大小可以很小
文件每個工作會複製一份
示例:
放文件:
Job job = new Job();
job.addCacheFile(new Path(filename).toUri());
job.addCacheFile(new URI("/user/data/customer_types.json#customer_type"));
取文件:三種方法取一種
URI[] files = context.getCacheFiles();
Path filename = new Path(files[0])
File customerType = new File("./customer_type");
DistributedCache類已被棄用
/user/data/exchange_ rate.csv
job.addCacheFile("/user/data/exchange_ rate.csv")
把/user/data/exchange_ rate.csv這個文件的內容發到 DataNode (exchage_ rate.csv)上
job.addCacheFile("/user/data/exchange_ rate.csv#er.csv")
把/user/data/exchange_ rate.csv這個文件內容發到DataNode (er.csv)上
DataNode . >
erFile:File=new File("./exchage_ rate.csv")
erFile: File . new File(" ./er.csv")
Map-Side Join
a big table + a small table 1
small table:mapper -> setup ( erFile:File . new File("./er.csv") --> hashtable
big table:map( ... ) {
record(sc, dc) -> erFile }
預測執行(SE):
問題:當100個map任務中的99個已經完成時,系統仍然在等待最終的map任務簽入,這比所有其他節點花費的時間要長得多。
解決:使用SE
相同的輸入可以並行處理多次,以利用機器功能的差異。
將慢速任務的冗餘副本安排在沒有其他工作要執行的多個節點上。
任務最先完成的副本成爲最終副本。