Hadoop第二部分:MapReudce(二)

目錄
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
  • 邏輯的五個階段
    • Input
      • InputFormat
        • 1-將所有給定的數據源轉換成keyvalue
        • 2-進行任務的拆分:計算:每個小的任務:分片:split
          • 假設:100萬條數據,按1萬條數據1個split,共100個split
          • 存儲:每個小的 存儲:分塊:block
      • 默認:TextInputFormat
    • 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放入同一個迭代器中
    • Reduce:第三個處理邏輯
      • Reducer<keyin,valuein,keyout,valueout>
      • 用於啓動一個ReduceTask來實現將之前所有拆分的數據進行合併
      • 合併邏輯:reduce,根據業務來決定reduce方法的實現
    • Output
      • OutputFormat
      • TextOutputFormat:將keyvalue寫入文件,並且key與value之間用製表符分隔
      • 將Reduce的結果進行輸出

2、編程規則

  • 數據類型:Hadoop的序列化類型,Text、IntWritable……
  • 數據結構:KeyValue
  • 編程模型:
    • Driver類
      • 必須包含main,作爲程序的入口
      • main:作爲程序入口,負責調用run方法
      • run:初始化一個job,配置job,提交job的運行
        • 只有job被提交之後,後面五大階段纔開始運行
    • Input類:TextInputFormat
    • Map類:map方法
    • Shuffle
      • 分區器:HashPartitioner
      • 排序器
      • 分組器
    • Reduce類:reduce方法
    • Output:TextOutputFormat

二、課程目標

  1. MapReduce程序歷史監控【掌握】
    • jobhistoryserver:用於監控所有運行過的程序的日誌信息
  2. MapReduce中自定義數據類型【重點】
    • 自定義Hadoop中的序列化類型,封裝JavaBean
    • 排序
  3. 手機流量分析案例【掌握】
  4. 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中提供了哪些序列化類型?
    • Text、NullWritable、IntWritable、DoubleWritable、BooleanWritable……
  • 爲什麼要自定義數據類型?
    • 因爲整個Hadoop中所有數據只能以keyvalue進行傳輸
    • Hadoop自帶的類型,只能保存一個字段
    • 如果我們需要傳輸計算多個字段 N>2,無法實現【拼接字符串】
    • 所有Hadoop中也提供了自定義序列化類型的接口

2、自定義數據類型

  • MapReduce程序的兩種類型
    • 沒有shuffle:Input -> Map -> Output
      • 一般用於ETL的過程:數據清洗
        • 原來的數據中有100個字段,只需要50列
          • 輸入多少行,就輸出多少行
    • 有shuffle:Input -> Map -> Shuffle -> Reduce -> Output
  • 自定義數據類型的規則
    • 第一種:實現Writable的接口
      • 定義屬性
      • 重寫方法:
        • 構造
        • get and set
        • 序列化:write
        • 反序列化:readFields
        • toString
    • 第二種:實現WritableComparable接口
      • 定義屬性
      • 重寫方法:
        • 構造
        • get and set
        • 序列化:write
        • 反序列化:readFields
        • toString
        • compareTo:用於比較兩個相同的對象
    • 區分:什麼時候需要用第一種,什麼時候用第二種
      • 當前這個自定義的類型是否會作爲Map輸出的Key經過shuffle
        • 如果是:用第二種
        • 如果不是:用第一種
        • 弄不清楚:全部用第二種
  • 需求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

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章