目錄
Hadoop第0部分:分佈式存儲計算平臺及Hadoop入門
Hadoop第一部分:HDFS的構架和使用
Hadoop第二部分:MapReudce(一)
Hadoop第二部分:MapReudce(二)
Hadoop第二部分:MapReudce(三)
MapReudce(二)
本文項目地址:https://github.com/KingBobTitan/hadoop.git
Hadoop04:MR編程規則及原理深入
一、回顧
1、MapReduce
- 運行的兩個階段
- Map:由MapTask進程完成
- Input+Map+Map端的shuffle
- Reduce:由ReduceTask進程完成
- Reduce端的shuffle+Reduce+Output
- Map:由MapTask進程完成
- 邏輯的五個階段
- Input
- InputFormat
- 1-將所有給定的數據源轉換成keyvalue
- 2-進行任務的拆分:計算:每個小的任務:分片:split
- 假設:100萬條數據,按1萬條數據1個split,共100個split
- 存儲:每個小的 存儲:分塊:block
- 默認:TextInputFormat
- InputFormat
- Map:第一個處理邏輯
- Mapper<keyin,valuein,keyout,valueout>
- 1-根據Input階段中Split的個數,來啓動對應的 MapTask :1個split = 1MapTask
- 100split = 100 MapTask : 每個mapTask處理1萬條數據
- 2-負責所有數據的第一步轉換:轉換邏輯:map方法決定
- 填空:由需求決定map方法怎麼寫
- Shuffle:第二個處理邏輯,自動實現三大功能
- 分區:Partitioner
- 當有多個reduce的情況下,決定了當前的這條數據會被哪個reduce進行處理
- 默認方式:按照key的hash分區
- Hash分區存在的問題:導致數據傾斜
- 自定義分區
- 繼承Partitioner<key,value>
- 重寫方法:pubilc int getPartition(key,value,reduceNumber)
- 返回對應的 分區編號
- 排序:Sort
- 對key進行排序
- 默認方式:對key按照字典順序排序
- 分組:Group
- 對所有的keyvalue,按照key進行分組,每組就是每個key只有一條,相同key的value放入同一個迭代器中
- 分區:Partitioner
- Reduce:第三個處理邏輯
- Reducer<keyin,valuein,keyout,valueout>
- 用於啓動一個ReduceTask來實現將之前所有拆分的數據進行合併
- 合併邏輯:reduce,根據業務來決定reduce方法的實現
- Output
- OutputFormat
- TextOutputFormat:將keyvalue寫入文件,並且key與value之間用製表符分隔
- 將Reduce的結果進行輸出
- Input
2、編程規則
- 數據類型:Hadoop的序列化類型,Text、IntWritable……
- 數據結構:KeyValue
- 編程模型:
- Driver類
- 必須包含main,作爲程序的入口
- main:作爲程序入口,負責調用run方法
- run:初始化一個job,配置job,提交job的運行
- 只有job被提交之後,後面五大階段纔開始運行
- Input類:TextInputFormat
- Map類:map方法
- Shuffle
- 分區器:HashPartitioner
- 排序器
- 分組器
- Reduce類:reduce方法
- Output:TextOutputFormat
- Driver類
二、課程目標
- MapReduce程序歷史監控【掌握】
- jobhistoryserver:用於監控所有運行過的程序的日誌信息
- MapReduce中自定義數據類型【重點】
- 自定義Hadoop中的序列化類型,封裝JavaBean
- 排序
- 手機流量分析案例【掌握】
- Shuffle過程初探【瞭解】
三、MapReduce程序歷史監控
1、MapReduce的HistoryServer的功能
- 問題:每次YARN重啓以後,之前運行過的程序就看不到了,並且看不到每個程序具體的運行的信息
- 原因:每個程序的具體運行信息由單獨的一個進程來管理:JobHistoryServer
- 管理所有運行過的程序的運行日誌
- 解決:手動配置JobHitoryServer
2、YARN日誌聚集的功能
- 將所有程序的運行日誌存儲在HDFS上
3、配置啓動測試
-
先配置MapReduce的JobHitosryServer
-
修改mapred-site.xml
<!--配置JobHistoryServer進程的地址--> <property> <name>mapreduce.jobhistory.address</name> <value>node-02:10020</value> </property> <property> <name>mapreduce.jobhistory.webapp.address</name> <value>node-02:19888</value> </property>
-
-
再配置YARN的日誌聚集功能
-
修改yarn-site.xml
<!--配置日誌聚集的啓動--> <property> <name>yarn.log-aggregation-enable</name> <value>true</value> </property> <!--配置日誌聚集的自動刪除--> <property> <name>yarn.log-aggregation.retain-seconds</name> <value>604800</value> </property>
-
-
分發
cd /export/servers/hadoop-2.6.0-cdh5.14.0/etc/hadoop/ scp mapred-site.xml yarn-site.xml node-02:$PWD scp mapred-site.xml yarn-site.xml node-03:$PWD
-
啓動測試
-
在第一臺機器啓動hdfs
-
在第二臺機器啓動JobHistoryServer
sbin/mr-jobhistory-daemon.sh start historyserver
-
在第三臺機器啓動yarn
-
四、MapReduce中自定義數據類型
1、Hadoop中的類型
- 爲什麼不用Java中的類型?
- 因爲Hadoop是分佈式的,所有的數據類型要經過網絡傳輸,必須序列化
- String age= “1”
- 不做序列化:A -> 1 ->B -> B只知道數據是什麼
- 做序列化:A -> 對象:String age= “1” ->B知道數據是什麼,也知道類型
- 因爲Hadoop是分佈式的,所有的數據類型要經過網絡傳輸,必須序列化
- Hadoop中提供了哪些序列化類型?
- Text、NullWritable、IntWritable、DoubleWritable、BooleanWritable……
- 爲什麼要自定義數據類型?
- 因爲整個Hadoop中所有數據只能以keyvalue進行傳輸
- Hadoop自帶的類型,只能保存一個字段
- 如果我們需要傳輸計算多個字段 N>2,無法實現【拼接字符串】
- 所有Hadoop中也提供了自定義序列化類型的接口
2、自定義數據類型
- MapReduce程序的兩種類型
- 沒有shuffle:Input -> Map -> Output
- 一般用於ETL的過程:數據清洗
- 原來的數據中有100個字段,只需要50列
- 輸入多少行,就輸出多少行
- 原來的數據中有100個字段,只需要50列
- 一般用於ETL的過程:數據清洗
- 有shuffle:Input -> Map -> Shuffle -> Reduce -> Output
- 沒有shuffle:Input -> Map -> Output
- 自定義數據類型的規則
- 第一種:實現Writable的接口
- 定義屬性
- 重寫方法:
- 構造
- get and set
- 序列化:write
- 反序列化:readFields
- toString
- 第二種:實現WritableComparable接口
- 定義屬性
- 重寫方法:
- 構造
- get and set
- 序列化:write
- 反序列化:readFields
- toString
- compareTo:用於比較兩個相同的對象
- 區分:什麼時候需要用第一種,什麼時候用第二種
- 當前這個自定義的類型是否會作爲Map輸出的Key經過shuffle
- 如果是:用第二種
- 如果不是:用第一種
- 弄不清楚:全部用第二種
- 當前這個自定義的類型是否會作爲Map輸出的Key經過shuffle
- 第一種:實現Writable的接口
- 需求1:將wordcount程序中Map階段的結果輸出,並且有三列:單詞、單詞長度、1
- key:單詞、單詞長度
- value:1
- 需求2:將wordcount程序中Reduce的結果輸出,並且有三列:單詞、單詞長度、單詞個數
3、測試
-
將wordcount中map的結果進行輸出,包含三列:單詞、單詞長度、1
-
自定義數據類型:實現Writable接口
/** * @ClassName WordCountBean * @Description TODO 自定義數據類型,用於封裝單詞和單詞長度 * @Date 2020/1/11 11:01 * @Create By Frank */ public class WordCountBean implements Writable { //定義屬性 private String word; private int length ; //構造 public WordCountBean() { } //一次性給所有屬性賦值 public void setALL(String word, int length) { this.setWord(word); this.setLength(length); } //get and set public String getWord() { return word; } public void setWord(String word) { this.word = word; } public int getLength() { return length; } public void setLength(int length) { this.length = length; } //序列化 @Override public void write(DataOutput out) throws IOException { //輸出兩個對象 out.writeUTF(this.word); out.writeInt(this.length); } //反序列化:注意順序必須與序列化的順序保持一致,不然會導致值錯亂 @Override public void readFields(DataInput in) throws IOException { //讀出兩個對象 this.word = in.readUTF(); this.length = in.readInt(); } //toString方法,在輸出的 時候會被調用,寫入文件或者打印 @Override public String toString() { return this.word+"\t"+this.length; } }
-
輸出Map的結果
/** * @ClassName WordCount * @Description TODO 用於實現Wordcount的MapReduce * @Date 2020/1/9 16:00 * @Create By Frank */ public class WordCountMapOut extends Configured implements Tool { /** * 構建一個MapReduce程序,配置程序,提交程序 * @param args * @return * @throws Exception */ @Override public int run(String[] args) throws Exception { /** * 第一:構造一個MapReduce Job */ //構造一個job對象 Job job = Job.getInstance(this.getConf(),"mrword"); //設置job運行的類 job.setJarByClass(WordCountMapOut.class); /** * 第二:配置job */ //input:設置輸入的類以及輸入路徑 // job.setInputFormatClass(TextInputFormat.class); 這是默認的 Path inputPath = new Path("datas/word/count.txt");//以程序的第一個參數作爲輸入路徑 TextInputFormat.setInputPaths(job,inputPath); //shuffle //map job.setMapperClass(WordCountMapper.class);//指定Mapper的類 job.setMapOutputKeyClass(WordCountBean.class);//指定map輸出的key的類型 job.setMapOutputValueClass(IntWritable.class);//指定map輸出的value的類型 //shuffle //reduce // job.setReducerClass(WordCountReduce.class);//指定reduce的類 // job.setOutputKeyClass(Text.class);//指定reduce輸出的 key類型 // job.setOutputValueClass(IntWritable.class);//指定reduce輸出的value類型 job.setNumReduceTasks(0);//這是默認的,沒有reduce記得設置爲0 //output // job.setOutputFormatClass(TextOutputFormat.class);//這是默認的輸出類 Path outputPath = new Path("datas/output/mapoutpupt");//用程序的第二個參數作爲輸出路徑 //如果輸出目錄已存在,就刪除 FileSystem hdfs = FileSystem.get(this.getConf()); if(hdfs.exists(outputPath)){ hdfs.delete(outputPath,true); } //設置輸出的地址 TextOutputFormat.setOutputPath(job,outputPath); /** * 第三:提交job */ //提交job運行,並返回boolean值,成功返回true,失敗返回false return job.waitForCompletion(true) ? 0 : -1; } /** * 整個程序的入口,負責調用當前類的run方法 * @param args */ public static void main(String[] args) { //構造一個conf對象,用於管理當前程序的所有配置 Configuration conf = new Configuration(); try { //調用當前類的run方法 int status = ToolRunner.run(conf, new WordCountMapOut(), args); //根據程序運行的 結果退出 System.exit(status); } catch (Exception e) { e.printStackTrace(); } } /** * Mapper的類,實現四個泛型,inputkey,inputValue,outputKey,outputValue * 輸入的泛型:由輸入的類決定:TextInputFormat:Longwritable Text * 輸出的泛型:由代碼邏輯決定:Text,IntWritable * 重寫map方法 */ public static class WordCountMapper extends Mapper<LongWritable, Text, WordCountBean, IntWritable>{ //構造用於輸出的key和value private WordCountBean outputKey = new WordCountBean(); private IntWritable outputValue = new IntWritable(1); /** * map方法:Input傳遞過來的每一個keyvalue會調用一次map方法 * @param key:當前的 key * @param value:當前的value * @param context:上下文,負責將新的keyvalue輸出 * @throws IOException * @throws InterruptedException */ @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //將每一行的內容轉換爲String String line = value.toString(); //對每一行的內容分割 String[] words = line.split(" "); //迭代取出每個單詞 for(String word:words){ //將單詞賦值給key this.outputKey.setWord(word); this.outputKey.setLength(word.length()); //輸出 context.write(this.outputKey,this.outputValue); } } } }
-
-
將wordcount中reduce的結果進行輸出,包含三列:單詞、單詞長度、個數
-
自定義數據類型:實現WritableComparable接口
/** * @ClassName WordCountBean * @Description TODO 自定義數據類型,用於封裝單詞和單詞長度 * @Date 2020/1/11 11:01 * @Create By Frank */ public class WordCountBean implements WritableComparable<WordCountBean> { //定義屬性 private String word; private int length ; //構造 public WordCountBean() { } //一次性給所有屬性賦值 public void setALL(String word, int length) { this.setWord(word); this.setLength(length); } //get and set public String getWord() { return word; } public void setWord(String word) { this.word = word; } public int getLength() { return length; } public void setLength(int length) { this.length = length; } //序列化 @Override public void write(DataOutput out) throws IOException { //輸出兩個對象 out.writeUTF(this.word); out.writeInt(this.length); } //反序列化:注意順序必須與序列化的順序保持一致,不然會導致值錯亂 @Override public void readFields(DataInput in) throws IOException { //讀出兩個對象 this.word = in.readUTF(); this.length = in.readInt(); } //toString方法,在輸出的 時候會被調用,寫入文件或者打印 @Override public String toString() { return this.word+"\t"+this.length; } //用於在shuffle階段的排序以及分組 @Override public int compareTo(WordCountBean o) { //先比較第一個值 int comp = this.getWord().compareTo(o.getWord()); if(0 == comp){ //如果第一個值,相等,以第二個值的比較結果作爲最終的結果 return Integer.valueOf(this.getLength()).compareTo(Integer.valueOf(o.getLength())); } return comp; } }
-
輸出reduce的結果
/** * @ClassName WordCount * @Description TODO 用於實現Wordcount的MapReduce * @Date 2020/1/9 16:00 * @Create By Frank */ public class WordCountMapOut extends Configured implements Tool { /** * 構建一個MapReduce程序,配置程序,提交程序 * @param args * @return * @throws Exception */ @Override public int run(String[] args) throws Exception { /** * 第一:構造一個MapReduce Job */ //構造一個job對象 Job job = Job.getInstance(this.getConf(),"mrword"); //設置job運行的類 job.setJarByClass(WordCountMapOut.class); /** * 第二:配置job */ //input:設置輸入的類以及輸入路徑 // job.setInputFormatClass(TextInputFormat.class); 這是默認的 Path inputPath = new Path("datas/word/count.txt");//以程序的第一個參數作爲輸入路徑 TextInputFormat.setInputPaths(job,inputPath); //shuffle //map job.setMapperClass(WordCountMapper.class);//指定Mapper的類 job.setMapOutputKeyClass(WordCountBean.class);//指定map輸出的key的類型 job.setMapOutputValueClass(IntWritable.class);//指定map輸出的value的類型 //shuffle //reduce job.setReducerClass(WordCountReduce.class);//指定reduce的類 job.setOutputKeyClass(WordCountBean.class);//指定reduce輸出的 key類型 job.setOutputValueClass(IntWritable.class);//指定reduce輸出的value類型 job.setNumReduceTasks(1);//這是默認的,沒有reduce記得設置爲0 //output // job.setOutputFormatClass(TextOutputFormat.class);//這是默認的輸出類 Path outputPath = new Path("datas/output/reduceoutput");//用程序的第二個參數作爲輸出路徑 //如果輸出目錄已存在,就刪除 FileSystem hdfs = FileSystem.get(this.getConf()); if(hdfs.exists(outputPath)){ hdfs.delete(outputPath,true); } //設置輸出的地址 TextOutputFormat.setOutputPath(job,outputPath); /** * 第三:提交job */ //提交job運行,並返回boolean值,成功返回true,失敗返回false return job.waitForCompletion(true) ? 0 : -1; } /** * 整個程序的入口,負責調用當前類的run方法 * @param args */ public static void main(String[] args) { //構造一個conf對象,用於管理當前程序的所有配置 Configuration conf = new Configuration(); try { //調用當前類的run方法 int status = ToolRunner.run(conf, new WordCountMapOut(), args); //根據程序運行的 結果退出 System.exit(status); } catch (Exception e) { e.printStackTrace(); } } /** * Mapper的類,實現四個泛型,inputkey,inputValue,outputKey,outputValue * 輸入的泛型:由輸入的類決定:TextInputFormat:Longwritable Text * 輸出的泛型:由代碼邏輯決定:Text,IntWritable * 重寫map方法 */ public static class WordCountMapper extends Mapper<LongWritable, Text, WordCountBean, IntWritable>{ //構造用於輸出的key和value private WordCountBean outputKey = new WordCountBean(); private IntWritable outputValue = new IntWritable(1); /** * map方法:Input傳遞過來的每一個keyvalue會調用一次map方法 * @param key:當前的 key * @param value:當前的value * @param context:上下文,負責將新的keyvalue輸出 * @throws IOException * @throws InterruptedException */ @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //將每一行的內容轉換爲String String line = value.toString(); //對每一行的內容分割 String[] words = line.split(" "); //迭代取出每個單詞 for(String word:words){ //將單詞賦值給key this.outputKey.setWord(word); this.outputKey.setLength(word.length()); //輸出 context.write(this.outputKey,this.outputValue); } } } /** * 所有的Reduce都需要實現四個泛型 * 輸入的keyvalue:就是Map的 輸出的keyvalue類型 * 輸出的keyvalue:由代碼邏輯決定 * 重寫reduce方法 */ public static class WordCountReduce extends Reducer<WordCountBean, IntWritable,WordCountBean, IntWritable>{ private IntWritable outputValue = new IntWritable(); /** * reduce方法 ,每一個keyvalue,會調用一次reduce方法 * @param key:傳進來的key * @param values:迭代器,當前key的所有value * @param context * @throws IOException * @throws InterruptedException */ @Override protected void reduce(WordCountBean key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { //取出迭代器的值進行累加 int sum = 0; for (IntWritable value : values) { sum += value.get(); } //封裝成輸出的value this.outputValue.set(sum); //輸出每一個key的結果 context.write(key,outputValue); } } }
-
-
基於各地區的二手房個數、平均單價、最低單價、最高單價
-
第一步:看結果
- 地區 個數 平均 最低 最高
-
第二步:看分組或者排序
- 按照地區分組:key
-
第三步:看其他的結果字段定value:單價
-
第四步:帶入
-
input
-
map
- 邏輯:將每一行的地區和單價這兩個字段取出
- key:地區
- value:單價
-
shuffle
- 分組:相同地區的所有房子的單價放入同一個迭代器
-
reduce(key,Iterator<每套房子的單價>)
-
邏輯
-
將迭代器中的每條單價取出,放入集合
//注意:不能直接將迭代器中的類型直接放入集合,取裏面的基本類型的值放入 List lists = new ArrayList<Double> for(DoubleWritable value : values){ lists.add(value.get()) count++ sum+= value.get() } Collections.sort(lists)
-
個數:count
-
平均: sum/count
-
最低:lists.get(0)
-
最高:lists.get(list.size-1)
-
-
-
output
-
-
-
排序的兩種方式
-
如果是自定義數據類型:可以調用該類型中的compareTo方法實現排序
-
官方默認的排序器:對key進行升序
-
官方提供了一個排序器的接口
-
job.setSortComparatorClass(null);//自定義排序規則
-
需求:希望實現對WordCount的結果進行key的降序
-
實現
-
先自定義一個 排序器
import org.apache.hadoop.io.Text; import org.apache.hadoop.io.WritableComparable; import org.apache.hadoop.io.WritableComparator; /** * @ClassName UserSort * @Description TODO 用戶自定義排序器 * @Date 2020/1/11 11:47 * @Create By Frank */ public class UserSort extends WritableComparator { /** * 註冊類型的轉換 */ public UserSort() { //類型的轉換註冊,允許將類型轉換爲Text super(Text.class,true); } /** * 調用排序的方法 * @param a * @param b * @return */ @Override public int compare(WritableComparable a, WritableComparable b) { //需求:比較Text類型,傳入WritableComparable,要進行轉換 Text o1 = (Text) a; Text o2 = (Text) b; //降序 return -o1.compareTo(o2); } }
-
-
-
-
加載排序器並實現業務邏輯
job.setSortComparatorClass(UserSort.class);//自定義排序規則
-
源碼中調用排序的邏輯
- 是否有自定義排序器
- 有:就用自定義排序器
- 沒有:進入下一步判斷
- 判斷該類型是否有comparaTo方法
- 有:就用compareTo方法
- 沒有:調用默認的key的字典順序升序排序器
- 有:就用自定義排序器
- 是否有自定義排序器
五、手機流量分析案例
1、需求
- 基於給定的數據統計實現每個手機號的上行包總和、下行包總和、上行流量總和、下行流量總和
- 基於第一個需求的結果來進行處理,將結果數據按照上行包總和進行降序排序
- 基於第一個程序的結果,將數據寫入不同的文件
2、需求1的實現
-
分析
-
第一步:看結果
手機號 上行總包 下行總包 上行總流量 下行總流量
-
第二步:看需求中有沒有分組或者排序
- 分組:每個手機號
- Map輸出的key就決定了
-
第三步:看剩餘的結果中需要使用哪些字段來實現
-
第四步:分析驗證
- Input:讀取文件,變成keyvalue對
- Map:map方法
- 邏輯
- 對每條數據進行分割
- 取出需要用到的五個字段
- 後面四個字段封裝javabean
- 輸出
- key:手機號
- value:上行包、下行包、上行流量、下行流量
- 邏輯
- Shuffle
- 分區:本質是打標籤
- 排序:按照手機號碼進行排序
- 分組
- 相同手機號碼的所有數據都放入了一個迭代器中
- Reduce
- 從迭代器中取出每個業務值,進行業務累加即可
- Output
-
實現
-
自定義數據類型
/** 流量案例1的自定義數據類型 */ public class FlowBean1 implements Writable { private long upPack; private long downPack; private long upFlow; private long downFlow; public FlowBean1() { } public void setAll(long upPack, long downPack, long upFlow, long downFlow) { this.setUpPack(upPack); this.setDownPack(downPack); this.setUpFlow(upFlow); this.setDownFlow(downFlow); } public long getUpPack() { return upPack; } public void setUpPack(long upPack) { this.upPack = upPack; } public long getDownPack() { return downPack; } public void setDownPack(long downPack) { this.downPack = downPack; } public long getUpFlow() { return upFlow; } public void setUpFlow(long upFlow) { this.upFlow = upFlow; } public long getDownFlow() { return downFlow; } public void setDownFlow(long downFlow) { this.downFlow = downFlow; } @Override public void write(DataOutput out) throws IOException { out.writeLong(this.upPack); out.writeLong(this.downPack); out.writeLong(this.upFlow); out.writeLong(this.downFlow); } @Override public void readFields(DataInput in) throws IOException { this.upPack = in.readLong(); this.downPack = in.readLong(); this.upFlow = in.readLong(); this.downFlow = in.readLong(); } @Override public String toString() { return this.upPack+"\t"+this.downPack+"\t"+this.upFlow+"\t"+this.downFlow; } }
-
mr實現
/** *實現流量案例的需求1 */ public class FlowMr1 extends Configured implements Tool { //構建一個job,配置job,提交運行job @Override public int run(String[] args) throws Exception { //構建 Job job = Job.getInstance(this.getConf(),"flow1"); job.setJarByClass(FlowMr1.class); //配置 Path inputPath = new Path("datas/flowCase/data_flow.dat"); TextInputFormat.setInputPaths(job,inputPath); job.setMapperClass(FlowMapper.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(FlowBean1.class); job.setReducerClass(FlowReduce.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(FlowBean1.class); Path outputPath = new Path("datas/output/flow/flow1"); TextOutputFormat.setOutputPath(job,outputPath); //提交 return job.waitForCompletion(true) ? 0 : -1; } //作爲程序的入口 public static void main(String[] args) throws Exception { //調用run方法 Configuration conf = new Configuration(); int status = ToolRunner.run(conf, new FlowMr1(), args); System.exit(status); } public static class FlowMapper extends Mapper<LongWritable, Text,Text, FlowBean1>{ private Text outputKey = new Text(); private FlowBean1 outputValue = new FlowBean1(); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //分割 String[] items = value.toString().split("\t"); //先做過濾 if(items.length >= 11){ //合法的數據有多少 context.getCounter("userlog","legal line").increment(1L); this.outputKey.set(items[1]);//將手機號作爲key this.outputValue.setAll(Long.valueOf(items[6]),Long.valueOf(items[7]),Long.valueOf(items[8]),Long.valueOf(items[9])); //輸出 context.write(this.outputKey,this.outputValue); }else{ //不合法的數據有多少 context.getCounter("userlog","illegal line").increment(1L); return;//如果 不合法,直接return,不處理該條數據,直接取下一條 } } } public static class FlowReduce extends Reducer<Text, FlowBean1,Text, FlowBean1>{ private FlowBean1 outputValue = new FlowBean1(); @Override protected void reduce(Text key, Iterable<FlowBean1> values, Context context) throws IOException, InterruptedException { long sumUpPack = 0; long sumDownPack = 0; long sumUpFlow = 0; long sumDownFlow = 0; for (FlowBean1 value : values) { sumUpPack += value.getUpPack(); sumDownPack += value.getDownPack(); sumUpFlow += value.getUpFlow(); sumDownFlow += value.getDownFlow(); } this.outputValue.setAll(sumUpPack,sumDownPack,sumUpFlow,sumDownFlow); //輸出 context.write(key,outputValue); } } }
-
-
3、需求2的實現
-
第一步:看結果
手機號 上行總包 下行總包 上行總流量 下行總流量
-
第二步:有沒有分組或者排序
- 排序:上行包總和,key中必須包含上行包總和
- key:將五個字段都作爲key中內容
-
第三步:分析value
- 爲NullWritable
-
第四步:分析
- Input:讀上個程序的結果
- Map:
- 處理邏輯
- 分割每一行的內容放入自定義數據類型
- key:整條數據
- value:null
- 處理邏輯
- Shuffle
- 排序:調用compareTo,按照上行總包進行降序排序
- 分組:不需要分組
- Reduce:沒有reduce
- Output
-
實現
-
自定義數據類型的實現
/** * 流量案例1的自定義數據類型 */ public class FlowBean2 implements WritableComparable<FlowBean2> { private String phone; private long upPack; private long downPack; private long upFlow; private long downFlow; public FlowBean2() { } public void setAll(String phone,long upPack, long downPack, long upFlow, long downFlow) { this.setPhone(phone); this.setUpPack(upPack); this.setDownPack(downPack); this.setUpFlow(upFlow); this.setDownFlow(downFlow); } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public long getUpPack() { return upPack; } public void setUpPack(long upPack) { this.upPack = upPack; } public long getDownPack() { return downPack; } public void setDownPack(long downPack) { this.downPack = downPack; } public long getUpFlow() { return upFlow; } public void setUpFlow(long upFlow) { this.upFlow = upFlow; } public long getDownFlow() { return downFlow; } public void setDownFlow(long downFlow) { this.downFlow = downFlow; } @Override public void write(DataOutput out) throws IOException { out.writeUTF(this.phone); out.writeLong(this.upPack); out.writeLong(this.downPack); out.writeLong(this.upFlow); out.writeLong(this.downFlow); } @Override public void readFields(DataInput in) throws IOException { this.phone = in.readUTF(); this.upPack = in.readLong(); this.downPack = in.readLong(); this.upFlow = in.readLong(); this.downFlow = in.readLong(); } @Override public String toString() { return this.phone+"\t"+this.upPack+"\t"+this.downPack+"\t"+this.upFlow+"\t"+this.downFlow; } @Override public int compareTo(FlowBean2 o) { //按照上行總包數進行排序 return -Long.valueOf(this.getUpPack()).compareTo(Long.valueOf(o.getUpPack())); } }
-
mr實現
/** * 實現流量案例的需求2 */ public class FlowMr2 extends Configured implements Tool { //構建一個job,配置job,提交運行job @Override public int run(String[] args) throws Exception { //構建 Job job = Job.getInstance(this.getConf(),"flow1"); job.setJarByClass(FlowMr2.class); //配置 Path inputPath = new Path("datas/output/flow/flow1"); TextInputFormat.setInputPaths(job,inputPath); job.setMapperClass(FlowMapper.class); job.setMapOutputKeyClass(FlowBean2.class); job.setMapOutputValueClass(NullWritable.class); // job.setReducerClass(FlowReduce.class); // job.setOutputKeyClass(Text.class); // job.setOutputValueClass(FlowBean1.class); job.setNumReduceTasks(1); Path outputPath = new Path("datas/output/flow/flow2"); TextOutputFormat.setOutputPath(job,outputPath); //提交 return job.waitForCompletion(true) ? 0 : -1; } //作爲程序的入口 public static void main(String[] args) throws Exception { //調用run方法 Configuration conf = new Configuration(); int status = ToolRunner.run(conf, new FlowMr2(), args); System.exit(status); } public static class FlowMapper extends Mapper<LongWritable, Text, FlowBean2, NullWritable>{ private FlowBean2 outputKey = new FlowBean2(); private NullWritable outputValue = NullWritable.get(); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //分割 String[] items = value.toString().split("\t"); //先做過濾 if(items.length == 5){ //合法的數據有多少 context.getCounter("userlog","legal line").increment(1L); //將五個字段封裝作爲key this.outputKey.setAll(items[0],Long.valueOf(items[1]),Long.valueOf(items[2]),Long.valueOf(items[3]),Long.valueOf(items[4])); //輸出 context.write(this.outputKey,this.outputValue); }else{ //不合法的數據有多少 context.getCounter("userlog","illegal line").increment(1L); return;//如果 不合法,直接return,不處理該條數據,直接取下一條 } } } public static class FlowReduce extends Reducer<Text, FlowBean1,Text, FlowBean1>{ private FlowBean1 outputValue = new FlowBean1(); @Override protected void reduce(Text key, Iterable<FlowBean1> values, Context context) throws IOException, InterruptedException { long sumUpPack = 0; long sumDownPack = 0; long sumUpFlow = 0; long sumDownFlow = 0; for (FlowBean1 value : values) { sumUpPack += value.getUpPack(); sumDownPack += value.getDownPack(); sumUpFlow += value.getUpFlow(); sumDownFlow += value.getDownFlow(); } this.outputValue.setAll(sumUpPack,sumDownPack,sumUpFlow,sumDownFlow); //輸出 context.write(key,outputValue); } } }
-
4、需求3的實現:
-
基於第一個程序的結果,將數據寫入不同的文件
-
第一步:看結果
手機號 上行總包 下行總包 上行總流量 下行總流量
-
第二步:有沒有排序和分組:沒有,是以一對一的關係,只有分區
-
實現
-
自定義分區
/** 自定義分區規則 */ public class FlowParitioner extends Partitioner<FlowBean2, NullWritable> { @Override public int getPartition(FlowBean2 key, NullWritable value, int numPartitions) { //所有134開頭寫入分區0 //135,136寫入分區1 //其他寫入分區2 String phone = key.getPhone(); if(phone.startsWith("134")){ return 0; }else if(phone.startsWith("135") || phone.startsWith("136")){ return 1; }else return 2; } }
-
-
mr的實現
/**
實現流量案例的需求3
*/
public class FlowMr3 extends Configured implements Tool {
//構建一個job,配置job,提交運行job
@Override
public int run(String[] args) throws Exception {
//構建
Job job = Job.getInstance(this.getConf(),"flow3");
job.setJarByClass(FlowMr3.class);
//配置
Path inputPath = new Path("datas/output/flow/flow1");
TextInputFormat.setInputPaths(job,inputPath);
job.setMapperClass(FlowMapper.class);
job.setMapOutputKeyClass(FlowBean2.class);
job.setMapOutputValueClass(NullWritable.class);
job.setPartitionerClass(FlowParitioner.class);
job.setReducerClass(FlowReduce.class);
job.setOutputKeyClass(FlowBean2.class);
job.setOutputValueClass(NullWritable.class);
job.setNumReduceTasks(3);
Path outputPath = new Path("datas/output/flow/flow3");
TextOutputFormat.setOutputPath(job,outputPath);
//提交
return job.waitForCompletion(true) ? 0 : -1;
}
//作爲程序的入口
public static void main(String[] args) throws Exception {
//調用run方法
Configuration conf = new Configuration();
int status = ToolRunner.run(conf, new FlowMr3(), args);
System.exit(status);
}
public static class FlowMapper extends Mapper<LongWritable, Text, FlowBean2, NullWritable>{
private FlowBean2 outputKey = new FlowBean2();
private NullWritable outputValue = NullWritable.get();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//分割
String[] items = value.toString().split("\t");
//先做過濾
if(items.length == 5){
//合法的數據有多少
context.getCounter("userlog","legal line").increment(1L);
//將五個字段封裝作爲key
this.outputKey.setAll(items[0],Long.valueOf(items[1]),Long.valueOf(items[2]),Long.valueOf(items[3]),Long.valueOf(items[4]));
//輸出
context.write(this.outputKey,this.outputValue);
}else{
//不合法的數據有多少
context.getCounter("userlog","illegal line").increment(1L);
return;//如果 不合法,直接return,不處理該條數據,直接取下一條
}
}
}
public static class FlowReduce extends Reducer<FlowBean2, NullWritable,FlowBean2, NullWritable>{
@Override
protected void reduce(FlowBean2 key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
//沒有聚合邏輯,直接輸出
context.write(key,NullWritable.get());
}
}
}
六、Shuffle過程初探
1、Shuffle的設計目的
-
基於統計的思想來設計的
-
以SQL的應用爲原型
select 1 from 2 where 3 group by 4 having 5 order by 6 limit 7 1-代表列的過濾,就是結果 2-代表數據源:從某張表或者某張臨時表中查詢 3-代表行的過濾 4-按什麼分組 5-對分組後的行進行過濾 6-用於排序 7-限制輸出
2、Shuffle的功能
- 分區:爲了多進程並行
- 默認reduce只有1個,如果Map輸出的數據非常的大,一個Reduce的負載就很高,效率很低
- 決定了當前的每一條數據歸哪個reduce進行處理
- 排序:符合統計分析的過程
- 分組:符合統計分析的過程
3、Shuffle的基本過程
-
Map:在Map階段對每條數據調用map方法之後,所有數據進入Map端的shuffle階段
-
Shuffle
-
Map端的Shuffle
- 排序
-
Reduce端的shuffle
-
分組
-
hadoop 1 hadoop 1 hive 1 hbase 1 hive 1 spark 1 hue 1 不排序直接分組: hadoop:1,1 每一組的比較次數:N-1 hadoop 1 hadoop 1 hive 1 hive 1 hbase 1 spark 1 hue 1 排序以後分組 hadoop:1,1 2
-
-
-
Reduce:在Reduce 階段對每條數據調用 reduce方法之前,數據在進行分組排序等操作爲Reduce端的shuffle