標籤:
通過API操作之前要先瞭解幾個基本知識
基礎資料型別 (Elementary Data Type)
Hadoop的基礎資料型別 (Elementary Data Type)和Java的基礎資料型別 (Elementary Data Type)是不一樣的,但是都存在對應的關係
如
如果需要定義自己的資料類型,則必須實現Writable
hadoop的資料類型可以通過get方法獲得對應的java資料類型
而java的資料類型可以通過hadoop資料類名的建構函式,或者set方法轉換
關於Hadoop的Writable介面,詳情請看Hadoop I/O中的序列化部分
MapReduce執行的基本步驟
Hadoop提交作業的的步驟分為八個,可以理解為天龍八步
Map端工作
- 1.1 讀取要操作的檔案–這步會將檔案的內容格式化成索引值對的形式,鍵為每一行的起始位置位移,值為每一行的內容。
- 1.2 調用map進行處理–在這步使用自訂的Mapper類來實現自己的邏輯,輸入的資料為1.1格式化的索引值對,輸入的資料也是索引值對的形式。
- 1.3 對map的處理結果進行分區–map處理完畢之後可以根據自己的業務需求來對索引值對進行分區處理,比如,將類型不同的結果儲存在不同的檔案中等。這裡設定幾個分區,後面就會有對應的幾個Reducer來處理相應分區中的內容。
- 1.4 分區之後,對每個分區的資料進行排序,分組–排序按照從小到大進行排列,排序完畢之後,會將索引值對中,key相同的選項 的value進行合并。如,所有的索引值對中,可能存在
hello 1
hello 1
key都是hello,進行合并之後變成
hello 2
可以根據自己的業務需求對排序和合并的處理進行幹涉和實現。
- 1.5 歸約(combiner)–簡單的說就是在map端進行一次reduce處理,但是和真正的reduce處理不同之處在於:combiner只能處理本機資料,不能跨網路處理。通過map端的combiner處理可以減少輸出的資料,因為資料都是通過網路傳輸的,其目的是為了減輕網路傳輸的壓力和後邊reduce的工作量。並不能取代reduce。
Reduce端工作
- 2.1 通過網路將資料copy到各個reduce。
- 2.2 調用reduce進行處理–reduce接收的資料是整個map端處理完畢之後的索引值對,輸出的也是索引值對的集合,是最終的結果。
- 2.3 將結果輸出到hdfs檔案系統的路徑中。
程式開發開發流程
一般情況下我們不可能直接在生產環境中直接拿海量資料進行程式開發,不說程式有沒有bug,能不能跑起來都是一個問題
所以通常步驟是:
1.本地程式開發測試:從海量資料中抽取小部分資料到本地,使用IDE等工具進行開發,並在測試資料集上進行程式運行、邏輯等測試
2.叢集環境運行:本地測試通過後,就可以將代碼運行在海量資料中,這時候90%的小bug已經得到修複了,剩餘要解決的就是生產環境中的問題了
3.調優:程式能夠在叢集中運行起來並不代表成功,通過一些叢集、程式調優方式可以讓你的代碼跑的更好、更快
導包
建立一個java項目,並匯入hadoop包,在項目選項上右鍵,選擇
找到hadoop的安裝目錄,選擇所有的包
在找到hadoop安裝目錄下的lib,匯入其中的所有包
代碼
建立JMapper類為自訂的Mapper類
import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapper; //自訂的Mapper類必須繼承Mapper類,並重寫map方法實現自己的邏輯 public class JMapper extends Mapper<LongWritable, Text, Text, LongWritable> { //處理輸入檔案的每一行都會調用一次map方法,檔案有多少行就會調用多少次 protected void map( LongWritable key, Text value, org.apache.hadoop.mapreduce.Mapper<LongWritable, Text, Text, LongWritable>.Context context) throws java.io.IOException, InterruptedException { //key為每一行的起始位移量 //value為每一行的內容 //每一行的內容分割,如hello world,分割成一個String數組有兩個資料,分別是hello,world String[] ss = value.toString().toString().split("\t"); //迴圈數組,將其中的每個資料當做輸出的鍵,值為1,表示這個鍵出現一次 for (String s : ss) { //context.write方法可以將map得到的索引值對輸出 context.write(new Text(s), new LongWritable(1)); } }; }
建立JReducer類為自訂的Reducer
import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Reducer; //自訂的Reducer類必須繼承Reducer,並重寫reduce方法實現自己的邏輯,泛型參數分別為輸入的鍵類型,實值型別;輸出的鍵類型,實值型別;之後的reduce類似 public class JReducer extends Reducer<Text, LongWritable, Text, LongWritable> { //處理每一個索引值對都會調用一次reduce方法,有多少個索引值對就調用多少次 protected void reduce( Text key, java.lang.Iterable<LongWritable> value, org.apache.hadoop.mapreduce.Reducer<Text, LongWritable, Text, LongWritable>.Context context) throws java.io.IOException, InterruptedException { //key為每一個單獨的單詞,如:hello,world,you,me等 //value為這個單詞在文本中出現的次數集合,如{1,1,1},表示總共出現了三次 long sum = 0; //迴圈value,將其中的值相加,得到總次數 for (LongWritable v : value) { sum += v.get(); } //context.write輸入新的索引值對(結果) context.write(key, new LongWritable(sum)); }; }
建立執行提交作業的類,取名JSubmit
import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.LongWritable; 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.input.TextInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat; public class JSubmit { public static void main(String[] args) throws IOException, URISyntaxException, InterruptedException, ClassNotFoundException { //Path類為hadoop API定義,建立兩個Path對象,一個輸入檔案的路徑,一個輸入結果的路徑 Path outPath = new Path("hdfs://localhost:9000/out"); //輸入檔案的路徑為本地linux系統的檔案路徑 Path inPath = new Path("/home/hadoop/word"); //建立預設的Configuration對象 Configuration conf = new Configuration(); //根據地址和conf得到hadoop的檔案系統獨享 //如果輸入路徑已經存在則刪除 FileSystem fs = FileSystem.get(new URI("hdfs://localhost:9000"), conf); if (fs.exists(outPath)) { fs.delete(outPath, true); } //根據conf建立一個新的Job對象,代表要提交的作業,作業名為JSubmit.class.getSimpleName() Job job = new Job(conf, JSubmit.class.getSimpleName()); //1.1 //FileInputFormat類設定要讀取的檔案路徑 FileInputFormat.setInputPaths(job, inPath); //setInputFormatClass設定讀取檔案時使用的格式化類 job.setInputFormatClass(TextInputFormat.class); //1.2調用自訂的Mapper類的map方法進行操作 //設定處理的Mapper類 job.setMapperClass(JMapper.class); //設定Mapper類處理完畢之後輸出的索引值對 的 資料類型 job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(LongWritable.class); //1.3分區,下面的兩行代碼寫和沒寫都一樣,預設的設定 job.setPartitionerClass(HashPartitioner.class); job.setNumReduceTasks(1); //1.4排序,分組 //1.5歸約,這三步都有預設的設定,如果沒有特殊的需求可以不管 //2.1將資料轉送到對應的Reducer //2.2使用自訂的Reducer類操作 //設定Reducer類 job.setReducerClass(JReducer.class); //設定Reducer處理完之後 輸出的索引值對 的資料類型 job.setOutputKeyClass(Text.class); job.setOutputValueClass(LongWritable.class); //2.3將結果輸出 //FileOutputFormat設定輸出的路徑 FileOutputFormat.setOutputPath(job, outPath); //setOutputFormatClass設定輸出時的格式化類 job.setOutputFormatClass(TextOutputFormat.class); //將當前的job對象提交 job.waitForCompletion(true); }
運行
運行java程式,可以再控制台看到提交作業的提示
在hdfs中查看輸出的檔案
程式調試
在叢集上偵錯工具是十分困難的,就算採用最常見的println列印資訊你也不知道該資訊會在叢集上哪個節點列印出來
在MapReduce中,可以使用系統錯誤資訊+計數器的組合來進行調試,在map或者reduce函數中,我們可以這樣做:
System.out.println("一些錯誤資訊");context.setStatus("關於錯誤的一些提示")context.getCounter(計數器組名一般為枚舉類型).increment(1);
在CLI中可以通過以下命令查看計數器:
mapred job -counter jobId counterGroup counterName
counterGroup:計數器組名,一般為枚舉類型的全類名
counterName:計數器名,一般為枚舉類型的值
效能調優
參考:MapReduce效能調優記錄
JobControl
開發MapReduce程式的時候,我們需要考慮如何把需求轉換為MapReduce模型來解決問題
對於一些複雜的情境,我們通常是使用多個Job來完成任務,而不是一個非常複雜的單一Job
所以一個完成的任務就可能會有多個Job,一個Job有可能有多個Mapper的情況,參考:
多個Mapper和Reducer處理多個輸入
開發技巧
參考:MapReduce開發技巧
更多應用情境
下面是自己在學習過程中收集整理的一些MapReduce情境Demo,可以提供參考和協助:
GitHub - chubbyjiang/MapReduce: MapReduce Demo
@小黑
MapReduce程式開發