文章目录
05 MapReduce增强
1、MapReduce的分区与reduceTask的数量
mapreduce当中的分区:物以类聚,人以群分,相同key的数据,去往同一个reduce
reducetask的数量通过我们自己手动指定 job.setNumReduceTasks(3);
案例1:
需求:将以下数据进行分开处理
详细数据参见partition.csv 这个文本文件,其中第五个字段表示开奖结果数值,现在需求
将15以 上的结果以及15以下的结果进行分开成两个文件进行保存
分析
注意:分区的案例,只能打成jar包发布到集群上面去运行,本地模式已经不能正常运行了
public class PartitionMain extends Configured implements Tool {
@Override
public int run(String[] args) throws Exception {
//获取job对象,封装我们的job
Job job = Job.getInstance(getConf(), "myPartition");
//打成jar包到集群的时候需要这样写
job.setJarByClass(PartitionMain.class);
//第一步 读取文件解析成k1 v1
TextInputFormat.addInputPath(job,new Path(args[0]));
job.setInputFormatClass(TextInputFormat.class);
//第二步 自定义mapper逻辑
job.setMapperClass(PartitionMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(NullWritable.class);
//第三步 分区 相同key数据发送到同一个reduce当中去
job.setPartitionerClass(PartitionOwn.class);
//第四步:排序
//第五步:规约
//第六步:分组
//第七步:reduce逻辑
job.setReducerClass(PartitionReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NullWritable.class);
//设置ReduceTasks的数量,
//如果ReduceTasks数量比分区多,那么就会有空文件
//如果ReduceTasks数量比分区少,那么有些reduce需要处理更多的数据
job.setNumReduceTasks(2);
//第八步:输出
TextOutputFormat.setOutputPath(job,new Path(args[1]));
job.setOutputFormatClass(TextOutputFormat.class);
boolean b = job.waitForCompletion(true);
return b?0:1;
}
public static void main(String[] args) throws Exception {
int run = ToolRunner.run(new Configuration(),new PartitionMain(),args);
System.exit(run);
}
}
public class PartitionMapper extends Mapper<LongWritable, Text,Text, NullWritable> {
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//输出我们的 k2 v2 其中k2是我们一行文本类型 v2是NullWritable类型
context.write(value,NullWritable.get());
}
}
public class PartitionOwn extends Partitioner<Text, NullWritable> {
/**
* 这个方法决定我们的数据去往哪一个reduce
* @param text k2类型
* @param nullWritable v2类型
* @param numReduceTask
* @return
*/
@Override
public int getPartition(Text text, NullWritable nullWritable, int numReduceTask) {
String line = text.toString();
String[] split = line.split("\t");
//判断如果结果值大于15去一个分区,小于等于15去另外一个分区
if (Integer.parseInt(split[5])>15)
return 0;
else {
return 1;
}
}
}
public class PartitionReducer extends Reducer<Text, NullWritable,Text, NullWritable> {
//将我们的数据输出
@Override
protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
context.write(key,NullWritable.get());
}
}
[root@node1 servers]# hadoop jar original-day03_mr-1.0-SNAPSHOT.jar cn.itcast.mr.demo1.PartitionMain /partitionin /partitionout
2、MapReduce排序以及序列化
mapreduce当中的排序的功能:
默认是有排序功能的,按照字典顺序来进行排序,对key2进行排序
hadoop当中没有沿用java序列化serialize方式,使用的是writable接口,
实现了writable接口就可以序列化
如果需要序列化,需要实现 writable接口
如果需要排序 需要实现 comparable接口
如果既需要序列化,也需要排序 可以实现 writable和comparable 或者 WritableComparable
案例2:
数据格式内容如下
a 1
a 9
b 3
a 7
b 8
b 10
a 5
a 9
现在要求,对数据进行排序
二次排序
如果第一列相等,那么就比较第二列的值
排序规则作用在我们的key2上面
如果以一行文本内容作为k2 不能够实现二次排序的功能
能不能把这两个字段封装成一个javaBean当做我们k2
a 1
a 5
a 7
a 9
a 9
b 3
b 8
b 10
package cn.itcast.mr_demo2;
public class ComPareToTest {
public static void main(String[] args) {
//两个字段比较大小,只能用大于0,小于0或者等于零进行判断
//如果大于0 a>b
//如果小于0 b<a
//如果等于0 a=b
Integer a = 2;
Integer b = 2;
int i = a.compareTo(b);
System.out.println(i);
/*
String first = "xxx";
String second = "xxxxxx";
System.out.println(first.compareTo(second)); --> -3所以不能用等于0,等于1,等于-1判断
*/
}
}
package cn.itcast.mr_demo2;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
public class SortMain extends Configured implements Tool {
@Override
public int run(String[] strings) throws Exception {
//获取job对象
Job job = Job.getInstance(super.getConf(), "sort");
//第一步:读取文件,解析成k1,v1
job.setInputFormatClass(TextInputFormat.class);
TextInputFormat.addInputPath(job,new Path("file:///F:\\排序\\input"));
//第二步,自定义map逻辑,输入k1,v1 输出k2,v2
job.setMapperClass(SortMapper.class);
job.setMapOutputKeyClass(K2Bean.class);
job.setMapOutputValueClass(NullWritable.class);
/**
* 分区,排序,规约,分组省略
*/
//第七步:自定义reduce逻辑
job.setReducerClass(SortReducer.class);
job.setOutputKeyClass(K2Bean.class);
job.setOutputValueClass(NullWritable.class);
//第八步输出:
job.setOutputFormatClass(TextOutputFormat.class);
TextOutputFormat.setOutputPath(job,new Path("file:///F:\\排序\\output"));
//提交任务
boolean b = job.waitForCompletion(true);
return b?0:1;
}
public static void main(String[] args) throws Exception {
int run = ToolRunner.run(new Configuration(), new SortMain(), args);
System.exit(run);
}
}
package cn.itcast.mr_demo2;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class K2Bean implements WritableComparable<K2Bean> {
private String first;
private int second;
/**
* compareTo用于我们的数据比较排序
* @param o
* @return
*/
@Override
public int compareTo(K2Bean o) {
//如何进行比较
//首先比较第一个字段,如果第一个字段相同,则比较第二个字段
//如果不同,没有可比性,直接返回结果
//如果 i==0; 说明第一个字段相等
int i = this.first.compareTo(o.first);
if (i==0){
//第一个字段相等,继续比较第二个字段
int i1 = Integer.valueOf(this.second).compareTo(o.second);
return i1; //-i1则是降序排序
}else {
//直接将我们比较的结果返回
return i;
}
}
//序列化方法
@Override
public void write(DataOutput out) throws IOException {
out.writeUTF(first);
out.writeInt(second);
}
//反序列化方法
@Override
public void readFields(DataInput in) throws IOException {
this.first = in.readUTF();
this.second = in.readInt();
}
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public int getSecond() {
return second;
}
public void setSecond(int second) {
this.second = second;
}
@Override
public String toString() {
return first + "\t" +second;
}
}
package cn.itcast.mr_demo2;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class SortMapper extends Mapper<LongWritable, Text,K2Bean, NullWritable> {
//读取数据,封装到我们key2里去
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] split = value.toString().split("\t");
K2Bean k2Bean = new K2Bean();
k2Bean.setFirst(split[0]);
k2Bean.setSecond(Integer.parseInt(split[1]));
context.write(k2Bean,NullWritable.get());
}
}
package cn.itcast.mr_demo2;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class SortReducer extends Reducer<K2Bean, NullWritable,K2Bean, NullWritable> {
//直接将结果输出
@Override
protected void reduce(K2Bean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
for (NullWritable value : values) {
context.write(key,NullWritable.get());
}
}
}
3、MapReduce当中的计数器
计数器是收集作业统计信息的有效手段之一,用于质量控制或应用级统计。计数器还可辅助诊断系统故障。如果需要将日志信息
传输到map 或reduce 任务, 更好的方法通常是看能否用一个计数器值来记录某一特定事件的发生。对于大型分布式作业而言,
使用计数器更为方便。除了因为获取计数器值比输出日志更方便,还有根据计数器值统计特定事件的发生次数要比分析一堆日志
文件容易得多。
自定义我们的计数器
1.在mapper端
public class SortMapper extends Mapper<LongWritable, Text,K2Bean, NullWritable> {
//读取数据,封装到我们key2里去
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//计数我们maps输入了多少条数据
Counter counter = context.getCounter("MR_INPUT_COUNT", "TOTAL_RESULT");
counter.increment(1L);
String[] split = value.toString().split("\t");
K2Bean k2Bean = new K2Bean();
k2Bean.setFirst(split[0]);
k2Bean.setSecond(Integer.parseInt(split[1]));
context.write(k2Bean,NullWritable.get());
}
}
20/04/14 01:32:37 INFO mapred.JobClient: MR_INPUT_COUNT
20/04/14 01:32:37 INFO mapred.JobClient: TOTAL_RESULT=8
2.在reducer端
public class SortReducer extends Reducer<K2Bean, NullWritable,K2Bean, NullWritable> {
public static enum Counter{
REDUCE_INPUT_RECORD,
REDUCE_OUTPUT_RECORD
}
//直接将结果输出
@Override
protected void reduce(K2Bean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
org.apache.hadoop.mapreduce.Counter counter = context.getCounter(Counter.REDUCE_INPUT_RECORD);
counter.increment(1L);
for (NullWritable value : values) {
//定义我们输出数据的计数器
org.apache.hadoop.mapreduce.Counter counter1 = context.getCounter(Counter.REDUCE_OUTPUT_RECORD);
counter1.increment(1L);
context.write(key,NullWritable.get());
}
}
}
20/04/14 01:32:37 INFO mapred.JobClient: REDUCE_INPUT_RECORD=7
20/04/14 01:32:37 INFO mapred.JobClient: REDUCE_OUTPUT_RECORD=8
4、MapReduce的combiner(规约)
规约combiner:是发生在map端的一个小型的reduce,接收我们的k2,v2输出k2,v2
combiner就是一个reducer类,但是这个reduce类输入输出都是一样,在map阶段进行一个数据的合并,较少reduce阶段的数据量(调优)
但是求平均值不能用combiner,因为到reducer阶段数据变少了。
combiner不能改变我们数据的结果,只是用于调优,较少reduce端的输入数据量
//设置我们的规约类
job.setCombinerClass(SortReducer.class);
5、MapReduce综合练习之上网流量统计
需求1:统计求和
需求一:统计求和
统计每个手机号的上行流量总和,下行流量总和,上行总流量之和,下行总流量之和
分析:以手机号码作为key值,上行流量,下行流量,上行总流量,下行总流量四个字段作为value值,
然后以这个key,和value作为map阶段的输出,reduce阶段的输入
流量统计求和
将相同手机号的上行流量,下行流量,上行总流量,下行总流量,进行累加求和
相同key的数据发送到同一个reduce,形成一个集合
第一步:自定义map的输出value对象FlowBean
package cn.itcast.mr_demo3;
import org.apache.hadoop.io.Writable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class FlowNum implements Writable {
private Integer upFlow;
private Integer downFlow;
private Integer upCountFlow;
private Integer downCountFlow;
//序列化方法
@Override
public void write(DataOutput out) throws IOException {
out.writeInt(upFlow);
out.writeInt(downFlow);
out.writeInt(upCountFlow);
out.writeInt(downCountFlow);
}
//反序列化方法
@Override
public void readFields(DataInput in) throws IOException {
this.upFlow = in.readInt();
this.downFlow = in.readInt();
this.upCountFlow = in.readInt();
this.downCountFlow = in.readInt();
}
public Integer getUpFlow() {
return upFlow;
}
public void setUpFlow(Integer upFlow) {
this.upFlow = upFlow;
}
public Integer getDownFlow() {
return downFlow;
}
public void setDownFlow(Integer downFlow) {
this.downFlow = downFlow;
}
public Integer getUpCountFlow() {
return upCountFlow;
}
public void setUpCountFlow(Integer upCountFlow) {
this.upCountFlow = upCountFlow;
}
public Integer getDownCountFlow() {
return downCountFlow;
}
public void setDownCountFlow(Integer downCountFlow) {
this.downCountFlow = downCountFlow;
}
@Override
public String toString() {
return upFlow + '\t' + downFlow + "\t" + upCountFlow + "\t" + downCountFlow;
}
}
第二步:定义FlowMapper类
package cn.itcast.mr_demo3;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class FlowNumMapper extends Mapper<LongWritable, Text,Text,FlowNum> {
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] split = value.toString().split("\t");
String phoneNum = split[1];
int upFlow = Integer.parseInt(split[6]);
int downFlow = Integer.parseInt(split[7]);
int upCountFlow = Integer.parseInt(split[8]);
int downCountFlow = Integer.parseInt(split[9]);
FlowNum flowNum = new FlowNum();
flowNum.setUpFlow(upFlow);
flowNum.setDownFlow(downFlow);
flowNum.setUpCountFlow(upCountFlow);
flowNum.setDownCountFlow(downCountFlow);
context.write(new Text(phoneNum),flowNum);
}
}
第三步:定义FlowReducer类
package cn.itcast.mr_demo3;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class FlowNumReducer extends Reducer<Text,FlowNum,Text,FlowNum> {
@Override
protected void reduce(Text key, Iterable<FlowNum> values, Context context) throws IOException, InterruptedException {
int upFlow = 0;
int downFlow = 0;
int upCountFlow = 0;
int downCountFlow = 0;
for (FlowNum value : values) {
upFlow+=value.getUpFlow();
downFlow +=value.getDownFlow();
upCountFlow +=value.getUpCountFlow();
downCountFlow +=value.getDownCountFlow();
}
FlowNum flowNum = new FlowNum();
flowNum.setUpFlow(upFlow);
flowNum.setDownFlow(downFlow);
flowNum.setUpCountFlow(upCountFlow);
flowNum.setDownCountFlow(downCountFlow);
context.write(key,flowNum);
}
}
第四步:程序main函数入口FlowMain
package cn.itcast.mr_demo3;
import cn.itcast.mr.demo2.K2Bean;
import cn.itcast.mr.demo2.SortMain;
import cn.itcast.mr.demo2.SortMapper;
import cn.itcast.mr.demo2.SortReducer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
public class FlowNumMain extends Configured implements Tool {
@Override
public int run(String[] args) throws Exception {
//获取job对象
Job job = Job.getInstance(super.getConf(), "flowNum");
//打包运行需要设置
job.setJarByClass(FlowNumMain.class );
//第一步:读取文件解析成k1 v1
job.setInputFormatClass(TextInputFormat.class);
TextInputFormat.addInputPath(job,new Path("file:///xxxinput"));
//第二步:自定义map逻辑 ,输入 k1 v1 输出 k2 v2
job.setMapperClass(FlowNumMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowNum.class);
job.setPartitionerClass(PhonePartition.class);
/**
* 分区
* 排序
* 规约
* 分组
* 省略
*/
//第七步:自定义reduce逻辑
job.setReducerClass(FlowNumReducer.class);
//注意,reduce的个数,需要我们指定
job.setNumReduceTasks(6);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowNum.class);
//第八步:输出
job.setOutputFormatClass(TextOutputFormat.class);
TextOutputFormat.setOutputPath(job,new Path("file:///xxx"));
//提交任务
boolean b = job.waitForCompletion(true);
return b?0:1;
}
public static void main(String[] args) throws Exception {
int run = ToolRunner.run(new Configuration(), new FlowNumMain(), args);
System.exit(run);
}
}
需求2:上行流量倒序排序(递减排序)
分析:
第一步:定义FlowBean实现WritableComparable实现比较排序
package cn.itcast.mr.demo3;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class FlowNumSort implements WritableComparable<FlowNumSort> {
private Integer upFlow;
private Integer downFlow;
private Integer upCountFlow;
private Integer downCountFlow;
@Override
public void write(DataOutput out) throws IOException {
out.writeInt(upFlow);
out.writeInt(downFlow);
out.writeInt(upCountFlow);
out.writeInt(downCountFlow);
}
@Override
public void readFields(DataInput in) throws IOException {
this.upFlow = in.readInt();
this.downFlow = in.readInt();
this.upCountFlow = in.readInt();
this.downCountFlow = in.readInt();
}
public Integer getUpFlow() {
return upFlow;
}
public void setUpFlow(Integer upFlow) {
this.upFlow = upFlow;
}
public Integer getDownFlow() {
return downFlow;
}
public void setDownFlow(Integer downFlow) {
this.downFlow = downFlow;
}
public Integer getUpCountFlow() {
return upCountFlow;
}
public void setUpCountFlow(Integer upCountFlow) {
this.upCountFlow = upCountFlow;
}
public Integer getDownCountFlow() {
return downCountFlow;
}
public void setDownCountFlow(Integer downCountFlow) {
this.downCountFlow = downCountFlow;
}
@Override
public String toString() {
return upFlow + "\t" + downFlow + "\t" + upCountFlow + "\t" + downCountFlow;
}
@Override
public int compareTo(FlowNumSort o) {
//安装上行流量进行排序
int i = this.upFlow.compareTo(o.upFlow);
return i;
}
}
第二步:定义FlowMapper
package cn.itcast.mr.demo3;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class FlowNumSortMapper extends Mapper<LongWritable, Text,FlowNumSort,Text> {
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] split = value.toString().split("\t");
FlowNumSort flowNumSort = new FlowNumSort();
flowNumSort.setUpFlow(Integer.parseInt(split[1]));
flowNumSort.setDownFlow(Integer.parseInt(split[2]));
flowNumSort.setUpCountFlow(Integer.parseInt(split[3]));
flowNumSort.setDownCountFlow(Integer.parseInt(split[4]));
context.write(flowNumSort,new Text(split[0]));
}
}
第三步:定义FlowReducer
package cn.itcast.mr.demo3;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class FlowNumSortReducer extends Reducer<FlowNumSort, Text,FlowNumSort,Text> {
@Override
protected void reduce(FlowNumSort key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
for (Text value : values) {
context.write(key,value);
}
}
}
第四步:程序main函数入口
package cn.itcast.mr.demo3;
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.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
public class FlowNumSortMain extends Configured implements Tool {
@Override
public int run(String[] args) throws Exception {
Job job = Job.getInstance(super.getConf(), "FlowNumSort");
//step1
job.setInputFormatClass(TextInputFormat.class);
TextInputFormat.addInputPath(job,new Path("file:///E:\\大数据资料\\大数据离线资料\\4、大数据离线第四天\\流量统计\\output"));
//step2
job.setMapperClass(FlowNumSortMapper.class);
job.setMapOutputKeyClass(FlowNumSort.class);
job.setMapOutputValueClass(Text.class);
//分区,排序,分组,规约
//step7
job.setReducerClass(FlowNumSortReducer.class);
job.setOutputKeyClass(FlowNumSort.class);
job.setOutputValueClass(Text.class);
//step8
job.setOutputFormatClass(TextOutputFormat.class);
TextOutputFormat.setOutputPath(job,new Path("file:///E:\\大数据资料\\大数据离线资料\\4、大数据离线第四天\\流量统计\\outsortByupFlow"));
boolean b = job.waitForCompletion(true);
return b?0:1;
}
public static void main(String[] args) throws Exception {
int run = ToolRunner.run(new Configuration(), new FlowNumSortMain(), args);
System.exit(run);
}
}
需求3:手机号码分区
在需求一的基础上,继续完善,将不同的手机号分到不同的数据文件的当中去,需要自定义分区
来实现,这里我们自定义来模拟分区:
将以下数字开头的手机号进行分开。
137 开头数据到一个分区文件
138 开头数据到一个分区文件
139 开头数据到一个分区文件
135 开头数据到一个分区文件
136 开头数据到一个分区文件
其他分区
分区类
package cn.itcast.mr.demo2;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
public class PhonePartition extends Partitioner<Text,FlowNum> {
@Override //接受k2,v2 手机号 与流量汇总
public int getPartition(Text text, FlowNum flowNum, int i) {
String phoneNum = text.toString();
if (phoneNum.startsWith("135")){
return 0;
}
else if (phoneNum.startsWith("136")){
return 1;
}
else if (phoneNum.startsWith("137")){
return 2;
}
else if (phoneNum.startsWith("138")){
return 3;
}
else if (phoneNum.startsWith("139")){
return 4;
}else {
return 5;
}
}
}
main函数设置分区类与reduce tasks个数
job.setPartitionerClass(PhonePartition.class);
job.setNumReduceTasks(6);
6、MapTask运行机制详解以及Map任务的并行度
Map任务的并行度
maptask的并行度,其实就是指代有多少个maptask的任务以及reducetask的任务
详细步骤:
TextInputFormat继承FileInputFormat
切片大小如何决定的???
如果一个切片256M,那么一个512M的文件产生两个切片,产生两个maptask
如果一个切片是128M ,那么一个512M的文件产生四个切片,产生四个maptask
获取文件的切片的几个参数控制:
mapred.min.split.size 没有配置的话默认值是1
mapred.max.split.size 没有配置的话默认值是 Long.MAX_VALUE
*如果没有配置上面这两个参数,我们文件的切片大小就是128M,与我们的block块相等
*1Kb的一个文件 一个block块 一个切片 1个maptaks
*300M的一个文件 三个block块 三个切片 3个maptask
*一般maptask的个数就是与我们block块的大小有关系
MapTask运行机制详解
环形缓冲区的大小,涉及到我们maptask的调优的过程,如果内存充足,可以将换型缓冲区的大小稍微调大,避免我们大量的小文件需要合并
1、首先,读取数据组件InputFormat(默认TextInputFormat)会通过getSplits方法对
输入目录中文件进行逻辑切片规划得到splits,有多少个split就对应启动多少个MapTask。
split与block的对应关系默认是一对一。
2、将输入文件切分为splits之后,由RecordReader对象(默认LineRecordReader)进行
读取,以\n作为分隔符,读取一行数据,返回<key,value>。Key表示每行首字符偏移值,
value表示这一行文本内容。
3、读取split返回<key,value>,进入用户自己继承的Mapper类中,执行用户重写的map
函数。RecordReader读取一行这里调用一次。
4、map逻辑完之后,将map的每条结果通过context.write进行collect数据收集。
在collect中,会先对其进行分区处理,默认使用HashPartitioner。
5、会将数据写入内存,内存中这片区域叫做环形缓冲区(作用是批量收集map结果,减少磁盘IO的
影响)我们的key/value对以及Partition的结果都会被写入缓冲区。当然写入之前,
key与value值都会被序列化成字节数组。
6、当溢写线程启动后,需要对这80MB空间内的key做排序(Sort)。排序是MapReduce模型
默认的行为,这里的排序也是对序列化的字节做的排序。如果job设置过Combiner,那么现在
就是使用Combiner的时候了。将有相同key的key/value对的value加起来,减少溢写到磁盘
的数据量。Combiner会优化MapReduce的中间结果,所以它在整个模型中会多次使用。
(Combiner绝不能改变最终的计算结果。Combiner只应该用于那种Reduce的输入key/value
与输出key/value类型完全一致,且不影响最终结果的场景。比如累加,最大值等。Combiner
的使用一定得慎重,如果用好,它对job执行效率有帮助,反之会影响reduce的最终结果。)
7、合并溢写文件:每次溢写会在磁盘上生成一个临时文件(写之前判断是否有combiner),
如果map的输出结果真的很大,有多次这样的溢写发生,磁盘上相应的就会有多个临时文件存在。
当整个数据处理结束之后开始对磁盘中的临时文件进行merge合并,因为最终的文件只有一个,
写入磁盘,并且为这个文件提供了一个索引文件,以记录每个reduce对应数据的偏移量。
mapTask的一些基础设置配置(mapred-site.xml当中设置):
设置一:设置环型缓冲区的内存值大小(默认设置如下)
mapreduce.task.io.sort.mb 100
设置二:设置溢写百分比(默认设置如下)
mapreduce.map.sort.spill.percent 0.80
设置三:设置溢写数据目录(默认设置)
mapreduce.cluster.local.dir ${hadoop.tmp.dir}/mapred/local
设置四:设置一次最多合并多少个溢写文件(默认设置如下)
mapreduce.task.io.sort.factor 10
7、ReduceTask 工作机制以及reduceTask的并行度
Reduce大致分为copy、sort、reduce三个阶段。
重点在前两个阶段。
copy阶段包含一个eventFetcher来获取已完成的map列表,由Fetcher线程去copy数据,在此过程中会启动两个merge线程,分别为inMemoryMerger和onDiskMerger,分别将内存中的数据merge到磁盘和将磁盘中的数据进行merge。
待数据copy完成之后,copy阶段就完成了,开始进行sort阶段,sort阶段主要是执行finalMerge操作,纯粹的sort阶段,完成之后就是reduce阶段,调用用户定义的reduce函数进行处理。
详细步骤:
1、Copy阶段,简单地拉取数据。Reduce进程启动一些数据copy线程(Fetcher),通过HTTP
方式请求maptask获取属于自己的文件。
2、Merge阶段。这里的merge如map端的merge动作,只是数组中存放的是不同map端copy来
的数值。Copy过来的数据会先放入内存缓冲区中,这里的缓冲区大小要比map端的更为灵活。
merge有三种形式:内存到内存;内存到磁盘;磁盘到磁盘。默认情况下第一种形式不启用。
当内存中的数据量到达一定阈值,就启动内存到磁盘的merge。与map 端类似,这也是溢写的
过程,这个过程中如果你设置有Combiner,也是会启用的,然后在磁盘中生成了众多的溢写文
件。第二种merge方式一直在运行,直到没有map端的数据时才结束,然后启动第三种磁盘到
磁盘的merge方式生成最终的文件。
3、合并排序。把分散的数据合并成一个大的数据后,还会再对合并后的数据排序。
4、对排序后的键值对调用reduce方法,键相等的键值对调用一次reduce方法,每次调用会
产生零个或者多个键值对,最后把这些输出的键值对写入到HDFS文件中。
8、MapReduceshuffle过程
shuffle是Mapreduce的核心,它分布在Mapreduce的map阶段和reduce阶段。一般把从Map产生输出开始到Reduce取得数据作为输入之前的过程称作shuffle。
1).Collect阶段:将MapTask的结果输出到默认大小为100M的环形缓冲区,保存的是
key/value,Partition分区信息等。
2).Spill阶段:当内存中的数据量达到一定的阀值的时候,就会将数据写入本地磁盘,在将
数据写入磁盘之前需要对数据进行一次排序的操作,如果配置了combiner,还会将有相同分区
号和key的数据进行排序。
3).Merge阶段:把所有溢出的临时文件进行一次合并操作,以确保一个MapTask最终只产生一
个中间数据文件。
4).Copy阶段:ReduceTask启动Fetcher线程到已经完成MapTask的节点上复制一份属于自
己的数据,这些数据默认会保存在内存的缓冲区中,当内存的缓冲区达到一定的阀值的时候,
就会将数据写到磁盘之上。
5).Merge阶段:在ReduceTask远程复制数据的同时,会在后台开启两个线程对内存到本地的
数据文件进行合并操作。
6).Sort阶段:在对数据进行合并的同时,会进行排序操作,由于MapTask阶段已经对数据进行
了局部的排序,ReduceTask只需保证Copy的数据的最终整体有效性即可。
Shuffle中的缓冲区大小会影响到mapreduce程序的执行效率,原则上说,缓冲区越大,磁盘
io的次数越少,执行速度就越快
缓冲区的大小可以通过参数调整, 参数:mapreduce.task.io.sort.mb 默认100M
9、shuffle阶段数据的压缩机制
从map阶段输出的数据,都要通过网络拷贝,发送到reduce阶段,这一过程中,涉及到大量的
网络IO,如果数据能够进行压缩,那么数据的发送量就会少得多
查看hadoop当中的本地库支持哪些压缩算法
bin/hadoop checknative
其中有一个snappy的压缩库,需要重新编译hadoop的源码,使其能够支持snappy方式的压缩
9.1、hadoop当中支持的压缩算法
方式一:在代码中进行设置压缩
设置我们的map阶段的压缩
Configuration configuration = new Configuration();
configuration.set("mapreduce.map.output.compress","true");
configuration.set("mapreduce.map.output.compress.codec","org.apache.hadoop.io.compress.SnappyCodec");
设置我们的reduce阶段的压缩
configuration.set("mapreduce.output.fileoutputformat.compress","true");
configuration.set("mapreduce.output.fileoutputformat.compress.type","RECORD");
configuration.set("mapreduce.output.fileoutputformat.compress.codec","org.apache.hadoop.io.compress.SnappyCodec");
方式二:配置全局的MapReduce压缩
修改mapred-site.xml配置文件
map输出数据进行压缩
<property>
<name>mapreduce.map.output.compress</name>
<value>true</value>
</property>
<property>
<name>mapreduce.map.output.compress.codec</name>
<value>org.apache.hadoop.io.compress.SnappyCodec</value>
</property>
reduce输出数据进行压缩
<property>
<name>mapreduce.output.fileoutputformat.compress</name>
<value>true</value>
</property>
<property>
<name>mapreduce.output.fileoutputformat.compress.type</name>
<value>RECORD</value>
</property>
<property>
<name>mapreduce.output.fileoutputformat.compress.codec</name>
<value>org.apache.hadoop.io.compress.SnappyCodec</value> </property>
所有节点都要修改mapred-site.xml,修改完成之后记得重启集群
10、 更多MapReduce编程案例
10.1 reduce端join算法实现
1、需求:
商品信息表t_product
p0001,小米5,1000,2000
p0002,锤子T1,1000,3000
订单数据表t_order:
1001,20150710,p0001,2
1002,20150710,p0002,3
1002,20150710,p0003,3
假如数据量巨大,两表的数据是以文件的形式存储在HDFS中,需要用mapreduce程序来
实现一下SQL查询运算:
select a.id,a.date,b.name,b.category_id,b.price from t_order a join t_product b on a.pid = b.id
要求最后数据出来是这样的格式
1001,20150710,p0001,2 p0001,小米5,1000,2000
1002,20150710,p0002,3 p0002,锤子T1,1000,3000
1002,20150710,p0003,3 null
如何解决???
关键点:将相同的producetId发送到同一个reduce里面去,形成一个集合
如果以productId作为我们key2
package cn.itcast.mr.demo4;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import java.io.IOException;
public class ReduceJoinMapper extends Mapper<LongWritable, Text,Text,Text> {
Text k2 = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//如何判断文件究竟是从哪个文件里面来的
//获取文件切片
FileSplit inputSplit = (FileSplit) context.getInputSplit();
//获取文件存放路径
Path path = inputSplit.getPath();
//获取文件名称
String name = path.getName();
System.out.println(name);
//可以使用文件名来判断
if (name=="orders.txt"){
//订单表数据
String line = value.toString();
String[] split = line.split(",");
k2.set(split[2]);
context.write(k2,value);
}
else {
//商品表数据
String line = value.toString();
String[] split = line.split(",");
k2.set(split[0]);
context.write(k2,value);
}
}
}
package cn.itcast.mr.demo4;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class ReduceJoinReducer extends Reducer<Text,Text,Text, NullWritable> {
@Override
protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
String orderLine = "";
String productLine = "";
for (Text value : values) {
if (value.toString().startsWith("p")){
productLine = value.toString();
}else {
orderLine = value.toString();
}
}
context.write(new Text(orderLine+"\t"+productLine),NullWritable.get());
}
}
package cn.itcast.mr.demo4;
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.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
public class ReduceJoinMain extends Configured implements Tool {
@Override
public int run(String[] args) throws Exception {
Job job = Job.getInstance(super.getConf(), "ReduceJoin");
//step1
job.setInputFormatClass(TextInputFormat.class);
TextInputFormat.addInputPath(job,new Path("file:///E:xxx\\input"));
//step2
job.setMapperClass(ReduceJoinMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
//分区,排序,分组,规约
//step7
job.setReducerClass(ReduceJoinReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
//step8
job.setOutputFormatClass(TextOutputFormat.class);
TextOutputFormat.setOutputPath(job,new Path("file:///E:xxx\\output"));
boolean b = job.waitForCompletion(true);
return b?0:1;
}
public static void main(String[] args) throws Exception {
int run = ToolRunner.run(new Configuration(), new ReduceJoinMain(), args);
System.exit(run);
}
}
10.2 map端join算法实现
map端以及reduce端的join操作
reduce端join操作 :将相同的key发送到同一个reduce里面去
搞定 map端的join操作:
放一个文件到hdfs上面去 通过DistributedCache添加一个缓存文件
每个maptask里面都可以获取到缓存文件,将缓存文件的内容保存到map当中去
接收我们输入的数据,然后从map当中获取对应的匹配的数据,组成一行输出
Mapper类
package cn.itcast.mr.demo5;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.filecache.DistributedCache;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
public class MapJoinMapper extends Mapper<LongWritable, Text,Text, NullWritable> {
//在我们程序主类添加了缓存文件,这里可以获取缓存文件
/*
重写setup方法,获取缓存文件并存储到map中去
*/
Map<String,String> map = null;
@Override
protected void setup(Context context) throws IOException, InterruptedException {
map = new HashMap<String,String>();
Configuration configuration = context.getConfiguration();
//我们只有一个缓存文件
URI[] cacheFiles = DistributedCache.getCacheFiles(configuration);
//获取我们缓存文件的URI,拿到了文件访问的URI,那么我们就可以访问文件
//hdfs://node1:8020/cachefile/pdts.txt
URI cacheFile = cacheFiles[0];
//获取文件系统
FileSystem fileSystem = FileSystem.get(cacheFile,configuration);
//获取文件输入流,如何将流转化读取成字符串
FSDataInputStream open = fileSystem.open(new Path(cacheFile));
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(open));
String line = null;
while ((line = bufferedReader.readLine())!=null){
String[] lineArray = line.split(",");
map.put(lineArray[0],line);
}
}
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] split = value.toString().split(",");
//获取到我们商品表的数据
String product = map.get(split[2]);
//将我们商品表与订单表中数据进行拼接,然后输出
context.write(new Text(value.toString()+"\t"+product),NullWritable.get());
}
}
主类
package cn.itcast.mr.demo5;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.filecache.DistributedCache;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
import java.net.URI;
public class MapJoinMain extends Configured implements Tool {
@Override
public int run(String[] args) throws Exception {
Configuration conf = super.getConf();
//添加缓存文件
DistributedCache.addCacheFile(new URI("hdfs://node1:8020/cachefile/pdts.txt"),conf);
Job job = Job.getInstance(conf, "MapJoin");
job.setInputFormatClass(TextInputFormat.class);
TextInputFormat.addInputPath(job,new Path("file:///E:\\大数据资料\\大数据离线资料\\4、大数据离线第四天\\map端join\\map_join_iput"));
job.setMapperClass(MapJoinMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(NullWritable.class);
job.setOutputFormatClass(TextOutputFormat.class);
TextOutputFormat.setOutputPath(job,new Path("file:///E:\\大数据资料\\大数据离线资料\\4、大数据离线第四天\\map端join\\map_join_output"));
boolean b = job.waitForCompletion(true);
return b?0:1;
}
public static void main(String[] args) throws Exception {
int run = ToolRunner.run(new Configuration(), new MapJoinMain(), args);
System.exit(run);
}
}