MapReduce入门( 二 )
mr编程中, 利用好key的特性
- 排序 默认为字典序
- 分区 默认为key的哈希值对reducertask数量取模
- 分组 默认为key相同的为一组
在mr编程中,可以把上一个mr的输出目录直接作为下一个mr的输入 mr程序能够自动识别里面什么是检验性文件 什么是成功标识文件 什么是真正的数据文件
mr默认分区
源码: 类HashPartition
mr解决切片时 数据分割的问题
# 在切分时, 到达切片规则 正好将某个单词切成两半 原来: hadoop spark spark split1 : hadoop spa split2 : rk spark # mr解决方案: 如果这个切片不是第一个,则在读取数据时,会额外读取下一个切片的第一行, 保证数据准确
源码 : 类 LineRecordReader
mr程序执行步骤
Mapper任务( 分 )
将输入目录下的文件逐个进行逻辑切片(默认和分块Size一样), 每个切片由一个MapTask处理
有几个文件, 则最少有几个maptask
split1—>1.txt 0M-128M
split2—>1.txt 128M-200M
split3—>1.txt 0M-10M
TextInPutFormat组件解析文本内容 生成map的输入kv [偏移量,“每一行的文本”]
调用map方法, 执行业务逻辑, 输出0个或多个键值对到内存缓冲区
kv输出时先输出到内存缓冲区中, 缓冲区大小默认为100M, 缓冲区存到100M时, 溢出,写入磁盘
- 每溢出一次, 生成一个小文件
- 溢出行为最少发生一次 不能根据切片大小判断, 因为业务逻辑不确定,输出kv内容也不确定
进入磁盘的小文件, 会进行合并, 排序, 分区
- 对kv进行分区 , 分区的数量是reducer的数量 默认为1
- 对分区的键值对进行排序 , 先根据key排序, key相同的, 根据value排序, 默认为字典序
如果需要 : 局部聚合处理, 经过combiner, 键相等的kv会调用一次reduce方法
磁盘中的数据等待reducer拉取
Reducer任务 ( 合 )
- reducer去每个map所在磁盘中, 拿到属于自己的kv分区数据
- reducer任务获得多个mapper任务的输出kv , 作为自己的输入kv
- 合并数据, 进行排序
- 排序后, key相同的kv分为一组. 调用一次reduce, 产生0个或多个键值对
- reducer调用TextOutPutFormat组件最终输出到文件
Mapreduce序列化 排序
序列化(Serialization)是指把结构化对象转化为字节流
反序列化(Deserialization)是序列化的逆过程。把字节流转为结构化对象
java的序列化太重, 一个对象被序列化后, 会携带很多额外信息(校验,继承等等) , 网络IO开销太大
hadoop有自己的一套序列化机制, 实现Writable接口
- 因为要进行网络IO 所以自定义bean应该实现序列化接口WritableComparable
- 在实现接口时, 会实现read(反序列化方法)和write(序列化方法)
- 这两个方法在实现时, 各个字段的顺序应该保持一致
package com.hrh; import org.apache.hadoop.io.WritableComparable; import org.apache.hadoop.io.WritableComparator; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; /** * @Title: FlowBean * @ProjectName BigData * @Description: TODO 流量实体类 */ public class FlowBean implements WritableComparable<FlowBean>{ //上传流量 下载流量 总流量 private long upFlow; private long downFlow; private long sumFlow; //反序列化时 用到空参构造 public FlowBean() { } public FlowBean(long upFlow, long downFlow, long sumFlow) { this.upFlow = upFlow; this.downFlow = downFlow; this.sumFlow = 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 { this.upFlow = in.readLong(); this.downFlow = in.readLong(); this.sumFlow = in.readLong(); } }
Mapreduce 排序
如果自定义bean作为key, 还要在reduce阶段进行排序
- 实现WritableComparable接口, 实现compareTo方法
package com.hrh; import org.apache.hadoop.io.WritableComparable; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; /** * @Title: FlowBean * @ProjectName BigData * @Description: TODO 流量实体类 */ public class FlowBean implements WritableComparable<FlowBean> { //.....上述方法省略 getter setter 构造 //按流量总值倒序排列 @Override public int compareTo(FlowBean o) { return this.sumFlow>o.getSumFlow()?-1:1; } }
MapReducer分区
mapper输出的kv对, 每一个都会经过Partitioner分发给reducer处理
默认分发规则为 key的hashcode%reducetask数
如果需要配置自己的分发规则( 分给多个reducer )
- 自定义类继承Partitioner
- getPartition 返回的int值表示这个kv交给第几个reducer处理
- 在driver类中, job设置partitioner 设置reducetask的个数
package com.hrh; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Partitioner; import java.util.HashMap; /** * @Title: ProvincePartitioner * @ProjectName BigData * @Description: TODO */ public class ProvincePartitioner extends Partitioner<Text,FlowBean> { public static HashMap<String,Integer> provinceMap=new HashMap<>(); static{ provinceMap.put("134",0); provinceMap.put("135",1); provinceMap.put("136",2); provinceMap.put("137",3); provinceMap.put("138",4); } @Override public int getPartition(Text text, FlowBean flowBean, int numPartitions) { Integer code = provinceMap.get(text.toString().substring(0, 3)); if(code!=null){ return code; } return 5; } }
Mapreduce 分组
在reduce阶段, 默认为key相同的kv 分为一组, 调用一次reduce方法
如果key不相同, 又想分为一组
- 自定义类GroupingComparator实现WritableComparator
- Driver类 job.setGroupingComparatorClass();
package com.hrh.group; import org.apache.hadoop.io.WritableComparable; import org.apache.hadoop.io.WritableComparator; /** * @Title: GroupComparator * @ProjectName BigData * @Description: TODO */ public class GroupComparator extends WritableComparator { //必须写 传入作为key的bean的class类型,以及制定需要让框架做反射获取实例对象 public GroupComparator() { super(FlowBean.class,true); } @Override public int compare(WritableComparable a, WritableComparable b) { FlowBean aa= (FlowBean) a; FlowBean bb= (FlowBean) b; //只要上传流量相同视为key相同, 分为一组 return aa.getUpFlow()==bb.getUpFlow()?0:1; } }
Mapreduce Combiner
mapper的每一次map输出都可能生成很多kv combiner的作用就是先对map端的输出做局部合并
减少map-reduce的数据传输( 网络IO) 提高性能
- combiner组件的父类是reducer
- combiner在每个maptask节点运行
- reducer接收全局所有mapper的输出结果
- 可以没有, 是用来优化的
- 能够应用的前提是不能影响最终的业务逻辑
- 不适用于所有场景, 适合sum求和, 具体看业务需求
- 代码实现
- 自定义类实现reducer 重写reduce方法
- combiner 的输出 kv 应该跟 reducer 的输入 kv 类型要对应起来
- driver中 job设置 job.setCombinerClass