MapReduce中定义了如下的Map和Reduce两个抽象的编程接口,由用户去编程实现:
map: (k1; v1) → [(k2; v2)]
输入:键值对(k1; v1)表示的数据
处理:文档数据记录(如文本文件中的行,或数据表格中的行)将以“键值对”形式传入map函数;map函数将处理这些键值对,并以另一种键值对形式输出处理的一组键值对中间结果[(k2; v2)]
输出:键值对[(k2; v2)]表示的一组中间数据
reduce: (k2; [v2]) → [(k3; v3)]
输入: 由map输出的一组键值对[(k2; v2)] 将被进行合并处理将同样主键下的不同数值合并到一个列表[v2]中,故reduce的输入为(k2; [v2])
处理:对传入的中间结果列表数据进行某种整理或进一步的处理,并产生最终的某种形式的结果输出[(k3; v3)]
输出:最终输出结果[(k3; v3)]
public class Sogou500wDemo {
private static class UidMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] fields = value.toString().split("\t");
context.write(new Text(fields[1]), new IntWritable(1));
}
}
private static class UidReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable value : values) {
sum += value.get();
}
context.write(key, new IntWritable(sum));
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, Sogou500wDemo.class.getSimpleName());
job.setJarByClass(Sogou500wDemo.class);
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
job.setMapperClass(UidMapper.class);
job.setReducerClass(UidReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
1、打包作业
2、启动作业
3、通过web UI查看Job状态
4、获取结果
一旦作业完成,有许多方法可以获取结果。每个reducer产生一个输出文件,因此,在输出的目录中会有part file,命名为part-00000。
6、作业调试
最经典的方法通过打印语句来调试程序,这在Hadoop中同样适用。然而,需要考虑复杂的情况:当程序运行在几十台、几百台甚至几千台节点上时,如何找到并检测调试语句分散在这些节点中的输出呢?为了处理这种情况,要查找一个特殊情况,用一个调试语句记录到一个标准错误中,它将发送一个信息来更新任务的状态信息以提示查看错误日志。Web UI简化了这个操作。
1、MapReduce中的键值对
Hadoop的MapReduce中,map和reduce函数遵循如下常规格式:
map: (K1, V1) list(k2, v2)
reduce: (K2,list(v2)) list(k3, v3)
一般来说,map函数输入的键/值的类型(K1和V1)类型不同于输出类型(K2和V2)。虽然,reduce函数的输入类型必须与map函数的输出类型相同,但reduce函数的输出类型(K3和V3)可以不同于输入类型。
combiner函数,它与reduce函数的形式相同(它是Reducer的一个实现),不同之处是它的输出类型是中间的键/值对类型(K2和V2),这些中间值可以输入reduce函数:
map: (K1, V1) list(k2, v2)
combine: (K2,list(v2)) list(k2, v2)
reduce: (K2,list(v2)) list(k3, v3)
combine与reduce函数通常是一样的,在这种情况下,K3与K2类型相同.V3与V2类型相同。
partition函数将中间的键/值对(K2和V2)进行处理,并且返回一个分区索引(partition index)。实际上,分区单独由键决定(值是被忽略的)。
panition: (K2, V2) integer
1.输入分片与记录
public interface InputSplit extends Writable{
long getLength() throws IOException;
String[] getLocations() throws IOException;
}
FileInputFormat.add(job, new Path(a.txt))
Public static void addInputPath(JobConf, Path path)
Public static void addInputPaths(JobConf,String commaSeparatedPaths)
Public static void setInputPaths(JobConf, Path… inputPaths)
Public static void setInputPaths(JobConf, String commaSeparatedPaths)
2、文本输入
TextlnputFormat
TextInputFormat 是默认的 InputFormat。每条记录是一行输入。键是 LongWritable 类型,存储该行在整个文件中的字节偏移量。值是这行的内容,不包括任何行终止符(换行符和回车符),它是 Text 类型的。所以,包含如下文本的文件:
On the top of the Crumpetty Tree
The Quangle Wangle sat,
But his face you could not see,
On account of his Beaver Hat.
被切分为每个分片 4 条记录:
(0,On the top of the Crumpetty Tree)
(33,The Quangle Wangle sat,)
(57,But his face you could not see,)
(89,On account of his Beaver Hat.)
输入分片和HDFS块之间可能不能很好的匹配,出现跨块的情况
KeyValueTextlnputFormat
NLineInputFormat
NLineInputFormat:与TextInputFormat一样,键是文件中行的字节偏移量,值是行本身。主要是希望mapper收到固定行数的输入。
以下是一个示例,其中一表示一个(水平方向的)制表符:
linel->On the top of the Crumpetty Tree
line2->The Quangle Wangle sat,
line3->But his face you could not see.
line4->On account of his Beaver Hat.
与 TextInputFormat 类似,输入是一个包含 4 条记录的分片,不过此时的键是每行排在Tab 之前的 Text 序列:
(linel, On the top of the Crumpetty Tree)
(line2, The Quangle Wangle sat,)
(line3, But his face you could not see,)
(line4, On account of his Beaver Hat.)
3、多种输入
public class CityMapJoinDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//判断输入路径
if(args.length!=3 || args==null){
System.err.println("Please Input Full Path!");
System.exit(1);
}
Job job = Job.getInstance(new Configuration(), CityReducer.class.getSimpleName());
job.setJarByClass(CityReducer.class);
//通过MultipleInputs多输入的方式添加多个Map的处理类
MultipleInputs.addInputPath(job, new Path(args[0]), TextInputFormat.class, AllCityMapper.class);
MultipleInputs.addInputPath(job ,new Path(args[1]), org.apache.hadoop.mapreduce.lib.input.TextInputFormat.class, SomeCityMapper.class);
FileOutputFormat.setOutputPath(job, new Path(args[2]));
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
//设置Reducer阶段处理的类
job.setReducerClass(CityReducer.class);
System.exit(job.waitForCompletion(true)?0:1);
}
/** * 处理所有城市的map * <济南,a_济南> * <青岛,a_青岛> * <德州,a_德州> */
static class AllCityMapper extends Mapper<LongWritable, Text, Text, Text>{
public static final String LABEL = "a_";
public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String cityName = value.toString();
context.write(new Text(cityName), new Text(LABEL+cityName));
}
}
/** * 处理只有飞机场的城市 * <济南,s_济南 济南飞机场> * <青岛,s_青岛 青岛飞机场> */
static class SomeCityMapper extends Mapper<LongWritable, Text, Text, Text>{
public static final String LABEL = "s_";
public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] lines = value.toString().split("\t");
String cityName = lines[0];
context.write(new Text(cityName), new Text(LABEL+value.toString()));
}
}
/** * 经过shuffle之后变成: * <济南,{a_济南,s_济南 济南飞机场}> * <德州,{a_德州}> * 青岛,{a_青岛,s_青岛 青岛机场} */
static class CityReducer extends Reducer<Text, Text, Text, Text>{
public void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
//城市的名字
String cityName = null;
//存放符合条件过滤出来的城市
List<String> list = new ArrayList<String>();
for(Text value : values){
//如果列表中有包含有s_开头的数据吗,则表明该数据是已经有飞机场的城市
if(value.toString().startsWith(SomeCityMapper.LABEL)){
int index = value.toString().indexOf("_");
cityName = value.toString().substring(index+1, index+3);
} else if(value.toString().startsWith(AllCityMapper.LABEL)){
list.add(value.toString().substring(2));
}
}
//如果城市名为空并且list列表中有值,则列表中的值就是符合条件的数据
if(cityName==null && list.size()>0){
for(String str : list){
context.write(new Text(str), new Text(""));
}
}
}
}
}
1、掌握MapReduce的编程,编写Mapper类,Reducer类和main类。
2、知道作业是如何在集群上运作的,打包、启动、查看结果。
3、知道如何从web UI查看Job状态。
4、掌握MapReduce的出入输出格式。
5、理解MapReduce中键值对的概念