MapReduce是一个分布式运算程序,核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在Hadoop集群上。特点是易于编程,用户只用关心业务逻辑,有良好的拓展性,可以动态增加服务器的数量,高容错,可以在任务挂掉的时候将任务转移给其他节点;适合海量数据计算。缺点是不适合实时计算,不适合流式数据,不擅长DAG有向无环图计算
MapReduce分为两个阶段,Map阶段和Reduce阶段,Map阶段的的并发MapTask完全并发运行互不干扰,Reduce阶段的并发ReduceTask互不相干,但是他们的数据依赖于Map阶段的输出,MapReduce编程模型只能包含一个Map阶段和一个Reduce阶段,如果用户的业务逻辑比较复杂,只能多个MapReduce程序串行运行
编写Mapper
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
private Text outK = new Text();
private IntWritable outV = new IntWritable(1); //map阶段不进行聚合
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1 获取一行
String line = value.toString();
// 2 切割(取决于原始数据的中间分隔符)
String[] words = line.split(" ");
// 3 循环写出
for (String word : words) {
// 封装outk
outK.set(word);
// 写出
context.write(outK, outV);
}
}
}
编写Reducer
public class WordCountReducer extends Reducer<Text, IntWritable,Text,IntWritable> {
private IntWritable outV = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum = 0;
// 将values进行累加
for (IntWritable value : values) {
sum += value.get();
}
outV.set(sum);
// 写出
context.write(key,outV);
}
}
编写Driver
public class WordCountDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 1 获取job
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 2 设置jar包路径
job.setJarByClass(WordCountDriver.class);
// 3 关联mapper和reducer
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
// 4 设置map输出的kv类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 5 设置最终输出的kV类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 6 设置输入路径和输出路径
FileInputFormat.setInputPaths(job, new Path("D:\\input\\inputword"));
FileOutputFormat.setOutputPath(job, new Path("D:\\hadoop\\output888"));
// 7 提交job
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
MapTask的并行度决定Map阶段的任务处理并发度,进而影响整个Job的处理速度。
数据块:HDFS的存储单位
数据切片:逻辑上对输入进行分片,并不会在磁盘上将其切分成片进行存储。数据切片是MapReduce程序计算输入数据的单位,一个切片会对应启动一个MapTask
切片大小和数据块大小一致时效率比较高,默认情况一样大
Math.max(minSize,Math.min(maxSize,blockSize))
,默认情况minSize为1,maxSize为Long的最大值小文件过多会产生大量的MapTask,处理效率极其低下,因此可以用CombineTextInputFormat处理小文件过多的场景,这样多个小文件可以合并成一个切片进行处理。可以通过设置MaxInputSplitSize来调整虚拟存储切片的大小,从而控制小文件合并。
map之后,reduce之前。
job.setPartitionerClass(CustomPartitioner.class)
job.setNumReduceTasks(2);
,如果小于partition的数量会爆异常,如果大于partition的数量会产生空文件当缓冲队列到达百分之80时,会进行快速排序,排的是key。而当自定义类型作为key时,就需要使得自定义类型实现WriteComparable接口。
Combiner是mr之外的一种组件,是可选的。父类是Reducer,区别在于Combiner是每一个Maptask所在的节点运行,Reducer是接收全局所有mapper的输出结果。Combiner的意义就是对每一个MapTask的输出进行局部汇总,以减少网络传输量。实际场景中不需要自己定义,使用自己的Reducer即可。
Reduce之后默认使用textOutputFormat,按行写出。可以自定义OutputFormat来实现文件的写出,比如写到mysql、写到es、写到log等。
public class LogOutputStream extends FileOutputStream<Text,NullWritable> {
private FSDataOutputStream out1;
private FSDataOutputStream out2;
public RecordWriter<Text,NullWritable> getRecordWriter(TaskAttemptContext job) throws IOException,InterruptedException{
LogRecordWriter lrw = new LogRecordWriter(job);
return lrw;
}
}
public class LogRecordWriter extends RecordWriter<Text,NullWritable> {
public LogRecordWriter(TaskAttemptContext job){
FileSystem fs=FileSystem.get(job.getConfiguration());
out1 = fs.create(new Path("输出路径1"));
out2 = fs.create(new Path("输出路径2"));
}
public void write(Text key, NullWritable value) throws IOException,InterruptedException {
String log=key.toString();
if(log.contains("xxxx")){
out1.writeBytes(log);
}else{
out2.writeBytes(log);
}
}
public void close(TaskAttemptContext context) throws IOException,InterruptedException {
IOUtils.closeStream(out1);
IOUtils.closeStream(out2);
}
}
客户端对文件进行切片划分,然后提交给yarn,包括Job.split、wc.jar、Job.xml,接下来yarn开启MrAppMaster。MrAppMaster计算出MapTask数量,每个MapTask由inputFormat去读去数据,默认key为偏移量,value为一行数据。
由用户自定义的map来实现
环形缓冲区内部进行的分区和排序
环形缓冲区产生大量的溢写文件
对溢写阶段的文件进行归并合并
拉取自己指定分区的数据
对拉取的数据进行归并排序
reduce方法的操作
利用MapReduce合并两张表的话可以讲共同字段作为key,然后在reduce端进行合并,但是产生的问题是如果数据量特别大,对于reduce端的压力会很大。因此可以用Map join的方式。
思想:适用于一张小表和一张大表的情况,将小表放到内存中,然后大表直接对应。
方案:采用DistributedCache,将小表以缓存数据加载,在mapper的setup方法中将文件读取到缓存集合中。
driver加载缓存文件:job.addCacheFile("路径")
setup中获取缓存文件:context.getCacheFiles();
Map输入端:数据量小则考虑压缩和解压缩速率,比如LZO和snappy;数据量大则考虑是否能切片选Bzip2和LZO
Map->Reduce:减少网络IO,重点考虑速率LZO和snappy
reduce输出端:如果数据永久保存,考虑压缩率,则选Bzip2和Gzip