注:以下內容來源於互聯用,用於個人讀書筆記。
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。
任務:
查找每個單詞在哪個文件出現,並統計查詢出現出現幾次。
思路:
後面有時間再補充這個程序。