案例:
package cn.itheima.bigdata.hadoop.mr.wordcount;
import java.io.IOException;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class WordCountMapper extends Mapper<LongWritable, Text, Text, LongWritable>{
@Override
protected void map(LongWritable key, Text value,Context context)
throws IOException, InterruptedException {
//获取到一行文件的内容
String line = value.toString();
//切分这一行的内容为一个单词数组
String[] words = StringUtils.split(line, " ");
//遍历输出 <word,1>
for(String word:words){
context.write(new Text(word), new LongWritable(1));
}
}
}
package cn.itheima.bigdata.hadoop.mr.wordcount;
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class WordCountReducer extends Reducer<Text, LongWritable, Text, LongWritable>{
// key: hello , values : {1,1,1,1,1.....}
@Override
protected void reduce(Text key, Iterable<LongWritable> values,Context context)
throws IOException, InterruptedException {
//定义一个累加计数器
long count = 0;
for(LongWritable value:values){
count += value.get();
}
//输出<单词:count>键值对
context.write(key, new LongWritable(count));
}
}
package cn.itheima.bigdata.hadoop.mr.wordcount;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
/**
* 用来描述一个作业job(使用哪个mapper类,哪个reducer类,输入文件在哪,输出结果放哪。。。。)
* 然后提交这个job给hadoop集群
* @author duanhaitao@itcast.cn
*
*/
//cn.itheima.bigdata.hadoop.mr.wordcount.WordCountRunner
public class WordCountRunner {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job wcjob = Job.getInstance(conf);
//设置job所使用的jar包
conf.set("mapreduce.job.jar", "wcount.jar");
//设置wcjob中的资源所在的jar包
wcjob.setJarByClass(WordCountRunner.class);
//wcjob要使用哪个mapper类
wcjob.setMapperClass(WordCountMapper.class);
//wcjob要使用哪个reducer类
wcjob.setReducerClass(WordCountReducer.class);
//wcjob的mapper类输出的kv数据类型
wcjob.setMapOutputKeyClass(Text.class);
wcjob.setMapOutputValueClass(LongWritable.class);
//wcjob的reducer类输出的kv数据类型
wcjob.setOutputKeyClass(Text.class);
wcjob.setOutputValueClass(LongWritable.class);
//指定要处理的原始数据所存放的路径
FileInputFormat.setInputPaths(wcjob, "hdfs://yun12-01:9000/wc/srcdata");
//指定处理之后的结果输出到哪个路径
FileOutputFormat.setOutputPath(wcjob, new Path("hdfs://yun12-01:9000/wc/output"));
boolean res = wcjob.waitForCompletion(true);
System.exit(res?0:1);
}
}
mr job的几种运行模式
1、在eclipse中开发好mr程序(windows或linux下都可以),然后打成jar包(wc.jar),上传到服务器
执行命令 hadoop jar wc.jar cn.itheima.hadoop.MainClassRunner
这种方式会将这个job提交到yarn集群上去运行
2、在Linux的eclipse中直接启动Runner类的main方法,这种方式可以使job运行在本地,也可以运行在yarn集群
----究竟运行在本地还是在集群,取决于一个配置参数
mapreduce.framework.name == yarn (local)
----如果确实需要在eclipse中提交到yarn执行,必须做好以下两个设置
a/将mr工程打成jar包(wc.jar),放在工程目录下
b/在工程的main方法中,加入一个配置参数 conf.set("mapreduce.job.jar","wc.jar");
3、在windows的eclipse中运行本地模式,步骤为:
----a、在windows中找一个地方放一份hadoop的安装包,并且将其bin目录配到环境变量中
----b、根据windows平台的版本(32?64?win7?win8?),替换掉hadoop安装包中的本地库(bin,lib)
----c、mr程序的工程中不要有参数mapreduce.framework.name的设置
4、在windows的eclipse中运行main方法来提交job到集群执行,比较麻烦
----a、类似于方式3中所描述的对本地库兼容性进行改造
----b、修改YarnRunner这个类
默认的fileinputformat对数据处理的切片split规划
一、combiner
1、是在每一个map task的本地运行,能收到map输出的每一个key的valuelist,所以可以做局部汇总处理
2、因为在map task的本地进行了局部汇总,就会让map端的输出数据量大幅精简,减小shuffle过程的网络IO
3、combiner其实就是一个reducer组件,跟真实的reducer的区别就在于,combiner运行maptask的本地
4、combiner在使用时需要注意,输入输出KV数据类型要跟map和reduce的相应数据类型匹配
5、要注意业务逻辑不能因为combiner的加入而受影响
二、hadoop的序列化机制
跟jdk自带的比较起来,更加精简,只传递对象中的数据,而不传递如继承结构等额外信息
要想让自定义的数据类型在hadoop集群中传递,需要实现hadoop的序列化接口Writable或者WritableComparable<T>
自定义的数据类型bean实现了Writable接口后,要实现其中的两个方法
public void write(DataOutput out) throws IOException ----序列化,将数据写入字节流
以及
public void readFields(DataInput in) throws IOException ----反序列化,从字节流中读出数据
注意:
写入数据和读出数据的顺序和类型要保持一致
三、自定义排序
hadoop的排序是在shuffle中完成的
排序的依据是map输出的key
要想实现自定义的排序,就要将需要排序的数据封装到key中传输,并且要将数据实现WritableComparable接口
四、自定义分区 partition
****每一个reduce task输出一个结果文件
----自定义一个类AreaPartitioner继承 Partitioner抽象类,实现其中的方法int getPartition(K,V)
----在job的描述中设置使用自定义的partitioner
job.setPartitionerClass(AreaPartitioner.class)
----在job的描述中指定作业的reduce task并发数量,job.setNumReduceTasks(5),数量要与partitioner中的分区数一致
五、shuffle机制 —— map task的输出数据 到reduce task之间的一种数据调度机制
shuffle中最重要的功能是分组和排序
map task端先输出到本地缓存(内存缓冲区和磁盘文件)进行分组排序
在reduce task端还要再次进行归并排序
更多细节参见《hadoop权威指南3/4》