mr整體流程
maptask
調用FileInputFormat的createRecordReader(底層lineRecordReader)讀取分片數據
每行數據讀取一次,返回一個(K,V)對,K是offset,V是一行數據
將k-v對交給maptask處理
每對k-v調用一次map(K,V,context)方法,然後context.write(k,v)
寫出的數據交給收集器OutputCollector.collector()處理
將數據寫入環形緩衝區,並記錄寫入的起始偏移量,終止偏移量,環形緩衝區默認大小100M
默認寫到80%的時候要溢寫磁盤,溢寫磁盤的過程中數據繼續寫入剩餘20%
環形緩衝區(在80%時開始溢寫(鎖定這80的空間,同時通過(索引(key的開始,value的開始,value的結束,p的信息))
溢寫磁盤之前要先進行分區然後分區內進行排序
默認的分區規則是hashpatitioner,即key的hash%reduceNum
默認的排序規則是key的字典順序,使用的是快速排序
溢寫會形成多個文件,在maptask讀取完一個分片數據後,先將環形緩衝區數據刷寫到磁盤
將數據多個溢寫文件進行合併,分區內排序(外部排序===》歸併排序)
reduceTask執行詳解
數據按照分區規則發送到reducetask
reducetask將來自多個maptask的數據進行合併,排序(外部排序===》歸併排序)
按照key相同分組()
一組數據調用一次reduce(k,iterablevalues,context)
處理後的數據交由reducetask
reducetask調用FileOutputFormat組件
FileOutputFormat組件中的write方法將數據寫出
shuffle概念
shuffle過程從map寫數據到環形緩衝區到reduce讀取數據合併(見maptask和reducetask執行過程)
洗牌打亂
Collections.shuffle(List):
mr的shuffle:map階段輸出到reduce輸入的這段過程
buffer in memory:圓形緩衝區(80M兆溢寫)
根據key快排:
合併:根據分區合併
默認分區:1 (hashcode%1恆等於0)
reduce去map的分區合併的數據拉去自己的(分區的reduce)的數組(appmaster告知)
reduce在拉取的過程(多個map的)中合併(排序)一個臨時文件
part-r-00000:-r執行過reduce:part:分區
map的執行結果在圓形緩衝區中默認是100M,閾值是80M.
OutputCollection.collector():收集器
合併小文件(歸併)
80M一溢寫可能會有大量的80M的小文件
切片:
fileinputformat:中計算切片信息(getspilt)
切片信息:
本地運行
本地提交(集羣運行) debug 用於本地模擬生產環境
.cross-platform.
conf.setBoolean(“mapreduce.app-submission.cross-platform”, true);
這是那個參數
那個配置文件
集羣提交集羣運行 :用於測試,生產,研發
mr的數據類型
Text;
intWritable
VintWritable
longWritable
vlongWritable
BooleanWritable
NullWritable
MapWritable<>;
java有mr沒有的
short
自定義組件combiner
1.Combiner是MR程序中Mapper和Reduce之外的一種組件
2.Combiner組件的父類就是Reducer
3.Combiner和Reducer之間的區別在於運行的位置
4.Reducer是每一個接收全局的Map Task 所輸出的結果
5.Combiner是在MapTask的節點中運行
6.每一個map都會產生大量的本地輸出,Combiner的作用就是對map輸出的結果先做一次合併,以較少的map和reduce節點中的數據傳輸量
7.Combiner的存在就是提高當前網絡IO傳輸的性能,也是MapReduce的一種優化手段。
8.Combiner的具體實現:
wordcount案例中直接調用寫好的reduce,即在job的設置中加入
job.setCombinerClass(WordCountReduce.class);
public class WordCountRunner {
public static void main(String[] args) {
try {
Configuration conf = new Configuration();
//獲取job並攜帶參數
Job job = Job.getInstance(conf,"wordCount");
//可以用job對象封裝一些信息
//首先程序是一個jar包,就要指定jar包的位置
//將jar包放在root目錄下
//可以將這個程序打包爲pv.jar,上傳到linux機器上
//使用命令運行
//hadoop jar /root/pv.jar pvcount.PvCountRunner /data/pvcount /out/pvcount
//job.setJar("d:/word.jar");
/**
* 一個jar包中可能有多個job,所以我們要指明job使用的是哪兒一個map類
* 哪兒一個reduce類
*/
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReduce.class);
/**
* map端輸出的數據要進行序列化,所以我們要告訴框架map端輸出的數據類型
*/
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
/**
* reduce端要輸出,所有也要指定數據類型
*/
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
/**
* 告訴框架用什麼組件去讀數據,普通的文本文件,就用TextInputFormat
* 導入長包
*/
job.setInputFormatClass(TextInputFormat.class);
/**
* 告訴這個組件去哪兒讀數據
* TextInputFormat有個父類FileInputFormat
* 用父類去指定到哪兒去讀數據
* 輸入路徑是一個目錄,該目錄下如果有子目錄需要進行設置遞歸遍歷,否則會報錯
*/
FileInputFormat.addInputPath(job,new Path(args[0]));
//設置寫出的組件
job.setOutputFormatClass(TextOutputFormat.class);
//設置寫出的路徑
FileOutputFormat.setOutputPath(job,new Path(args[1]));
job.setCombinerClass(WordCountReduce.class);
/**
* 信息設置完成,可以調用方法向yarn去提交job
* waitForCompletion方法就會將jar包提交給RM
* 然後rm可以將jar包分發,其他機器就執行
*/
//傳入一個boolean類型的參數,如果是true,程序執行會返回true/flase
//如果參數傳入true,集羣在運行時會有進度,這個進度會在客戶端打印
boolean res = job.waitForCompletion(true);
/**
* 客戶端退出後會返回一個狀態碼,這個狀態碼我們可以寫shell腳本的時候使用
* 根據返回的狀態碼的不同區執行不同的邏輯
*/
System.exit(res? 0:1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Hadoop序列化類型,以及自定義類型
MapReduce序列化,序列化是指將結構化對象轉爲字節流以便於通過網絡進行傳輸或寫入持久存儲的過程。反序列化指的是將字節流轉爲結構化對象的過程。在Hadoop MapReduce中,序列化的主要作用有兩個:永久存儲和進程間通信。
爲了能夠讀取或者存儲Java對象,MapReduce編程模型要求用戶輸入和輸出數據中的key和value必須是可序列化的。在Hadoop MapReduce中,使一個Java對象可序列化的方法是讓其對應的類實現Writable接口。但對於key而言,由於它是數據排序的關鍵字,因此還需要提供比較兩個key對象的方法。爲此,key對應類需實現WritableComparable接口
(自定義數據類型)
需求:對於記錄用戶手機信息的文件,得出統計每一個用戶(手機號)所耗費的總上行流量、下行流量,總流量結果
實現思路:實現自定義的 bean 來封裝流量信息,使用手機號碼作爲Key,Bean作爲value。這個Bean的傳輸需要實現可序列化,Java類中提供的序列化方法中會由很多冗餘信息(繼承關係,類信息)是我們不需要的,而這些信息在傳輸中佔據大量資源,會導致有效信息傳輸效率減低。因此我們需要實現MapReduce的序列化接口Writable,自定義方法實現。
計算上行流量、下行流量、計費流量
取一行TextInputFormat類去讀取,offset做key,一行數據做value,拆分,取出倒數第二倒數第三段
map端讀取數據然後輸出
offset:phoneNum,upflow,downflow
phoneNum:upfolw,downflow
phoneNum:{upfolw_downflow;upfolw1_downflow2}
phoneNum:totalupflow,totaldownflow,totalflow
考慮把字符串拼接的方式存儲數據改寫成類去存儲數據,這個類三個屬性,upflow,downflow,totalflow,這些數據在hadoop框架要進行序列化和反序列化
所以要實現writable接口,重寫序列化和反序列方法
public class FlowBean implements Writable{
long upflow;
long downflow;
long sumflow;
//如果空參構造函數被覆蓋,一定要顯示定義一下,否則在反序列化時會拋出異常
public flowBean() {
}
public flowBean(long upflow, long downflow) {
this.upflow = upflow;
this.downflow = downflow;
this.sumflow=upflow+downflow;
}
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;
}
public long getSumflow() {
return sumflow;
}
public void setSumflow(long sumflow) {
this.sumflow = sumflow;
}
@Override
public String toString() {
return upflow + "\t" + downflow + "\t" + sumflow;
}
//序列化,將對象的字段信息寫入輸出流
@Override
public void write(DataOutput out) throws IOException {
out.writeLong(upflow);
out.writeLong(downflow);
out.writeLong(sumflow);
}
//反序列化,從輸入流讀取各字段的信息
@Override
public void readFields(DataInput in) throws IOException {
upflow=in.readLong();
downflow=in.readLong();
sumflow=in.readLong();
}
}
public class flowCount {
static class MyMapper extends Mapper<LongWritable,Text,Text,flowBean>{
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] fields = line.split("\t");
String phoneNum = fields[1];
long upFlow = Long.parseLong(fields[fields.length - 3]);
long downFlow = Long.parseLong(fields[fields.length - 2]);
flowBean bean = new flowBean(upFlow,downFlow);
context.write(new Text(phoneNum),bean);
}
}
static class MyReducer extends Reducer<Text,flowBean,Text,flowBean>{
@Override
protected void reduce(Text key, Iterable<flowBean> values, Context context) throws IOException, InterruptedException {
Iterator<flowBean> it = values.iterator();
long upflow=0;
long downflow=0;
while (it.hasNext()){
flowBean bean = it.next();
upflow+=bean.getUpflow();
downflow+=bean.getDownflow();
}
flowBean total = new flowBean(upflow, downflow);
context.write(key,total);
}
}
public static void main(String[] args) throws InterruptedException, IOException, ClassNotFoundException {
//1、配置連接hadoop集羣的參數
Configuration conf = new Configuration();
// conf.set("fs.defaultFS","hdfs://qianfeng");
conf.set("fs.defaultFS","file:///");
conf.set("mapreduce.framework.name","local");
//2、獲取job對象實例
Job job = Job.getInstance(conf,"FLOWCOUNT");
//3、指定本業務job的路徑
job.setJarByClass(flowCount.class);
//4、指定本業務job要使用的Mapper類
job.setMapperClass(MyMapper.class);
//5、指定mapper類的輸出數據的kv的類型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(flowBean.class);
//6、指定本業務job要使用的Reducer類
job.setReducerClass(MyReducer.class);
//7、設置程序的最終輸出結果的kv的類型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(flowBean.class);
//8、設置job要處理的數據的輸入源
FileInputFormat.setInputPaths(job,new Path("/data/flowinput"));
//判斷輸出目錄是否存在,如果存在,則刪除之
//9、設置job的輸出目錄
FileOutputFormat.setOutputPath(job,new Path("/out/flowoutput"));
//10、提交job
boolean b = job.waitForCompletion(true);
System.exit(b?0:1);
}
}
自定義分區:
[外鏈圖片轉存失敗(img-255KxceZ-1568967115064)(D:/新機/千峯筆記/1567677569296.png)]
二次排序
[外鏈圖片轉存失敗(img-mMgqKVYW-1568967115065)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1567568985274.png)]
行比較器:raw
map端的jion
使用場景:有一個或者多個小表(文件)
map端的join:
加緩存:
在job中。job.setcacheFiles():url數組
job.addcacheFile():url單個路徑
定義map存值
在setup中使用流讀文件放入map中
public Map<String, String> sexMap = new ConcurrentHashMap<String, String>();
public Map<String, String> userMap = new ConcurrentHashMap<String, String>();
/**
* 只在map階段執行一次 setup()
*
* 此方法被MapReduce框架僅且執行一次,在執行Map任務前, 進行相關變量或者資源的集中初始化工作。 若是將資源初始化工作放在方法map()中,
* 導致Mapper任務在解析每一行輸入時都會進行資源初始化工作, 導致重複,程序運行效率不高! cleanup()
* 此方法被MapReduce框架僅且執行一次,在執行完畢Map任務後, 進行相關變量或資源的釋放工作。若是將釋放資源工作放入方法map()中,
* 也會導致Mapper任務在解析、處理每一行文本後釋放資源, 而且在下一行文本解析前還要重複初始化,導致反覆重複,程序運行效率不高!
*/
/**
* 定義倆個內存數據結構來存儲緩存文件的內容
*/
@Override
protected void setup(Context context) throws IOException, InterruptedException {
// TODO Auto-generated method stub
// 首先獲取緩存文件數組
// context.getLocalCacheFiles();
//DistributedCache.getLocalCacheFiles(new Configuration());
Path[] paths = context.getLocalCacheFiles();
for (Path path : paths) {
String filename = path.getName();
BufferedReader br = null;
if (filename.equals("sex")) {
br = new BufferedReader(new FileReader(new File(path.getName())));
String str = null;
while ((str = br.readLine()) != null) {
String strs[] = str.split("\t");
sexMap.put(strs[0], strs[1]);
}
// 關閉流
br.close();
} else if (filename.equals("user")) {
br = new BufferedReader(new FileReader(new File(path.getName())));
String str = null;
while ((str = br.readLine()) != null) {
String strs[] = str.split("\t");
userMap.put(strs[0], strs[1]);
}
// 關閉流
br.close();
}
}
}
reduce的jion
連續的job
// 驅動
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 獲取配置對象信息,和對配置對象進行設置(沒有就不用了)獲取Job對象
Job grepjob = Job.getInstance(new Configuration(), "grep job");
// 設置Job的運行主類
grepjob.setJarByClass(DeperDemo.class);
// 對map階段進行設置
grepjob.setMapOutputKeyClass(Text.class);
grepjob.setMapOutputValueClass(Text.class);
grepjob.setMapperClass(GrepMapper.class);
FileInputFormat.addInputPath(grepjob, new Path("/GrepWords"));
FileOutputFormat.setOutputPath(grepjob, new Path("/output"));
// 依賴的job
Job countjob = Job.getInstance(new Configuration(), "Count job");
countjob.setJarByClass(DeperDemo.class);
countjob.setMapOutputKeyClass(Text.class);
countjob.setMapOutputValueClass(IntWritable.class);
countjob.setMapperClass(CountMapper.class);
FileInputFormat.addInputPath(countjob, new Path("/output"));
// 對reduce階段設置
countjob.setOutputKeyClass(Text.class);
countjob.setOutputValueClass(IntWritable.class);
countjob.setReducerClass(CountReducer.class);
FileOutputFormat.setOutputPath(countjob, new Path("/output/out00"));
// 創建單個作業控制器
ControlledJob gcj = new ControlledJob(grepjob.getConfiguration());
ControlledJob ccj = new ControlledJob(countjob.getConfiguration());
// 再添加依賴
ccj.addDependingJob(gcj);
// 定義一個總的作業控制器
JobControl jc = new JobControl("grepjob and countjob");
// 將單個作業控制器放入總的作業控制器中
jc.addJob(gcj);
jc.addJob(ccj);
// 獲取一個線程
Thread th = new Thread(jc);
// 啓動線程
th.start();
// 判斷job是否完全運行完成
if (jc.allFinished()) {
Thread.sleep(1000);
th.stop();
jc.stop();
System.exit(0);
}
}
}
discp:集羣之間的copy。使用mr進行copy
常用命令:
hadoop distcp hdfs://nn1:8020/foo/bar hdfs://nn2:8020/bar/foo
bash$ hadoop distcp hdfs://nn1:8020/foo/a hdfs://nn1:8020/foo/b hdfs://nn2:8020/bar/foo
hadoop distcp -f hdfs://nn1:8020/srclist hdfs://nn2:8020/bar/foo
hadoop distcp -overwrite hdfs://nn1:8020/srclist hdfs://nn2:8020/bar/foo
操作選項:
[root@hadoop01 hadoop-2.7.1]# hadoop distcp
usage: distcp OPTIONS [source_path...] <target_path>
OPTIONS
-append Reuse existing data in target files and append new
data to them if possible
-async Should distcp execution be blocking
-atomic Commit all changes or none
-bandwidth <arg> Specify bandwidth per map in MB
-delete Delete from target, files missing in source
-diff <arg> Use snapshot diff report to identify the
difference between source and target
-f <arg> List of files that need to be copied
-filelimit <arg> (Deprecated!) Limit number of files copied to <= n
-i Ignore failures during copy
-log <arg> Folder on DFS where distcp execution logs are
saved
-m <arg> Max number of concurrent maps to use for copy
-mapredSslConf <arg> Configuration for ssl config file, to use with
hftps://
-overwrite Choose to overwrite target files unconditionally,
even if they exist.
-p <arg> preserve status (rbugpcaxt)(replication,
block-size, user, group, permission,
checksum-type, ACL, XATTR, timestamps). If -p is
specified with no <arg>, then preserves
replication, block size, user, group, permission,
checksum type and timestamps. raw.* xattrs are
preserved when both the source and destination
paths are in the /.reserved/raw hierarchy (HDFS
only). raw.* xattrpreservation is independent of
the -p flag. Refer to the DistCp documentation for
more details.
-sizelimit <arg> (Deprecated!) Limit number of files copied to <= n
bytes
-skipcrccheck Whether to skip CRC checks between source and
target paths.
-strategy <arg> Copy strategy to use. Default is dividing work
based on file sizes
-tmp <arg> Intermediate work path to be used for atomic
commit
-update Update target, copying only missingfiles or
directories
more details.
-sizelimit (Deprecated!) Limit number of files copied to <= n
bytes
-skipcrccheck Whether to skip CRC checks between source and
target paths.
-strategy Copy strategy to use. Default is dividing work
based on file sizes
-tmp Intermediate work path to be used for atomic
commit
-update Update target, copying only missingfiles or
directories