MapReduce
1.MapReduce介绍:
MapReduce是一个分布式运算程序的编程框架,它采用分而治之的思想解决海量数据的计算问题;MapReduce的核心功能是将用户编写的业务逻辑代码和它自带的默认组件整合成一个完整的分布式运算程序,并发运行在hadoop集群上
2.MapReduce程序运行阶段:
* Map阶段:局部并行计算(要求计算的程序之间不能太强的依赖关系)
* Reduce阶段:对Map阶段的结果进行全局汇总
3.MapReduce框架组件:
MRAppMaster:负责整个程序的过程调度及状态协调,job
MapTask:负责map阶段的整个数据处理流程,mapper
ReduceTask:负责reduce阶段的整个数据处理流程,reducer
4.MapReduce的编写规范
第一步:自定义一个类继承Mapper<k1,v1,k2,v2>类
MapReduce默认使用InputFormat类来读取文件信息,InputFormat类每读取文件中的一行信息,就会调用
一次Mapper中的map(k1,v1,contexy)方法,并将读取到的数据封装到v1中;
* key1:表示每一行的起始偏移量,通常用LongWritable,LongWritable是hadoop对Long类型的封装,提高网
络IO效率;起始偏移量是指从最左上角开始,每个字符算一个偏移量,也就是第二行的偏移量就是第一行的总字符个数
* value1:InputFormat读取一行数据后,封装到value1中,通常用Text,Text是hadoop对String类型的封装
* key2:表示对第一组<key1,value1>并行计算后,发送给reducer处理的key;reducer会根据map阶段的key2
进行分组,并将value2封装成Iterator迭代器
* value2:表示key2对应的返回值
第二步:自定义一个类继承Reducer<k1,v1,k2,v2>
当map阶段数据计算完毕后,reduceTask就会调用Reducer执行reduce方法完成全局汇总
* key1:表示map阶段发送的计算结果的key,与map阶段key2保持一致
* value1:表示map阶段计算结果value的迭代器对象,Iterator<value>,reducer会自动按照字典排序封装到iterator中
* key2:reducer将最终统计结果发送给MRAppMaster来展示,通常key1类型跟key2类型保持一致
* value2:发送给MRAppMaster
第三步:编写启动类,使用Job类将所有组件编织起来
5.将mapreduce提交给YARN:
先将项目打成jar包,并rz到linux下,执行"hadoop jar jar包名称"
深入理解MapReduce
1.MapReduce的输入和输出:
InputFormat读取一对键值对,mapTask开始调用mapper中的map方法,对初始的键值对进行理;reduceTask
将处理结果汇总,并调用reducer中的reduce方法对汇总结果处理得到最终结果;然后输出到MRAppMaster中;也就
是在整个标准的流程中,至少会产生3对键值对
2.mr数据分区:
reduceTask的个数跟最终生成的结果文件的个数相同,默认情况下,只有一个reduceTask,并将结果输出到part-r-00000的文件中;如果手动通过job.setNumReduceTasks(int n)方法设置reduceTask的个数,
那么最终生成的结果文件也会有n个,即文件分区;并且默认情况下分区文件中保存的数据的规则是: key.hashCode() % n,即分区文件中保存的数据根据key的哈希值取模于分区数决定的,因此也叫哈希值取模法
3.map阶段详细分析:
mr程序会扫描待处理文件目录中的所有文件,并对每一个待处理文件进行逻辑切片(默认按照分块大小128M对待处理文件进行逻辑切分),由逻辑切片的总数量决定mapTask的数量;一个mapTask对应一个逻辑切片,当TextInputFormat开始读取逻辑切片时,默认每读取一行返回一个键值对,并调用一次map方法;map方法执行完毕后,通过context.write()方法,先将局部并行处理结果保存到内存缓冲区,当内存不够时,再将内存数据排序并溢出到硬盘上;如果存在多个reducer-->job.setNumReduceTasks(),那么溢出的过程还会对数据进行分区处理再排序,具体分区规则便是key哈希值取模法;最终每一个逻辑分片对应一个mapTask,对应一块本地数据
4.reduce阶段详细分析:
reduceTask将map阶段的所有本地数据中数据当前reduceTask分区的数据全部读取到reduceTask本地数据中,根据key合并成迭代器,并且按照字典顺数对key排序,reduceTask中每有一个key就会调用一次reduce()方法,reduce()方法执行完毕后,通过context.write()方法,底层根据TextOutputFormat将最终结果输出到reduceTask对应的port-r-0000n文件中;由于key是已经排好序了的,因此文本文件中的结果也是排好序的
5.MapReduce序列化方序列化
当在进程间进行数据传输时就需要序列化,比如map-->reduce-->mrMaster,而使用jdk自带的可序列化接口效率非常低,Serializable是一个重量级的序列化接口,因此hadoop提供了Writable接口,来完成序列化和反序列化;但是当需要使用序列化对象作为key来进行比较时,就实现WritableComparable<T>;
Writable序列化机制的原理是需要传输对象的什么属性,就序列化什么属性;反序列化的顺序必须与序列化的顺序保持一致;Writable使用方式:自定义实体类,实现Writable接口,重写write和readFields方法,在方法内添加需要序列化的属性即可
6.如果需要对最终输出的结果排序,那么应该是在map-->reduce阶段,对返回的key进行处理,reduce-->mrMaster是不进行排序的
7.数据分区详细分析:
默认情况下,数据分区是由HashPartitioner中的key.hashCode() & Integer.MAX_VALUE) % numReduceTasks完成的,即key哈希取模法,&Integer.MAX_VALUE的作用是,如果key.hashCode()计算值为负数,那么取值就为key.hashCode() + Integer.MAX_VALUE;如果需要自定义分区规则:
第一步:自定义一个类,继承Partitioner<KEY,VALUE>;重新getPartition()方法,方法的返回值就是分区的编号尾号;
第二步:在引导类中引入自定义的Partitioner,job.setPartitionerClass(MyPartitioner.class);
8.分区个数与reduceTask个数比较:
默认情况下按照哈希取模法,getPartition() == numReduceTasks;正常执行
getPartition() < numReduceTasks ;正常执行,但是会多出空文件
getPartition() > numReduceTasks ;异常,Illegal partition
getPartition()决定数据应该放在哪个分区文件中,numReduceTasks决定分区文件个实际个数
9.MapReduce优化组件Combiner
reducer是对全局数据汇总计算,而map阶段的重复数据如果直接在网络IO中传输,会影响效率,因此可以引入Combiner来完成对map的局部聚合;使用Combiner的前提是,局部聚合的结果不会影响全局计算,Combiner使用方式:
第一步:自定义一个Combiner继承Reducer,重写reduce方法(combiner组件的父类就是Reducer)
第二步:在引导类中引入Combiner, job.setCombinerClass(MyCombiner.class)
案例:统计hdfs中文件下单词出现的次数
import org.apache.hadoop.*;
1.Mapper
public class WordCountMap extends Mapper<LongWritable, Text, Text, IntWritable> {
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//获取InputFormat读取到的每一行数据
String line = value.toString();
//对line按照空格切分
String[] wordArr = line.split(" ");
//获取每一个word
for (String word : wordArr) {
//将局部并行计算结果发送给reducer,word每出现一次,便发送一次
context.write(new Text(word),new IntWritable(1));
}
}
}
2.Reducer
public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
//定义统计变量
int count = 0;
//遍历value集合
for (IntWritable value : values) {
count += value.get();
}
//将最终结果发送给MrAppMaster
context.write(key, new IntWritable(count));
}
}
3.Driven
public class WordCountDriven {
public static void main(String[] args) throws Exception{
//通过Job来封装本次mr的相关信息
Configuration conf = new Configuration();
// conf.set("mapreduce.framework.name","local");
Job job = Job.getInstance(conf);
//指定本次mr job jar包运行主类
job.setJarByClass(WordCountDriver.class);
//指定本次mr 所用的mapper reducer类分别是什么
job.setMapperClass(WordCountMap.class);
job.setReducerClass(WordCountReducer.class);
//指定本次mr mapper阶段的输出 k v类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
//指定本次mr 最终输出的 k v类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//指定本次mr 输入的数据路径 和最终输出结果存放在什么位置
FileInputFormat.setInputPaths(job,"D:\\wordcount\\input");
FileOutputFormat.setOutputPath(job,new Path("D:\\wordcount\\output"));
//job.submit();无法监控程序执行情况
//提交程序 并且监控打印程序执行情况
boolean b = job.waitForCompletion(true);
System.exit(b?0:1);
}
}
4.pom.xml
<dependencies>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-core</artifactId>
<version>2.7.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<!--指定job jar运行主类的位置--> <mainClass>com.baidu.hadoop.mr.WordCountDriver</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
案例二:统计用户行为的上下行流量和总流量
1.序列化实体类
@Data
public class FlowBean implements Writable {
//上行流量
private Long upFlow;
//下行流量
private Long downFlow;
//总流量
private Long sumFlow;
@Override //间隔符使用横向字符表
public String toString() {
return upFlow + "\t" + downFlow + "\t" + sumFlow;
}
protected void set(Long upFlow, Long downFlow) {
this.upFlow = upFlow;
this.downFlow = downFlow;
this.sumFlow = upFlow + downFlow;
}
//序列化
@Override
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeLong(upFlow);
dataOutput.writeLong(downFlow);
dataOutput.writeLong(sumFlow);
}
//反序列化,注意属性顺序一致
@Override
public void readFields(DataInput dataInput) throws IOException {
this.upFlow = dataInput.readLong();
this.downFlow = dataInput.readLong();
this.sumFlow = dataInput.readLong();
}
}
2.mapper
public class FlowSumMapper extends Mapper<LongWritable, Text, Text, FlowBean> {
//避免多次在方法内部new,造成资源浪费
private FlowBean flowBean = new FlowBean();
private Text phone = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//获取读取的数据
String line = value.toString();
//根据横向字符表切割
String[] strArr = line.split("\t");
int length = strArr.length;
//防止数组索引越界
if (length < 3)
return;
String _phone = strArr[1];
phone.set(_phone);
//由于正向部分数据缺失,因此方向获取数据
String upFlow = strArr[length - 3];
String downFlow = strArr[length - 2];
flowBean.set(Long.parseLong(upFlow), Long.parseLong(downFlow));
context.write(phone, flowBean);
}
}
3.reducer
public class FlowSumReducer extends Reducer<Text, FlowBean, Text, FlowBean> {
private FlowBean flowBean = new FlowBean();
@Override
protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
Long upFlow = 0l;
Long downFlow = 0l;
for (FlowBean bean : values) {
upFlow += bean.getUpFlow();
downFlow += bean.getDownFlow();
}
flowBean.set(upFlow, downFlow);
context.write(key, flowBean);
}
}
4.引导类,同上...
window 环境下 hadoop bug
1.Exception in thread
"main"java.lang.UnsatisfiedLinkError:org.apache.hadoop.io.nativeio.NativeIO$Windows.
access0(Ljava/lang/String;I)Z
将hadoop/bin/hadoop.dll拷贝到C:\Windows\System32下
2.hadoop环境变量始终不生效
在代码中手动指定HADOOP_HOME位置,System.setProperty("hadoop.home.dir", "E:\\hadoop-2.7.4-with-windows\\hadoop-2.7.4");
3.检查hadoop环境变量是否配置成功
黑窗口输入hadoop