Hadoop的mapreduce处理阶段
一、了解 inputSplit
代表传给一个单独mapper任务的数据。inputsplit是一个抽象类,定义了如下方法:
getLength:得到每个inputsplit有多少个bytes 。
getLocations:获取inputsplit的主机名。
public abstract class InputSplit { public abstract long getLength() throws IOException, InterruptedException; public abstract String[] getLocations() throws IOException, InterruptedException; }
FileInputFormat计算InputSplit的getSplits方法的流程
public List<InputSplit> getSplits(JobContext job), 这个由客户端调用来获得当前Job的所有分片(split),然后发送给JobTracker(新API中应该是ResourceManager),而JobTracker根据这些分片的存储位置来给TaskTracker分配map任务去处理这些分片。这个方法用到了后边的listStatus,然后根据得到的这些文件信息,从FileSystem那里去拉取这些组成这些文件的块的信息(BlockLocation),使用的是getFileBlockLocation(file,start,len),这个方法是与使用的文件系统实现相关的(FileSystem,LocalFileSystem,DistributedFileSystem)
/** * Generate the list of files and make them into FileSplits. */ public List<InputSplit> getSplits(JobContext job ) throws IOException { long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job)); long maxSize = getMaxSplitSize(job); // generate splits List<InputSplit> splits = new ArrayList<InputSplit>(); List<FileStatus>files = listStatus(job); //2 for (FileStatus file: files) { Path path = file.getPath(); FileSystem fs = path.getFileSystem(job.getConfiguration()); long length = file.getLen(); /*Return an array containing hostnames, offset and size of portions of the given file. For a nonexistent file or regions, null will be returned. This call is most helpful with DFS, where it returns hostnames of machines that contain the given file. The FileSystem will simply return an elt containing 'localhost'.*/ BlockLocation[] blkLocations = fs.getFileBlockLocations(file, 0, length); if ((length != 0) && isSplitable(job, path)) { long blockSize = file.getBlockSize(); long splitSize = computeSplitSize(blockSize, minSize, maxSize); //3 long bytesRemaining = length; while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) { int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining); //4 splits.add(new FileSplit(path, length-bytesRemaining, splitSize, blkLocations[blkIndex].getHosts())); bytesRemaining -= splitSize; } if (bytesRemaining != 0) { splits.add(new FileSplit(path, length-bytesRemaining, bytesRemaining, blkLocations[blkLocations.length-1].getHosts())); } } else if (length != 0) { //这里使用的是FileSplit,在RecordReader实现中拿到Split的时候就可以向下转型,从而拿到一些分片的信息 splits.add(new FileSplit(path, 0, length, blkLocations[0].getHosts())); //Create empty hosts array for zero length files splits.add(new FileSplit(path, 0, length, new String[0])); } } // Save the number of input files in the job-conf job.getConfiguration().setLong(NUM_INPUT_FILES, files.size()); LOG.debug("Total # of splits: " + splits.size()); return splits; }
二、处理阶段map-> [partitioner,combiner,shuffle]->reduce
1、第一部分:Map端Shuffle
对于输入文件,会进行分片,对于一个split,有一个map任务进行处理,每个Map在内存中都有一个缓存区,map的输出结果会先放到这个缓冲区中,在缓冲区中,会进行预排序(即sort和comibner),以提高效率。
缓冲区默认大小是100MB(可以通过io.sort.mb属性更改大小),当缓冲区中的数据达到特定的阈值(io.sort.mb * io.sort.spill.percent,其中io.sort.spill.percent默认是0.80)时,系统会启动一个后台线程把缓冲区的内容spill(溢写)到磁盘。溢出到磁盘的一个临时文件中,即80%的内容成为一个临时文件。当这80%的内容溢出时,map会继续向剩余的20%缓冲区中输出。
spill线程在把缓冲区中的数据写到磁盘前,会进行一个二次快速排序,首先根据数据所属的Partition排序,然后每个Partition中再按Key排序。输出包括一个索引文件和数据文件。如果设定了Combiner,将在排序输出的基础上进行。
Comibner就是一个Mini Reducer,在执行Map任务的节点本身运行,对Map的输出做一次简单Reduce,使得Map'de输出更紧凑,更少的数据会被写入磁盘和传送到Reduce端。
一个Map任务会产生多个spill文件,在Map任务完成前,所有的spill文件将会归并排序为一个索引文件和数据文件。当spill文件归并完成后,Map将删除所有的临时文件,并告知TaskTracker任务已完成。
对写入到磁盘的数据可以选择采取压缩的方式,如果需要压缩,则需要设置mapred.compress.map.output为true。
还有一个Partition的概念,一个临时文件是进行了分区的,并且分区的数量由reduce的数量决定,不同的分区传给不同的reduce。
2、第二部分:Reduce端Shuffle
Reduce端通过HTTP获取Map端的数据,只要有一个map任务完成,Reduce任务就开始复制它的输出,这称为copy阶段。
JobTracker知道Map输出与TaskTracker的映射关系,Reduce端有一个线程间歇地向JobTracker询问Map输出的地址,直到把所有的数据都获取到。
如果map输出比较小,他们被复制到Reduce的内存中,如果缓冲区空间不足,会被复制到磁盘上。复制的数据放在磁盘上,后台线程会进行归并为更大的排序文件,对于压缩文件,系统会自动解压到内存方便归并。
当所有的Map输出被复制后,Reduce任务进入排序阶段(确切的说是归并阶段),这个过程会重复多次。Merge有三种形式:内存到内存,内存到磁盘,磁盘到磁盘。
内存到内存默认不启用;内存到磁盘的方式也会产生溢写,如果设置了Combiner,此时也会启用,在磁盘上生成多个溢写文件;磁盘到磁盘会生成一个最终的文件作为Reduce的输入。
三、排序 [first,second]---key
package cn.mk.hadoop.mapreduce; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import org.apache.hadoop.io.WritableComparable; public class A implements WritableComparable<A> { private long first; public long getFirst() { return first; } public void setFirst(long first) { this.first = first; } public long getLast() { return last; } public void setLast(long last) { this.last = last; } private long last; public A() { super(); } public A(long f, long l) { first=f; last=l; } @Override public void readFields(DataInput in) throws IOException { first=in.readLong(); last=in.readLong(); } @Override public void write(DataOutput out) throws IOException { out.writeLong(first); out.writeLong(last); } @Override public int compareTo(A o) { int a=(int)(first-o.first); if(a!=0) return a; return (int)(last-o.last); } }
package cn.mk.hadoop.mapreduce; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.io.WritableComparable; 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 MySort { private static class MySortMapper extends Mapper<Object,Text,A,NullWritable>{ @Override protected void map(Object key, Text value, Context context) throws IOException, InterruptedException { String[] data=value.toString().split(","); A a=new A(Long.parseLong(data[0]),Long.parseLong(data[1])); context.write(a, NullWritable.get()); } } private static class MySortReducer extends Reducer<A,NullWritable,Text,Text>{ @Override protected void reduce(A a, Iterable<NullWritable> v, Reducer<A, NullWritable, Text, Text>.Context context) throws IOException, InterruptedException { context.write(new Text(""+a.getFirst()),new Text(""+a.getLast())); } } static String inputPath ="hdfs://master:9000/input/sort.txt"; static String outputPath ="hdfs://master:9000/output/sort"; public static void main(String[] args) throws IOException, URISyntaxException, ClassNotFoundException, InterruptedException{ Configuration conf =new Configuration(); FileSystem fs=FileSystem.get(new URI(outputPath),conf); if(fs.exists(new Path(outputPath))) fs.delete(new Path(outputPath)); Job job =new Job(conf,"asb1"); job.setJarByClass(MySort.class); job.setMapperClass(MySortMapper.class); job.setReducerClass(MySortReducer.class); job.setMapOutputKeyClass(A.class); job.setMapOutputValueClass(NullWritable.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class); FileInputFormat.addInputPath(job,new Path(inputPath)); FileOutputFormat.setOutputPath(job, new Path(outputPath)); job.waitForCompletion(true); } }

浙公网安备 33010602011771号