MapReduce编程规范
- Mapper阶段
(1)用户自定义的Mapper要继承自己的父类.
(2) Mapper的输入数据是KV对的形式(KV的类型可自定义)
(3) Mapper中的业务逻辑写在map()方法中
(4) Mapper的输出数据是KV对的形式(KV的类型可自定义)
(5) map()方法(MapTask进程) 对每一个K,V>调用一次 - Reducer阶 段
(1)用户自定义的Reducer要继承自己的父类
(2) Reducer的输入数据类型对应Mapper的输出数据类型,也是KV
(3) Reducer的业 务逻辑写在reduce()方法中。
(4) ReduceTask进 程对每- -组相同k的<k,v>组调用一次reduce()方法 - Driver阶 段
相当于YARN集群的客户端,用于提交我们整个程序到YARN集群,提交的是
封装了MapReduce程序相关运行参数的job对象
自定义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();
}
(5)注意反序列化的顺序和序列化的顺序完全一致
(6)要想把结果显示在文件中,需要重写toString(),可用”\t”分开,方便后续用。
(7)如果需要将自定义的bean放在key中传输,则还需要实现Comparable接口,因为MapReduce框中的Shuffle过程要求对key必须能排序。
@Override
public int compareTo(FlowBean o) {
// 倒序排列,从大到小
return this.sumFlow > o.getSumFlow() ? -1 : 1;
}
设置切片大小
驱动类中添加代码如下:
// 如果不设置InputFormat,它默认用的是TextInputFormat.class
job.setInputFormatClass(CombineTextInputFormat.class);
//虚拟存储切片最大值设置4m
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);
//运行如果为3个切片。
//虚拟存储切片最大值设置20m 运行结果为1个切片
CombineTextInputFormat.setMaxInputSplitSize(job, 20971520);
每一行均为一条记录,被分隔符分割为key value。可以通过在驱动类中设置
conf. set(KeyValueLineRecordReader.KEY_VALUE_SEPERATOR, "\t");来设定分隔符。默认分隔符是tab (\t) 。
NLinelnputF ormat
如果使用NlineInputFormat,代表每个map进程处理的InputSplit不再按B1ock块去划分,而是按NineInputF ormat指定的行数N来划分。即输入文件的总行数/N=切片数,如果不整除,切片数=商+1。
// 设置每个切片InputSplit中划分三条记录
NLineInputFormat.setNumLinesPerSplit(job, 3);
// 使用NLineInputFormat处理记录数
job.setInputFormatClass(NLineInputFormat.class);
自定义InputFormat
在企业开发中,Hadoop框架自带的InputF ormat类型不能满足所有应用场景,需要自定义InputF ormat来解决实际问题。自定义InputF ormat步骤如下:
(1)自定义一个类继承FileInputFormat。
(1)重写isSplitable0方法,返回false不可切割.
@Override
protected boolean isSplitable(JobContext context, Path filename) {
return false;
}
(2)重写createRecordReader),创建自定义的RecordReader对象,并初始化
@Override public RecordReader<Text, BytesWritable> createRecordReader(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
WholeRecordReader recordReader = new WholeRecordReader();
recordReader.initialize(split, context);
return recordReader;
(2)改写RecordReader, 实现-次读取一个完整文件 封装为KV。
(1)采用I0流一次读取一个文件输出到value中,因为设置了不可切片,最终把所有文 件都封装到了value中
(2)获取文件路径信息+名称,并设置key
public class WholeRecordReader extends RecordReader<Text, BytesWritable>{
private Configuration configuration;
private FileSplit split;
private boolean isProgress= true;
private BytesWritable value = new BytesWritable();
private Text k = new Text();
@Override
public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
this.split = (FileSplit)split;
configuration = context.getConfiguration();
}
@Override
public boolean nextKeyValue() throws IOException, InterruptedException {
if (isProgress) {
// 1 定义缓存区
byte[] contents = new byte[(int)split.getLength()];
FileSystem fs = null;
FSDataInputStream fis = null;
try {
// 2 获取文件系统
Path path = split.getPath();
fs = path.getFileSystem(configuration);
// 3 读取数据
fis = fs.open(path);
// 4 读取文件内容
IOUtils.readFully(fis, contents, 0, contents.length);
// 5 输出文件内容
value.set(contents, 0, contents.length);
// 6 获取文件路径及名称
String name = split.getPath().toString();
// 7 设置输出的key值
k.set(name);
} catch (Exception e) {
}finally {
IOUtils.closeStream(fis);
}
isProgress = false;
return true;
}
return false;
}
@Override
public Text getCurrentKey() throws IOException, InterruptedException {
return k;
}
@Override
public BytesWritable getCurrentValue() throws IOException, InterruptedException {
return value;
}
@Override
public float getProgress() throws IOException, InterruptedException {
return 0;
}
@Override
public void close() throws IOException {
}
}
(3)在输出时使用SequenceF ileOutPutFormat输出合并文件。
//设置输入的inputFormat
job.setInputFormatClass(WholeFileInputformat.class);
// 8设置输出的outputFormat
job.setOutputFormatClass(SequenceFileOutputFormat.class);
添加分区
- 继承Partitioner并重写getPartition()
#手机归属地不同省份输出到不同文件中(分区)
public class ProvincePartitioner extends Partitioner<Text, FlowBean> {
@Override
public int getPartition(Text key, FlowBean value, int numPartitions) {
// 1 获取电话号码的前三位
String preNum = key.toString().substring(0, 3);
int partition = 4;
// 2 判断是哪个省
if ("136".equals(preNum)) {
partition = 0;
}else if ("137".equals(preNum)) {
partition = 1;
}else if ("138".equals(preNum)) {
partition = 2;
}else if ("139".equals(preNum)) {
partition = 3;
}
return partition;
}
}
-
在驱动函数中增加自定义数据分区设置和ReduceTask设置
// 指定自定义数据分区 job.setPartitionerClass(ProvincePartitioner.class); // 同时指定相应数量的reduce task job.setNumReduceTasks(5);
WritableComparable排序
bean对象做为key传输,需要实现WritableComparable接口重写compareTo方法,就可以实现排序。
@Override
public int compareTo(FlowBean o) {
int result;
// 按照总流量大小,倒序排列
if (sumFlow > bean.getSumFlow()) {
result = -1;
}else if (sumFlow < bean.getSumFlow()) {
result = 1;
}else {
result = 0;
}
return result;
}
MapReduce开发总结(重点)
1.输入数据接口: InputFormat
(1)默认使用的实现类是: TextInputFormat
(2) TextInputFormat的功能逻辑是: 一次读一行文本, 然后将该行的起始 偏移量作为key,行内容作为value返回。
(3) KeyValue TextInputFormat每一行均为一 条记录,被分隔符分割为key, value。默认分隔符是tab (\t)
(4) NlineInputF ormat按照指定的行数N来划分切片。
(5) CombineTextInputFormat可以把多个小文件合并成一个切片处理,提高 处理效率。
(6)户还可以自定义InputF ormat。
2.逻辑处理接口: Mapper
用户根据业务需求实现其中三个方法: map() setup() cleanup()
- Partitioner分区
(1)有默认实现HasPatitioner,逻辑是根据key的哈希值和numReduces来返回-个分区号; key
hashCoe()&Intger.MAXVALUE % numReduces (2)如果业务上有特别的需求,可以自定义分区。
- Comparable排序
(1) 当我们用自定义的对象作为key来输出时,就必须要实现
WritableComparable接口,重写其中的compareTo()方法。
(2)部分排序:对最终输出的每一个文件进行内部排序。
(3)全排序:对所有数据进行排序,通常只有一个Reduce。
(4)二次排序:排序的条件有两个。
- Combiner合并
Combiner合并可以提高程序执行效率,减少I0传输。但是使用时必须不能影响原有的业务处理结果
- Reduce端分组: GroupingC omparator
在Reduc e端对kexj进行分组。应用于:在接收的key为bean对象时,想让一个或几个字段相同(全部字段比较不相同)的key进入到同一-个reduce方法时,可以采用分组排序。
7.逻辑处理接口: Reducer
用户根据业务需求实现其中三个方法: reduce() setup() cleamp ()
8.输出数据接口: OutputFormat
(1)默认实现类是TextOutputFormat, 功能逻辑是:将每-个KV对,向目标文 本文件输出一行。
(2)将SequenceFileOutputFormat输出作为后续MapReduce任务的输入,这便是 一种好的输出格式,因为它的格式紧凑,很容易被压缩。
(3)用户还可以自定义OutputFormat。