MapReduce定義
MapReduce優缺點
缺點
MapReduce核心思想
1)分佈式的運算程序往往需要分成至少2個階段。
2)第一個階段的MapTask併發實例,完全並行運行,互不相干。
3)第二個階段的ReduceTask併發實例互不相干,但是他們的數據依賴於上一個階段的所有MapTask併發實例的輸出。
4)MapReduce編程模型只能包含一個Map階段和一個Reduce階段,如果用戶的業務邏輯非常複雜,那就只能多個MapReduce程序,串行運行。
總結:分析WordCount數據流走向深入理解MapReduce核心思想。
MapReduce進程
常用數據序列化類型
Java類型 |
Hadoop Writable類型 |
Boolean |
BooleanWritable |
Byte |
ByteWritable |
Int |
IntWritable |
Float |
FloatWritable |
Long |
LongWritable |
Double |
DoubleWritable |
String |
Text |
Map |
MapWritable |
Array |
ArrayWritable |
MapReduce編程規範
用戶編寫的程序分成三個部分:Mapper、Reducer和Driver。
WordCount案例實操
1.準備數據
在電腦磁盤上隨便準備一些數據
隨便寫一些詞 然後記住路徑
2.創建一個maven程序
3.導入依賴(有些依賴非必須,是之前hdfs直接粘貼過來的)
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>jdk.tools</groupId>
<artifactId>jdk.tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${JAVA_HOME}/lib/tools.jar</systemPath>
</dependency>
</dependencies>
4.配置log4j
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
5.創建3個類
package com.atguigu.wordcount;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class WcMapper extends Mapper<LongWritable, Text, Text ,IntWritable> {
private static IntWritable one = new IntWritable(1);
private Text word = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//1.拿到一行數據
String line = value.toString();
//2.按照空格切分
String[] words = line.split(" ");
//3.遍歷words 把單詞變成<key,1>的形式
for(String word: words){
this.word.set(word);
context.write(this.word,this.one);
}
}
}
package com.atguigu.wordcount;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class WcReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable total = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
//1.做累加
int sum = 0;
for (IntWritable value : values) {
sum += value.get();
}
total.set(sum);
//2.寫入
context.write(key, total);
}
}
package com.atguigu.wordcount;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
public class WcDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//1.獲取Job實例
Job job = Job.getInstance(new Configuration());
//2.設置類路徑
job.setJarByClass(WcDriver.class);
//3.設置Mapper和Reducer
job.setMapperClass(WcMapper.class);
job.setReducerClass(WcReducer.class);
//4.設置Mapper和Reducer的輸出類型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//5.設置輸入輸出參數
FileInputFormat.setInputPaths(job,new Path(args[0]));
FileOutputFormat.setOutputPath(job,new Path(args[1]));
//6.提交Job
boolean b = job.waitForCompletion(true);
System.exit(b ? 0 : 1);
}
}
6.配置參數
共兩個參數,詳見Driver 一個輸入路徑 一個輸出路徑(不存在的)
7.運行
8.查看結果
9.打包
10.copy jar包到一個地方,改個名字 然後扔到集羣中
我直接拷到桌面
扔到集羣
hadoop jar + jar包 + Driver全類名 + 輸入文件 + 輸出文件(集羣下)
跑出結果
Hadoop序列化
自定義bean對象實現序列化接口(Writable)
在企業開發中往往常用的基本序列化類型不能滿足所有需求,比如在Hadoop框架內部傳遞一個bean對象,那麼該對象就需要實現序列化接口。
具體實現bean對象序列化步驟如下7步。
(1)必須實現Writable接口
(2)反序列化時,需要反射調用空參構造函數,所以必須有空參構造
public FlowBean() { super(); } |
(3)重寫序列化方法
@Override public void write(DataOutput out) throws IOException { out.writeLong(upFlow); out.writeLong(downFlow); out.writeLong(sumFlow); } |
(4)重寫反序列化方法
@Override public void readFields(DataInput in) throws IOException { upFlow = in.readLong(); downFlow = in.readLong(); sumFlow = in.readLong(); } |
(6)要想把結果顯示在文件中,需要重寫toString(),可用”\t”分開,方便後續用。
(7)如果需要將自定義的bean放在key中傳輸,則還需要實現Comparable接口,因爲MapReduce框中的Shuffle過程要求對key必須能排序。詳見後面排序案例。
@Override public int compareTo(FlowBean o) { // 倒序排列,從大到小 return this.sumFlow > o.getSumFlow() ? -1 : 1; } |
MapReduce框架原理
切片與MapTask並行度決定機制
1.問題引出
MapTask的並行度決定Map階段的任務處理併發度,進而影響到整個Job的處理速度。
思考:1G的數據,啓動8個MapTask,可以提高集羣的併發處理能力。那麼1K的數據,也啓動8個MapTask,會提高集羣性能嗎?MapTask並行任務是否越多越好呢?哪些因素影響了MapTask並行度?
2.MapTask並行度決定機制
數據塊:Block是HDFS物理上把數據分成一塊一塊。
數據切片:數據切片只是在邏輯上對輸入進行分片,並不會在磁盤上將其切分成片進行存儲。
Job提交流程源碼和切片源碼詳解
waitForCompletion()
submit();
// 1建立連接
connect();
// 1)創建提交Job的代理
new Cluster(getConfiguration());
// (1)判斷是本地yarn還是遠程
initialize(jobTrackAddr, conf);
// 2 提交job
submitter.submitJobInternal(Job.this, cluster)
// 1)創建給集羣提交數據的Stag路徑
Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);
// 2)獲取jobid ,並創建Job路徑
JobID jobId = submitClient.getNewJobID();
// 3)拷貝jar包到集羣
copyAndConfigureFiles(job, submitJobDir);
rUploader.uploadFiles(job, jobSubmitDir);
// 4)計算切片,生成切片規劃文件
writeSplits(job, submitJobDir);
maps = writeNewSplits(job, jobSubmitDir);
input.getSplits(job);
// 5)向Stag路徑寫XML配置文件
writeConf(conf, submitJobFile);
conf.writeXml(out);
// 6)提交Job,返回提交狀態
status = submitClient.submitJob(jobId, submitJobDir.toString(), job.getCredentials());
FileInputFormat切片源碼解析(input.getSplits(job))
FileInputFormat切片機制
CombineTextInputFormat切片機制
框架默認的TextInputFormat切片機制是對任務按文件規劃切片,不管文件多小,都會是一個單獨的切片,都會交給一個MapTask,這樣如果有大量小文件,就會產生大量的MapTask,處理效率極其低下。
1、應用場景:
CombineTextInputFormat用於小文件過多的場景,它可以將多個小文件從邏輯上規劃到一個切片中,這樣,多個小文件就可以交給一個MapTask處理。
2、虛擬存儲切片最大值設置
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m
注意:虛擬存儲切片最大值設置最好根據實際的小文件大小情況來設置具體的值。
3、切片機制
生成切片過程包括:虛擬存儲過程和切片過程二部分。
(1)虛擬存儲過程:
將輸入目錄下所有文件大小,依次和設置的setMaxInputSplitSize值比較,如果不大於設置的最大值,邏輯上劃分一個塊。如果輸入文件大於設置的最大值且大於兩倍,那麼以最大值切割一塊;當剩餘數據大小超過設置的最大值且不大於最大值2倍,此時將文件均分成2個虛擬存儲塊(防止出現太小切片)。
例如setMaxInputSplitSize值爲4M,輸入文件大小爲8.02M,則先邏輯上分成一個4M。剩餘的大小爲4.02M,如果按照4M邏輯劃分,就會出現0.02M的小的虛擬存儲文件,所以將剩餘的4.02M文件切分成(2.01M和2.01M)兩個文件。
(2)切片過程:
(a)判斷虛擬存儲的文件大小是否大於setMaxInputSplitSize值,大於等於則單獨形成一個切片。
(b)如果不大於則跟下一個虛擬存儲文件進行合併,共同形成一個切片。
(c)測試舉例:有4個小文件大小分別爲1.7M、5.1M、3.4M以及6.8M這四個小文件,則虛擬存儲之後形成6個文件塊,大小分別爲:
1.7M,(2.55M、2.55M),3.4M以及(3.4M、3.4M)
最終會形成3個切片,大小分別爲:
(1.7+2.55)M,(2.55+3.4)M,(3.4+3.4)M
FileInputFormat實現類
自定義InputFormat
自定義InputFormat案例實操
無論HDFS還是MapReduce,在處理小文件時效率都非常低,但又難免面臨處理大量小文件的場景,此時,就需要有相應解決方案。可以自定義InputFormat實現小文件的合併。
1.需求
將多個小文件合併成一個SequenceFile文件(SequenceFile文件是Hadoop用來存儲二進制形式的key-value對的文件格式),SequenceFile裏面存儲着多個文件,存儲的形式爲文件路徑+名稱爲key,文件內容爲value。
2.需求分析
package com.atguigu.inputformat;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.JobContext;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import java.io.IOException;
public class WholeFileInputFormat extends FileInputFormat<Text, BytesWritable> {
@Override
protected boolean isSplitable(JobContext context, Path filename) {
return false;
}
@Override
public RecordReader createRecordReader(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
return new WholeFileRecordReader();
}
}
package com.atguigu.inputformat;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import java.io.IOException;
/**
* 自定義RecordReader,處理一個文件,把一個文件直接讀取成一個KV值
*/
public class WholeFileRecordReader extends RecordReader<Text, BytesWritable> {
private boolean notRead = true;
private Text key = new Text();
private BytesWritable value = new BytesWritable();
private FSDataInputStream inputStream;
private FileSplit fs;
/**
* 初始化方法,框架在一開始調用一次
*
* @param split
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
fs = (FileSplit) split;
Path path = fs.getPath();
FileSystem fileSystem = path.getFileSystem(context.getConfiguration());
inputStream = fileSystem.open(path);
}
/**
* 讀取下一組KV
* @return
* @throws IOException
* @throws InterruptedException
*/
@Override
public boolean nextKeyValue() throws IOException, InterruptedException {
if(notRead){
//具體讀文件過程
//read key
key.set(fs.getPath().toString());
//read value
byte[] buf = new byte[(int) fs.getLength()];
inputStream.read(buf);
value.set(buf, 0, buf.length);
notRead = false;
return true;
}else{
return false;
}
}
/**
* 獲取當前key
* @return
* @throws IOException
* @throws InterruptedException
*/
@Override
public Text getCurrentKey() throws IOException, InterruptedException {
return key;
}
/**
* 獲取當前value
* @return
* @throws IOException
* @throws InterruptedException
*/
@Override
public BytesWritable getCurrentValue() throws IOException, InterruptedException {
return value;
}
/**
* 當前數據讀取進度
*
* @return 0 or 1
* @throws IOException
* @throws InterruptedException
*/
@Override
public float getProgress() throws IOException, InterruptedException {
return notRead ? 0 : 1;
}
/**
* 關閉資源
* @throws IOException
*/
@Override
public void close() throws IOException {
IOUtils.closeStream(inputStream);
}
}
package com.atguigu.inputformat;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat;
import java.io.IOException;
public class WholeFileDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//1.獲取Job實例
Job job = Job.getInstance(new Configuration());
//2.設置類路徑
job.setJarByClass(WholeFileDriver.class);
//3.設置Mapper和Reducer
//4.設置Mapper和Reducer的輸出類型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(BytesWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(BytesWritable.class);
job.setInputFormatClass(WholeFileInputFormat.class);
job.setOutputFormatClass(SequenceFileOutputFormat.class);
//5.設置輸入輸出參數
FileInputFormat.setInputPaths(job,new Path(args[0]));
FileOutputFormat.setOutputPath(job,new Path(args[1]));
//6.提交Job
boolean b = job.waitForCompletion(true);
System.exit(b ? 0 : 1);
}
}