一、MapReduce概述
MapReduce 是 Hadoop 的核心组成, 是专用于进行数据计算的,是一种分布式计算模型。由Google提出,主要用于搜索领域,解决海量数据的计算问题.
MapReduce由两个阶段组成:Map和Reduce,用户仅仅须要实现map()和reduce()两个函数。就可以实现分布式计算。非常easy。这两个函数的形參是key、value对,表示函数的输入输出信息。
map、reduce键值对格式
二、MapReduce体系结构及工作流程
1、JobTracker
负责接收用户提交的作业。负责启动、跟踪任务运行。
JobSubmissionProtocol是JobClient与JobTracker通信的接口。
InterTrackerProtocol是TaskTracker与JobTracker通信的接口。
2、TaskTracker
负责运行任务。
3、JobClient
是用户作业与JobTracker交互的主要接口。
负责提交作业的,负责启动、跟踪任务运行、訪问任务状态和日志等。
4、工作流程图
运行步骤:
1.map任务处理
1.1读取输入文件内容,解析成key、value对。对输入文件的每一行。解析成key、value对。每一个键值对调用一次map函数。
1.2 写自己的逻辑,对输入的key、value处理,转换成新的key、value输出。
1.3 对输出的key、value进行分区。
1.4 对不同分区的数据,依照key进行排序、分组。同样key的value放到一个集合中。
1.5 (可选)分组后的数据进行归约。
2.reduce任务处理
2.1 对多个map任务的输出,依照不同的分区,通过网络copy到不同的reduce节点。
2.2 对多个map任务的输出进行合并、排序。
写reduce函数自己的逻辑,对输入的key、value处理,转换成新的key、value输出。
2.3 把reduce的输出保存到文件里。
三、统计单词源代码及凝视
package mapreduce;
import java.io.IOException;
import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
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.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class WordCountApp {
static final String INPUT_PATH = "hdfs://liguodong:9000/hello";
static final String OUT_PATH = "hdfs://liguodong:9000/out";
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
final FileSystem fileSystem = FileSystem.get(new URI(INPUT_PATH),conf);
if(fileSystem.exists(new Path(OUT_PATH)))
{
fileSystem.delete(new Path(OUT_PATH),true);
}
final Job job = new Job(conf, WordCountApp.class.getSimpleName());
//1.1 输入文件夹在哪里
FileInputFormat.setInputPaths(job, INPUT_PATH);
//指定对输入数据进行格式化处理的类
//job.setInputFormatClass(TextInputFormat.class);
//1.2 指定自己定义的Mapper类
job.setMapperClass(MyMapper.class);
//指定map的输出的<k,v>类型,假设<k3,v3>的类型与<k2,v2>的类型一致,那么能够省略。
//job.setMapOutputKeyClass(Text.class);
//job.setMapOutputValueClass(LongWritable.class);
//1.3分区
//job.setPartitionerClass(HashPartitioner.class);
//job.setNumReduceTasks(1);
//1.4 TODO 排序、分组
//1.5 TODO (可选)归约
//2.2 指定自己定义的Reducer类
job.setReducerClass(MyReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(LongWritable.class);
//2.3 指定输出的路径
FileOutputFormat.setOutputPath(job, new Path(OUT_PATH));
//指定输出的格式化类
//job.setOutputFormatClass(TextOutputFormat.class);
//把作业交给JobTracker运行
job.waitForCompletion(true);
}
/**
* KEYIN 即K1 表示每一行的起始位置(偏移量offset)
* VALUEIN 即v1 表示每一行的文本内容
* KEYOUT 即k2 表示每一行中的每一个单词
* VALUEOUT 即v2 表示每一行中的每一个单词的出现次数,固定值1
* @author liguodong
* Java Hadoop
* Long LongWritable
* String Text
*/
static class MyMapper extends Mapper<LongWritable,Text,Text,LongWritable>{
@Override
protected void map(LongWritable key, Text value,
Mapper<LongWritable, Text, Text, LongWritable>.Context context)
throws IOException, InterruptedException {
final String[] splited = value.toString().split(" ");
for (String word : splited) {
context.write(new Text(word), new LongWritable(1));
}
}
}
/**
* KEYIN 即k2 表示每一行中的每一个单词
* VALUEIN 即v2 表示每一行中每一个单词出现次数,固定值1
* KEYOUT 即k3 表示整个文件里的不同单词
* VALUEOUT 即v3 表示整个文件里的不同单词的出现总次数
* @author liguodong
*/
static class MyReducer extends Reducer<Text, LongWritable,Text, LongWritable>{
@Override
protected void reduce(Text k2, Iterable<LongWritable> v2s,
Reducer<Text, LongWritable, Text, LongWritable>.Context context)
throws IOException, InterruptedException {
long sum = 0L;
for (LongWritable v2 : v2s) {
sum += v2.get();
}
context.write(k2, new LongWritable(sum));
}
}
}
四、最小的MapReduce驱动
Configuration configuration = new Configuration();
Job job = new Job(configuration, "HelloWorld");
job.setInputFormat(TextInputFormat.class);
job.setMapperClass(IdentityMapper.class);
job.setMapOutputKeyClass(LongWritable.class);
job.setMapOutputValueClass(Text.class);
job.setPartitionerClass(HashPartitioner.class);
job.setNumReduceTasks(1);
job.setReducerClass(IdentityReducer.class);
job.setOutputKeyClass(LongWritable.class);
job.setOutputValueClass(Text.class);
job.setOutputFormat(TextOutputFormat.class);
job.waitForCompletion(true);
五、MapReduce驱动默认的设置
六、Hadoop序列化与基本类型
1、序列化概念
序列化(Serialization)是指把结构化对象转化为字节流。
反序列化(Deserialization)是序列化的逆过程。即把字节流转回结构化对象。
Java序列化(java.io.Serializable)
2、Hadoop序列化格式特点
紧凑:高效使用存储空间。
高速:读写数据的额外开销小
可扩展:可透明地读取老格式的数据
互操作:支持多语言的交互
Hadoop的序列化格式:Writable
3、Hadoop序列化的作用
序列化在分布式环境的两大作用:进程间通信。永久存储。
Hadoop节点间通信。
4、基本数据类型
Hadoop的数据类型要求必须实现Writable接口。
Writable接口是依据 DataInput 和 DataOutput 实现的简单、有效的序列化对象.
MR的随意Key和Value必须实现Writable接口。
public interface Writable {
/**
* Serialize the fields of this object to <code>out</code>.
*
* @param out <code>DataOuput</code> to serialize this object into.
* @throws IOException
*/
void write(DataOutput out) throws IOException;
/**
* Deserialize the fields of this object from <code>in</code>.
*
* <p>For efficiency, implementations should attempt to re-use storage in the
* existing object where possible.</p>
*
* @param in <code>DataInput</code> to deseriablize this object from.
* @throws IOException
*/
void readFields(DataInput in) throws IOException;
}
MR的随意key必须实现WritableComparable接口。
/**
* A {@link Writable} which is also {@link Comparable}.
*
* <p><code>WritableComparable</code>s can be compared to each other, typically
* via <code>Comparator</code>s. Any type which is to be used as a
* <code>key</code> in the Hadoop Map-Reduce framework should implement this
* interface.</p>
**/
public interface WritableComparable<T> extends Writable, Comparable<T> {
}
实现Writable接口?
write 是把每一个对象序列化到输出流
readFields 是把输入流字节反序列化
实现WritableComparable?
Java值对象的比較:一般须要重写toString(),hashCode(),equals()方法。
Hadoop与Java常见基本类型的对比
Long—LongWritable
Integer—IntWritable
Boolean—BooleanWritable
String—Text
Text一般觉得它等价于java.lang.String的Writable。
针对UTF-8序列。
例:
Text test = new Text(“test”);
IntWritable one = new IntWritable(1);
java类型怎样转化为hadoop基本类型?
调用hadoop类型的构造方法,或者调用set()方法。
new LongWritable(123L);
hadoop基本类型怎样转化为java类型?
对于Text,须要调用toString()方法,其它类型调用get()方法。
七、基于文件存储的数据结构
SequenceFile—无序存储
MapFile—会对key建立索引文件,value按key顺序存储
基于MapFile的结构有:
ArrayFile—与我们使用的数组一样,key值为序列化的数字
SetFile—仅仅有key,value为不可变的数据
BloomMapFile—在 MapFile 的基础上添加了一个/bloom 文件,包括的是二进制的过滤表。在每一次写操作完毕时,会更新这个过滤表。
八、自己定义数据类型源代码
package mapreduce;
import java.io.DataInput;
import java.io.DataOutput;
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.io.Writable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.mapreduce.lib.partition.HashPartitioner;
public class KpiApp {
static final String INPUT_PATH = "hdfs://liguodong:9000/wlan";
static final String OUT_PATH = "hdfs://liguodong:9000/out";
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
final Job job = new Job(new Configuration(),KpiApp.class.getSimpleName());
//1.1 指定输入文件路径
FileInputFormat.setInputPaths(job, INPUT_PATH);
//指定那个类用来格式化输入文件
job.setInputFormatClass(TextInputFormat.class);
//1.2 指定自己定义的Mapper类
job.setMapperClass(MyMapper.class);
//指定输出<k2,v2>的类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(KpiWritable.class);
//1.3 指定分区类
job.setPartitionerClass(HashPartitioner.class);
job.setNumReduceTasks(1);
//1.4 TODO 排序、分组
//1.5 TODO (可选)归约
//2.2 指定自己定义的Reducer类
job.setReducerClass(MyReducer.class);
//指定输入<k3,v3>的值
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(KpiWritable.class);
//2.3 指定输出到哪里
FileOutputFormat.setOutputPath(job, new Path(OUT_PATH));
//设定输出文件的格式化类
job.setOutputFormatClass(TextOutputFormat.class);
//把任务及交给JobTrack运行
job.waitForCompletion(true);
}
static class MyMapper extends Mapper<LongWritable, Text, Text, KpiWritable>{
@Override
protected void map(LongWritable key, Text value,
Mapper<LongWritable, Text, Text, KpiWritable>.Context context)
throws IOException, InterruptedException {
final String[] splited = value.toString().split("\t");
final String msisdn = splited[1];
final Text k2 = new Text(msisdn);
final KpiWritable v2 = new KpiWritable(splited[6],splited[7],splited[8],splited[9]);
context.write(k2, v2);
}
}
static class MyReducer extends Reducer<Text, KpiWritable, Text, KpiWritable>{
/**
* @param k2 表示整个文件里不同的手机号码
* @param v2表示该手机号在不同一时候段的流量的集合
* @param
*/
@Override
protected void reduce(Text k2, Iterable<KpiWritable> v2s,
Reducer<Text, KpiWritable, Text, KpiWritable>.Context context)
throws IOException, InterruptedException {
long upPackNum = 0L;
long downPackNum = 0L;
long upPayLoad = 0L;
long downPayLoad = 0L;
for (KpiWritable kpiWritable : v2s) {
upPackNum += kpiWritable.upPackNum;
downPackNum += kpiWritable.downPackNum;
upPayLoad += kpiWritable.upPayLoad;
downPayLoad += kpiWritable.downPayLoad;
}
final KpiWritable v3 = new KpiWritable(upPackNum+"",downPackNum+"",upPayLoad+"",downPayLoad+"");
context.write(k2, v3);
}
}
}
class KpiWritable implements Writable{
long upPackNum;
long downPackNum;
long upPayLoad;
long downPayLoad;
public KpiWritable(){
}
public KpiWritable(String upPackNum, String downPackNum, String upPayLoad,
String downPayLoad) {
super();
this.upPackNum = Long.parseLong(upPackNum);
this.downPackNum = Long.parseLong(downPackNum);
this.upPayLoad = Long.parseLong(upPayLoad);
this.downPayLoad = Long.parseLong(downPayLoad);
}
@Override
public void write(DataOutput out) throws IOException {
out.writeLong(upPackNum);
out.writeLong(downPackNum);
out.writeLong(upPayLoad);
out.writeLong(downPayLoad);
}
@Override
public void readFields(DataInput in) throws IOException {
this.upPackNum = in.readLong();
this.downPackNum = in.readLong();
this.upPayLoad = in.readLong();
this.downPayLoad = in.readLong();
}
@Override
public String toString(){
return upPackNum+"\t"+downPackNum+"\t"+upPayLoad+"\t"+downPayLoad;
}
}
九、MapReduce输入输出处理类
1、输入处理类
FileInputFormat:
FileInputFormat是全部以文件作为数据源的InputFormat实现的基类,FileInputFormat保存作为job输入的全部文件。并实现了对输入文件计算splits的方法。至于获得记录的方法是有不同的子类——TextInputFormat进行实现的。
InputFormat 负责处理MR的输入部分.
有三个作用:
验证作业的输入是否规范.
把输入文件切分成InputSplit.
提供RecordReader 的实现类,把InputSplit读到Mapper中进行处理.
public abstract class InputFormat<K, V> {
/**
* Logically split the set of input files for the job.
*
* <p>Each {@link InputSplit} is then assigned to an individual {@link Mapper}
* for processing.</p>
*
* <p><i>Note</i>: The split is a <i>logical</i> split of the inputs and the
* input files are not physically split into chunks. For e.g. a split could
* be <i><input-file-path, start, offset></i> tuple. The InputFormat
* also creates the {@link RecordReader} to read the {@link InputSplit}.
*
* @param context job configuration.
* @return an array of {@link InputSplit}s for the job.
*/
public abstract
List<InputSplit> getSplits(JobContext context
) throws IOException, InterruptedException;
/**
* Create a record reader for a given split. The framework will call
* {@link RecordReader#initialize(InputSplit, TaskAttemptContext)} before
* the split is used.
* @param split the split to be read
* @param context the information about the task
* @return a new record reader
* @throws IOException
* @throws InterruptedException
*/
public abstract
RecordReader<K,V> createRecordReader(InputSplit split,
TaskAttemptContext context
) throws IOException,
InterruptedException;
}
InputSplit
在运行mapreduce之前。原始数据被切割成若干split。每一个split作为一个map任务的输入,在map运行过程中split会被分解成一个个记录(key-value对),map会依次处理每一个记录。
FileInputFormat仅仅划分比HDFS block大的文件,所以FileInputFormat划分的结果是这个文件或者是这个文件里的一部分。
假设一个文件的大小比block小,将不会被划分,这也是Hadoop处理大文件的效率要比处理非常多小文件的效率高的原因。
当Hadoop处理非常多小文件(文件大小小于hdfs block大小)的时候。因为FileInputFormat不会对小文件进行划分,所以每一个小文件都会被当做一个split并分配一个map任务,导致效率底下。
比如:一个1G的文件,会被划分成16个64MB的split,并分配16个map任务处理。而10000个100kb的文件会被10000个map任务处理。
TextInputFormat
TextInputformat是默认的处理类,处理普通文本文件。
文件里每一行作为一个记录,他将每一行在文件里的起始偏移量作为key,每一行的内容作为value。
默认以\n或回车键作为一行记录。
TextInputFormat继承了FileInputFormat。
其它输入类
◆ CombineFileInputFormat
相对于大量的小文件来说,hadoop更合适处理少量的大文件。
CombineFileInputFormat能够缓解这个问题,它是针对小文件而设计的。
◆ KeyValueTextInputFormat
当输入数据的每一行是两列。并用tab分离的形式的时候,KeyValueTextInputformat处理这样的格式的文件非常适合。
◆ NLineInputformat
NLineInputformat能够控制在每一个split中数据的行数。
◆ SequenceFileInputformat
当输入文件格式是sequencefile的时候,要使用SequenceFileInputformat作为输入。
InputFormat类的层次结构
2、输出处理类
TextOutputformat
默认的输出格式,key和value中间值用tab隔开的。
SequenceFileOutputformat
将key和value以sequencefile格式输出。
SequenceFileAsBinaryOutputFormat
将key和value以原始二进制的格式输出。
MapFileOutputFormat
将key和value写入MapFile中。
因为MapFile中的key是有序的,所以写入的时候必须保证记录是按key值顺序写入的。
MultipleOutputFormat
默认情况下一个reducer会产生一个输出。可是有些时候我们想一个reducer产生多个输出,MultipleOutputFormat和MultipleOutputs能够实现这个功能。
OutputFormat类的层次结构
3、自己定义输入类格式
1)继承FileInputFormat基类。
2)重写里面的getSplits(JobContext context)方法。
3)重写createRecordReader(InputSplit split,TaskAttemptContext context)方法。