Hadoop學習_mapreduce提交方式+實現簡單流量統計程序(有註釋)+shuffle

注:以下內容來源於互聯用,用於個人讀書筆記。

mapreduce提交方式

MR程序的幾種提交運行模式:

本地模型運行
1/在windows的eclipse裏面直接運行main方法,就會將job提交給本地執行器localjobrunner執行
—-輸入輸出數據可以放在本地路徑下(c:/wc/srcdata/)
—-輸入輸出數據也可以放在hdfs中(hdfs://weekend110:9000/wc/srcdata)

2/在linux的eclipse裏面直接運行main方法,但是不要添加yarn相關的配置,也會提交給localjobrunner執行(即本地服務器)
—-輸入輸出數據可以放在本地路徑下(/home/hadoop/wc/srcdata/)
—-輸入輸出數據也可以放在hdfs中(hdfs://weekend110:9000/wc/srcdata)

集羣模式運行
1/將工程打成jar包,上傳到服務器,然後用hadoop命令提交 hadoop jar wc.jar cn.itcast.hadoop.mr.wordcount.WCRunner
2/在linux的eclipse中直接運行main方法,也可以提交到集羣中去運行,但是,必須採取以下措施:
—-在工程src目錄下加入 mapred-site.xml 和 yarn-site.xml
—-將工程打成jar包(wc.jar),同時在main方法中添加一個conf的配置參數 conf.set(“mapreduce.job.jar”,”wc.jar”);
這裏寫圖片描述
   上面的圖片就是在Linux的IDE中運行集羣模式的兩種配置方法(任選其一)。 
3/在windows的eclipse中直接運行main方法,也可以提交給集羣中運行,但是因爲平臺不兼容,需要做很多的設置修改(不推薦)
—-要在windows中存放一份hadoop的安裝包(解壓好的)
—-要將其中的lib和bin目錄替換成根據你的windows版本重新編譯出的文件
—-再要配置系統環境變量 HADOOP_HOME 和 PATH
—-修改YarnRunner這個類的源碼
補充:https://blog.csdn.net/u010171031/article/details/53024516 Hadoop intellij windows本地環境配置。

實現簡單流量統計程序

數據格式:

1363157985066   13726230503 00-FD-07-A4-72-B8:CMCC  120.196.100.82  i02.c.aliimg.com        24  27  2481    24681   200
1363157995052   13826544101 5C-0E-8B-C7-F1-E0:CMCC  120.197.40.4            4   0   264 0   200
1363157991076   13926435656 20-10-7A-28-CC-0A:CMCC  120.196.100.99          2   4   132 1512    200
1363154400022   13926251106 5C-0E-8B-8B-B1-50:CMCC  120.197.40.4            4   0   240 0   200
1363157993044   18211575961 94-71-AC-CD-E6-18:CMCC-EASY 120.196.100.99  iface.qiyi.com  視頻網站    15  12  1527    2106    200
1363157995074   84138413    5C-0E-8B-8C-E8-20:7DaysInn  120.197.40.4    122.72.52.12        20  16  4116    1432    200
1363157993055   13560439658 C4-17-FE-BA-DE-D9:CMCC  120.196.100.99          18  15  1116    954 200
1363157995033   15920133257 5C-0E-8B-C7-BA-20:CMCC  120.197.40.4    sug.so.360.cn   信息安全    20  20  3156    2936    200
1363157983019   13719199419 68-A1-B7-03-07-B1:CMCC-EASY 120.196.100.82          4   0   240 0   200
1363157984041   13660577991 5C-0E-8B-92-5C-20:CMCC-EASY 120.197.40.4    s19.cnzz.com    站點統計    24  9   6960    690 200
1363157973098   15013685858 5C-0E-8B-C7-F7-90:CMCC  120.197.40.4    rank.ie.sogou.com   搜索引擎    28  27  3659    3538    200
1363157986029   15989002119 E8-99-C4-4E-93-E0:CMCC-EASY 120.196.100.99  www.umeng.com   站點統計    3   3   1938    180 200
1363157992093   13560439658 C4-17-FE-BA-DE-D9:CMCC  120.196.100.99          15  9   918 4938    200
1363157986041   13480253104 5C-0E-8B-C7-FC-80:CMCC-EASY 120.197.40.4            3   3   180 180 200
1363157984040   13602846565 5C-0E-8B-8B-B6-00:CMCC  120.197.40.4    2052.flash2-http.qq.com 綜合門戶    15  12  1938    2910    200
1363157995093   13922314466 00-FD-07-A2-EC-BA:CMCC  120.196.100.82  img.qfc.cn      12  12  3008    3720    200
1363157982040   13502468823 5C-0A-5B-6A-0B-D4:CMCC-EASY 120.196.100.99  y0.ifengimg.com 綜合門戶    57  102 7335    110349  200
1363157986072   18320173382 84-25-DB-4F-10-1A:CMCC-EASY 120.196.100.99  input.shouji.sogou.com  搜索引擎    21  18  9531    2412    200
1363157990043   13925057413 00-1F-64-E1-E6-9A:CMCC  120.196.100.55  t3.baidu.com    搜索引擎    69  63  11058   48243   200
1363157988072   13760778710 00-FD-07-A4-7B-08:CMCC  120.196.100.82          2   2   120 120 200
1363157985066   13726238888 00-FD-07-A4-72-B8:CMCC  120.196.100.82  i02.c.aliimg.com        24  27  2481    24681   200
1363157993055   13560436666 C4-17-FE-BA-DE-D9:CMCC  120.196.100.99          18  15  1116    954 200

任務是:
  對每一個用戶(手機號,第二列)統計他的上行流量(第7列),下行流量(第8列)和總的流量(前兩個加起來)。
  
大體思路:
  map階段用手機號做key,上行流量和下行流量作爲value傳出去供reduce彙總。在這過程有一個問題是原來統計每一個key的次數,直接使用封裝好的 LongWrite 類,現在我們這個value必須包含上行流量和下行流量,所以我們必須學着像 LongWrite 類一樣去構造我們需要的數據類型類。同時,這個類產生的數據對象還要符合hadoop中序列化傳輸的要求(什麼是序列化;怎麼寫這個數據類型的類)。
  
什麼是序列化:
  在計算機內存中,數據是分塊存儲的,但是在網絡中進行傳輸是用 流 串行傳輸的(如下圖)。接收端必須反序列化,讀取對應的內容。
序列化傳輸
  注意:JDK自帶的序列化機制,會將原來的繼承信息一起傳遞過去,可以復原。但hadoop沒有傳遞繼承結構(因爲hadoop主要用於大數據量的存儲和計算,不需要對象之間的繼承結構,所以不需要再序列化的傳輸過程中帶上繼承信息)。

如何寫這個數據類型的類:
  第一次接觸,肯定不知道相關概念,只有去參照hadoop已經寫好的(如 LongWrite 是對數據類型Long的封裝)(這是一種學習方式,沒事可以多讀讀源碼)。下面是LongWrite 的源碼。

package org.apache.hadoop.io;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.classification.InterfaceAudience.Public;
import org.apache.hadoop.classification.InterfaceStability.Stable;

@Public
@Stable
public class LongWritable implements WritableComparable<LongWritable> {
    private long value;

    public LongWritable() {
    }

    public LongWritable(long value) {
        this.set(value);
    }

    public void set(long value) {
        this.value = value;
    }

    public long get() {
        return this.value;
    }

    public void readFields(DataInput in) throws IOException {
        this.value = in.readLong();
    }

    public void write(DataOutput out) throws IOException {
        out.writeLong(this.value);
    }

    public boolean equals(Object o) {
        if (!(o instanceof LongWritable)) {
            return false;
        } else {
            LongWritable other = (LongWritable)o;
            return this.value == other.value;
        }
    }

    public int hashCode() {
        return (int)this.value;
    }

    public int compareTo(LongWritable o) {
        long thisValue = this.value;
        long thatValue = o.value;
        return thisValue < thatValue ? -1 : (thisValue == thatValue ? 0 : 1);
    }

    public String toString() {
        return Long.toString(this.value);
    }

    static {
        WritableComparator.define(LongWritable.class, new LongWritable.Comparator());
    }

    public static class DecreasingComparator extends LongWritable.Comparator {
        public DecreasingComparator() {
        }

        public int compare(WritableComparable a, WritableComparable b) {
            return super.compare(b, a);
        }

        public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
            return super.compare(b2, s2, l2, b1, s1, l1);
        }
    }

    public static class Comparator extends WritableComparator {
        public Comparator() {
            super(LongWritable.class);
        }

        public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
            long thisValue = readLong(b1, s1);
            long thatValue = readLong(b2, s2);
            return thisValue < thatValue ? -1 : (thisValue == thatValue ? 0 : 1);
        }
    }
}

  這裏面最重要的是定義數據類型(你要傳輸什麼樣子的數據),即成員變量;兩個構造函數(無參數是反序列化機制過程要用,有參數的是序列化時對成員變量賦值);序列化函數:write(DataOutput out) ,通過java的DataOutput 將成員變量送到網絡中傳給其他節點;反序列函數:readFields(DataOutput in) ,也是通過java的機制從流中讀取數據給成員變量賦值。
下面是flowSum的map程序:

package hadoop_llb_flowmr;
import java.io.IOException;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
/**
 * Created by llb on 2018/6/18.
 *
 */
/**wordcount中是: extends Mapper<LongWritable, Text, Text, LongWritable>
 *      表示的是:1.輸入:偏移量(數字,key)和該行內容(Text)-> <key,value>;
 *                2.輸出:統計name(字符串,Text)和在本行中的統計值(如次數,value) -> <key,value>;
 *                其中,輸入是自動傳入的,一行一行的;輸出是通過context將<key,value> 發送出去的。
 *            在這過程中,key和value的類型可以指定,也可以自己寫,但是自己寫要注意符合序列化的機制。
 * 因爲要統計每一個用戶的上行流量,下行流量,以及總的流量和(每個用戶多條記錄),而其他一般的
 * 基礎類型一次只傳一個類目,如只傳上行流量,現在我要一次傳上行和下行,所以要封裝一個自己的數據類型,
 * 將每條記錄中的手機號,上行流量和下行流量封裝到一個類的對象中,一起當成是value,即本例中的FlowBean。
 * FlowBean 是我們自定義的一種數據類型,要在hadoop的各個節點之間傳輸,應該遵循hadoop的序列化機制
 * 就必須實現hadoop相應的序列化接口
 */
public class flowSumMap extends Mapper<LongWritable,Text,Text,FlowBean> {  //指定輸入輸出的數據類型
    //拿到日誌中的一行數據,切分各個字段,抽取出我們需要的字段:手機號,上行流量,下行流量,然後封裝成kv發送出去
    protected void map(LongWritable key,Text value,Context context)
    throws IOException,InterruptedException{
        //拿一行數據,在內部都是用的原始數據類型
        String line = value.toString();
        //切分成各個字段
        String[] fields = StringUtils.split(line,"\t");
        //拿到需要的字段
        String phoneNB = fields[1];
        long u_flow = Long.parseLong(fields[7]);
        long d_flow = Long.parseLong(fields[8]);
        //將 string 參數解析爲有符號十進制 long,字符串中的字符必須都是十進制數字。
        //封裝成key-value ,通過context傳輸出去
        context.write(new Text(phoneNB),new FlowBean(phoneNB,u_flow,d_flow));
    }

}

下面是針對 FlowBean 的封裝:

package hadoop_llb_flowmr;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.WritableComparable;
/**
 * Created by llb on 2018/6/18.
 */
// Writable 直接這個就可以了,默認按照key排序,這裏WritableComparable
// 指定按照流量進行排序的實現。最後面成員函數就要加compare方法。
public class FlowBean implements WritableComparable<FlowBean>{
    private String phoneNB;
    private long up_flow;//上行流量
    private long d_flow;//下行流量
    private long s_flow;//總流量,爲什麼寫成私有的呢?防止在reduce中被串改?

    //在反序列化時,反射機制需要調用空參構造函數,所以顯示定義了一個空參構造函數
    public FlowBean(){}
//    寫一個有參構造函數,爲了初始化方便
    public FlowBean(String phoneNB,long up_flow,long d_flow){
        this.phoneNB = phoneNB;
        this.up_flow = up_flow;
        this.d_flow = d_flow;
        this.s_flow = up_flow+d_flow;
    }
    //現在寫write函數,將對象數據序列化到流中
    public void write(DataOutput out) throws IOException{
        out.writeUTF(phoneNB); //將電話號碼編碼爲UTF的字符串格式,傳輸到流中
        out.writeLong(up_flow);
        out.writeLong(d_flow);
        out.writeLong(s_flow);
    }

    //從數據流中取出反序列化對象的數據,注意要和剛剛序列化的順序一致
    public void readFields(DataInput in) throws IOException {
        phoneNB = in.readUTF();
        up_flow = in.readLong();
        d_flow = in.readLong();
        s_flow = in.readLong();
    }
//    因爲成員變量是私有變量,而後面的reduce中要用,所以定義兩個方法,獲取成員變量的值
    public long getUp_flow(){
        return up_flow;
    }
    public long getD_flow(){
        return d_flow;
    }
//    自定的數據類型,必須實現序列化和反序列化的方法
    /**
    我們是自定的一個數據類型,想要在最後的結果中顯示爲:
        手機號(key) 上行流量 下行流量 總流量(value)
     但是我們的contex是不知道怎麼排版這個的,所以要單獨重寫toString方法,
     指定輸出內容的顯示格式。
    */
    public String toString(){
        return " "+up_flow+"\t"+d_flow+"\t"+s_flow;
    }
    //因爲要按照總流量進行排序,所以要對比較器進行重寫
    public int compareTo(FlowBean o){
        return s_flow > o.s_flow ? -1:1; //倒排序
    }
}

下面是reduce程序:

package hadoop_llb_flowmr;
import java.io.IOException;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
/**
 * Created by llb on 2018/6/18.
 */
public class flowSumReduce extends Reducer<Text,FlowBean,Text,FlowBean>{

    //框架每傳遞一組數據<1387788654(phoneNB),{flowbean,flowbean,flowbean,flowbean.....}>
    //    調用一次我們的reduce方法
    //reduce中的業務邏輯就是遍歷values,然後進行累加求和再輸出
    protected void reduce(Text key,Iterable<FlowBean> values,Context context)
    throws IOException, InterruptedException {
        long up_flow_counter = 0;
        long d_flow_counter = 0;
        //遍歷對流量求和
        for (FlowBean bean : values){
            up_flow_counter += bean.getUp_flow();
            d_flow_counter += bean.getD_flow();
        }
        //最後將結果保存下來,這裏面用了bean中的toString方法,指定保存格式
        context.write(key,new FlowBean(key.toString(),up_flow_counter,d_flow_counter));
    }
}

下面是主類程序(Runner):

package hadoop_llb_flowmr;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputFormat;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.OutputFormat;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
/**
 * Created by llb on 2018/6/18.
 */
//下面是job提交的規範流程:通過hadoop的tool工具
public class flowSumRunner extends Configured implements Tool{
    //將wordcount中的main的內容寫到run中,並返回成功與否的標誌
    public int run(String[] args) throws Exception{
        //讀取配置文件,並創建一個工作ID
        Configuration conf = new Configuration();
        Job job = Job.getInstance();
        //將map和reduce類封裝類jar包
        job.setJarByClass(flowSumMap.class);
        job.setJarByClass(flowSumReduce.class);
        job.setJarByClass(flowSumRunner.class);
        //設定輸入輸出格式,見上一篇wordcount說明
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(FlowBean.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(FlowBean.class);
        //輸入和輸出地址通過參數進行指定,可以多次複用
        FileInputFormat.setInputPaths(job,new Path(args[0]));
        FileOutputFormat.setOutputPath(job,new Path(args[1]));
        //提交給yarn資源調度器,並返回狀態值
        return job.waitForCompletion(true)?0:1;
    }
    public static void main(String [] args) throws Exception {
        // ToolRunner 是框架裏面的,這是調用的標準寫法。
        int res = ToolRunner.run(new Configuration(),new flowSumRunner(),args);
        System.exit(res); // 正常就正常退出,否則就異常退出。
    }
}

然後是將這個工程打包上傳hadoop,交給集羣運行。Intellij生成jar包: https://jingyan.baidu.com/album/c275f6ba0bbb65e33d7567cb.html?picindex=2

還有自定義按照什麼排序,後面有時間再添加。
下面是shuffle:
首先補充下map task 併發:
這裏寫圖片描述
  切片是邏輯概念,一個split觸發一個map task進程。因爲split是文件的邏輯切片,所以當一個文件很大時,對應的block也很大,會一個block觸發一個map task;若block較小,則多個block觸發一個map task。這也從側面說明hadoop對於大文件的高併發。
shuffle:
  我感覺就是不同map和reduce之間的執行過程。放個總圖上來:
這裏寫圖片描述
  輸入一個split,啓動一個map task,執行map程序,得到 key - value 結果,然後存放到緩存區(本機),當緩存不夠時存到磁盤。在這過程中要對數據進行合併和排序,最後合併到一個分區文件。其他的結合圖中的內容和下面的博客來看。
https://blog.csdn.net/clerk0324/article/details/52461135

  Map之所以慢就慢在 很多時候緩存放不了,需要不斷的放到磁盤上。現在的spark框架不用放在磁盤上,所以能實時計算,但是內存始終有限,沒有hadoop處理的數據量大。
  Shuffle整個過程都是由 MRAppMaster 進行監控調度的,因爲yarn不懂mapreduce程序。
  這裏寫圖片描述
  分區信息主要是告訴 每個part對應結果文件 的偏移量和具體的內容,好拼接起來。
  這裏寫圖片描述
  輸入數據可能來自於各個客服端,所以爲了統一格式,有了InputFormat(內部有從文件系統讀,也有從數據庫讀)。爲了寫到HDFS文件上,有寫了一個OutputFormat 組件。讀入文件後,切分爲split,然後通過RR形成key-value傳給map。
任務:
  查找每個單詞在哪個文件出現,並統計查詢出現出現幾次。
  這裏寫圖片描述 
  思路:這裏寫圖片描述
  這裏寫圖片描述 
  後面有時間再補充這個程序。

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