**first Codec
public class Friend {
public static void main(String[] args){
System.out.println("BigData加QQ群:947967114");
}
}**
深入了解hadoop系统,我们需要深入了解两个流:一个控制流(程序的流程),另一个是数据流(数据从源头到终点的流动和变化)。有些系统数据流是在控制流的作用下形成的。另一些则是因为数据的驱动才运转起来。
对用户而言,Hadoop最主要的作用和特征就是MapReduce,所以对其MR框架中数据流的考察,即对数据从数据源到最终产物的输出之间各阶段的流动和改变的考察就尤其重要。
MR的框架就是Map和Reduce两个阶段,但事实上远不止于此,这两个大阶段都进一步划分成好几个更小的阶段。以前我们提过的sort阶段,Mapper的输出端提供了局部的排序成分,Reducer端输出端的拉取(fetch)和合并(merge),以至汇合(combine)阶段又带有排序的成分。由这些因素产生的全局排序都是框架提供的,我们并不能感受到它的存在。
Map和Reduce之间的排序只能在全部数据到位之后才能进行。例如:Mapper和Reducer之间起始不只是一条河流,而是一个水库。如果没有全部数据到位那么就会造成部分结果不准确。数据流和工作流的差别在于数据流动的粒度。数据流是小粒度的、均匀的数据流动,而工作流是最大粒度的、成批的数据流动。所以工作流天生就是批量处理。
hadoop的代码中为作业的操作流程定义了几个阶段:
STARTING—>MAP---->SHUFFLE—>REDUCE---->CLEANUP
这是控制流意义上的阶段划分,前一个阶段不结束,后一个阶段就不会开始。尽管如此我们还是可以大致把数据在MR框架中的流程称为数据流,从MAP---->SHUFFLE—>REDUCE---->CLEANUP的整个流程上看也正好和数据的流动相符。在局部环境中,还真有小粒度的、均匀的数据流动。例如:map阶段内部,其输入/处理/输出这几个阶段就是数据流。
那么我们探究一下MR的数据流动。里面经历了什么样的阶段。
我们从Mapper和Reducer控制流入手深入的考察。
package org.apache.hadoop.mapreduce;
import java.io.IOException;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.io.RawComparator;
import org.apache.hadoop.io.compress.CompressionCodec;
import org.apache.hadoop.mapreduce.task.MapContextImpl;
@InterfaceAudience.Public
@InterfaceStability.Stable
public class Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
public abstract class Context
implements MapContext<KEYIN,VALUEIN,KEYOUT,VALUEOUT> {
}
protected void setup(Context context
) throws IOException, InterruptedException {
// NOTHING
}
@SuppressWarnings(“unchecked”)
protected void map(KEYIN key, VALUEIN value,
Context context) throws IOException, InterruptedException {
context.write((KEYOUT) key, (VALUEOUT) value);
}
protected void cleanup(Context context
) throws IOException, InterruptedException {
// NOTHING
}
public void run(Context context) throws IOException, InterruptedException {
setup(context);
try {
while (context.nextKeyValue()) {
map(context.getCurrentKey(), context.getCurrentValue(), context);
}
} finally {
cleanup(context);
}
}
}
这里我们要看一下Mapper. run,这里的setup和cleanup都是空的。里面没有实现任何东西,用户可以自己编写,来覆盖原有的setup和cleanup。Context是个抽象类,这里没有具体定义,只是实现了MapContext,即必须提供MapContext规定的函数。这里面就包含了nextKeyValue、getCurrentKey、gerCurrentValue还有write方法。具体的Context是由MR框架传给Mapper的。Mapper只引用这几个方法。事实上Context起着非常关键的作用,Mapper的核心就是while循环,能够继续循环的条件就是context.nextKeyValue返回true,也就是说还有数据。只要有数据,context…getCurrentKey和context.getCurrentValue就能获取相应的K和V。这里map函数没有进行有效的处理,只是实现了K和V的类型转换,然后直接通过Context.write写出去了。这个map函数是可以被覆盖的。我们要想一个问题,下一个KV是来自哪里?怎么读进来的?写出去的时候又写到了哪里?数据怎么到达reduce?
我们再来看reducer
package org.apache.hadoop.mapreduce;
import java.io.IOException;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.mapreduce.task.annotation.Checkpointable;
import java.util.Iterator;
@Checkpointable
@InterfaceAudience.Public
@InterfaceStability.Stable
public class Reducer<KEYIN,VALUEIN,KEYOUT,VALUEOUT> {
public abstract class Context
implements ReduceContext<KEYIN,VALUEIN,KEYOUT,VALUEOUT> {
}
protected void setup(Context context
) throws IOException, InterruptedException {
// NOTHING
}
@SuppressWarnings(“unchecked”)
protected void reduce(KEYIN key, Iterable values, Context context
) throws IOException, InterruptedException {
for(VALUEIN value: values) {
context.write((KEYOUT) key, (VALUEOUT) value);
}
}
protected void cleanup(Context context
) throws IOException, InterruptedException {
// NOTHING
}
public void run(Context context) throws IOException, InterruptedException {
setup(context);
try {
while (context.nextKey()) {
reduce(context.getCurrentKey(), context.getValues(), context);
// If a back up store is used, reset it
Iterator iter = context.getValues().iterator();
if(iter instanceof ReduceContext.ValueIterator) {
((ReduceContext.ValueIterator)iter).resetBackupStore();
}
}
} finally {
cleanup(context);
}
}
}
这里的setup和cleanup以及Context和Mapper中的都很相似,特别是Context。但是注意MR框架传给Reducer的和传给Mapper的Context是不一样的。这个Context实现的是ReduceContext,它提供的是nextKey、getValues、write。当然还有write,但是他们的方法和目标是不一样的。
根据以前的知识我们知道Reducer输入是[K,V,V,V,V,V,V,V,V,V,V] 这样的形式,而Mapper的输出是[K,V].这中间肯定存在着一系列的处理过程。把K相同的V处理到一起去了。由于Mapper和Reducer通常不在同一个节点上,所以这个步骤应该在Mapper完成更加合理,从而减少带宽压力。MapReduce的框架是多个Mapper对应一个Reducer,那么就会涉及到在多个map端已经合并好的数据到达reduce的时候要再次进行合并的问题。在这个过程中还有排序的需求,多个Mapper的数据都是排序,所以Mapper和Reducer之间自然就存在一些列的MergeSort的排序机制。
Mapper的输入:
从数据源开始,MR数据一般来自于HDFS。对于具体的数据源,MR框架必须能从中读取数据,形成KV对,将其作为nextKeyValue调用的输出,由Mapper用作调用map的参数,相当于作为map的输入。
这就好iInputFormat的作用,有什么样的数据源,什么样的数据格式就的采用什么样的InputFormat。我们看到在具体作业提交之前,首先要调用job.setInputFormatClass.hadoop定义了一个抽象的InputFormat,规定每一种输入格式都必须自行提供getSplits和createRecordReader这两个方法。
package org.apache.hadoop.mapreduce;
import java.io.IOException;
import java.util.List;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
@InterfaceAudience.Public
@InterfaceStability.Stable
public abstract class InputFormat<K, V> {
public abstract
List getSplits(JobContext context
) throws IOException, InterruptedException;
public abstract
RecordReader<K,V> createRecordReader(InputSplit split,
TaskAttemptContext context
) throws IOException,
InterruptedException;
}
其中getSplits返回的是List,即一个Mapper输入“分片”列表。这是因为Mapper一般都有多个,但是输入源(假设为文件)却只有一个,所以需要把输入数据的集合(文件),分成很多片,一个片对应一个Mapper,如果给定了Mapper的数量那么片的数量也定了。如果不给定Mapper数量怎么分片比较合理就有讲究了。例如hadoop采用的方式是一个块对应一个分片,对应的块节点上开启一个Mapper。减少了跨节点分片造成的数据拉取。从而减轻带宽压力。这里已经在前面的源码中介绍过了。至于createRecordReader,顾名思义是创建ReacordReader的,,每种InputFormat都有固定的RecordReader,负责从数据源指定的Split中读取记录。形成KV对。供指定的Mapper使用。Mapper中对于指定的context.nextKeyValue,最后都会落实到RecordReader头上。
import java.io.Closeable;
import java.io.IOException;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
@InterfaceAudience.Public
@InterfaceStability.Stable
public abstract class RecordReader<KEYIN, VALUEIN> implements Closeable {
public abstract void initialize(InputSplit split,
TaskAttemptContext context
) throws IOException, InterruptedException;
public