MapReduce源码分析之InputSplit分析
前言
MapReduce的源码分析是基于Hadoop1.2.1基础上进行的代码分析。
什么是InputSplit
InputSplit是指分片,在MapReduce当中作业中,作为map task最小输入单位。分片是基于文件基础上出来的而来的概念,通俗的理解一个文件可以切分为多少个片段,每个片段包括了<文件名,开始位置,长度,位于哪些主机>等信息。在MapTask拿到这些分片后,会知道从哪开始读取数据。
Job提交时如何获取到InputSplit
以org.apache.hadoop.mapred包中的FileInputFormat为例(因为该类作为其他文件类型的基类),内部实现了如何获取分片,通过分析代码,以便知晓文件是如何被切片的。
- public InputSplit[] getSplits(JobConf job, int numSplits)
- throwsIOException {
-
- FileStatus[] files = listStatus(job);
-
-
- job.setLong(NUM_INPUT_FILES, files.length);
-
- longtotalSize = 0;
- for(FileStatus file: files) {
- if(file.isDir()) {
- throw new IOException("Not a file: "+ file.getPath());
- }
- totalSize += file.getLen();
- }
-
-
- longgoalSize = totalSize / (numSplits == 0 ? 1 : numSplits);
-
- longminSize = Math.max(job.getLong("mapred.min.split.size", 1),
- minSplitSize);
-
-
- ArrayList<FileSplit> splits = new ArrayList<FileSplit>(numSplits);
- NetworkTopology clusterMap = new NetworkTopology();
-
-
- for(FileStatus file: files) {
- Path path = file.getPath();
- FileSystem fs = path.getFileSystem(job);
- longlength = file.getLen();
-
- BlockLocation[] blkLocations = fs.getFileBlockLocations(file, 0,length);
-
- if((length != 0) && isSplitable(fs, path)) {
- long blockSize = file.getBlockSize();
-
- long splitSize = computeSplitSize(goalSize,minSize, blockSize);
-
- long bytesRemaining = length;
-
- while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
-
- String[] splitHosts =getSplitHosts(blkLocations,
- length-bytesRemaining, splitSize,clusterMap);
- splits.add(new FileSplit(path,length-bytesRemaining, splitSize,
- splitHosts));
- bytesRemaining -= splitSize;
- }
-
-
- if (bytesRemaining != 0) {
- splits.add(new FileSplit(path,length-bytesRemaining, bytesRemaining,
- blkLocations[blkLocations.length-1].getHosts()));
- }
- } elseif(length != 0) {
-
- String[] splitHosts = getSplitHosts(blkLocations,0,length,clusterMap);
- splits.add(new FileSplit(path, 0, length, splitHosts));
- } else{
-
-
- splits.add(new FileSplit(path, 0, length, new String[0]));
- }
- }
- LOG.debug("Total # of splits: "+ splits.size());
- returnsplits.toArray(newFileSplit[splits.size()]);
- }
通过上述分析,可以知道我们指定一个目录作为job的输入源时,用户指定的MapTask的个数,以及文件总长度,块大小,以及用户指定的最小分片长度会影响到最后可以产生多少个分片,也就是这个Job最后需要执行多少次MapTask。
同时,还可以得知,一个分片是不会跨越两个文件的;一个空的文件也会占用到一个分片;不是每个分片都是等长的;以及一个分片可以跨一个大文件中连续的多个block。
主机列表是什么,如何选择
InputSplit作为一个分片,所包含的的信息中有主机列表这一信息,这不是说这个分片就在这个主机列表上,这是错误的理解。主机列表是指做task的时候,JobTracker会把Task发送到主机列表所在的节点上,由该节点来执行task。
在上面我们已经得出过结论“一个分片可以有多个block”,那么这种这情况下,主机列表就不会覆盖所有block所对应的主机信息,而是根据一种算法来:通过将机架和数据节点引入进来,形成网络拓扑;机架对应的信息中会存储这个机架有这个分片的多少数据量,数据节点对应的节点信息中会存储这个节点有这个分片的多少数据量。根据机架和数据节点这两个信息来排序,会选择出机架列表里包含的了最多数据量的机架,在该机架内选择包含了最多的数据量的数据节点。如果第一个机架的主机列表数量不够,则再从第二个机架内选择数据节点。通过这种形式来选择出最合理的主机列表信息。
另外对应的,如果一个分片只包含一个block,那么就没有上述这么复杂的情况,只要将这个块对应的信息(BlockLocation)中的主机列表信息返回即可。
下面我们来实际分析代码,会通过注释来解释关键的步骤。
- protected String[] getSplitHosts(BlockLocation[] blkLocations,
- longoffset, longsplitSize, NetworkTopology clusterMap)
- throwsIOException {
-
-
- intstartIndex = getBlockIndex(blkLocations, offset);
-
-
- longbytesInThisBlock = blkLocations[startIndex].getOffset() +
- blkLocations[startIndex].getLength() - offset;
-
-
-
-
- if(bytesInThisBlock >= splitSize) {
- returnblkLocations[startIndex].getHosts();
- }
-
-
- longbytesInFirstBlock = bytesInThisBlock;
- intindex = startIndex + 1;
- splitSize -= bytesInThisBlock;
-
-
- while(splitSize > 0) {
- bytesInThisBlock =
- Math.min(splitSize,blkLocations[index++].getLength());
- splitSize -= bytesInThisBlock;
- }
-
- longbytesInLastBlock = bytesInThisBlock;
- intendIndex = index - 1;
-
-
-
-
-
-
-
-
-
- Map <Node,NodeInfo> hostsMap = new IdentityHashMap<Node,NodeInfo>();
- Map <Node,NodeInfo> racksMap = new IdentityHashMap<Node,NodeInfo>();
- String [] allTopos = new String[0];
-
-
-
-
-
- for(index = startIndex; index <= endIndex; index++) {
-
-
-
- if(index == startIndex) {
- bytesInThisBlock = bytesInFirstBlock;
- }
- elseif(index == endIndex) {
- bytesInThisBlock = bytesInLastBlock;
- }
- else{
- bytesInThisBlock =blkLocations[index].getLength();
- }
-
-
-
- allTopos = blkLocations[index].getTopologyPaths();
-
-
-
- if(allTopos.length== 0) {
- allTopos = fakeRacks(blkLocations,index);
- }
-
-
-
-
-
-
-
- for(String topo: allTopos) {
-
- Node node, parentNode;
- NodeInfo nodeInfo, parentNodeInfo;
-
- node = clusterMap.getNode(topo);
-
- if (node == null) {
- node = new NodeBase(topo);
- clusterMap.add(node);
- }
-
- nodeInfo = hostsMap.get(node);
-
-
-
- if (nodeInfo == null) {
- nodeInfo = new NodeInfo(node);
- hostsMap.put(node,nodeInfo);
- parentNode = node.getParent();
- parentNodeInfo =racksMap.get(parentNode);
- if (parentNodeInfo == null) {
- parentNodeInfo = new NodeInfo(parentNode);
- racksMap.put(parentNode,parentNodeInfo);
- }
- parentNodeInfo.addLeaf(nodeInfo);
- }
- else {
- nodeInfo = hostsMap.get(node);
- parentNode = node.getParent();
- parentNodeInfo =racksMap.get(parentNode);
- }
-
-
- nodeInfo.addValue(index,bytesInThisBlock);
-
- parentNodeInfo.addValue(index,bytesInThisBlock);
-
- }
-
- }
-
- returnidentifyHosts(allTopos.length, racksMap);
- }
-
-
-
-
- privateString[] identifyHosts(int replicationFactor,
- Map<Node,NodeInfo> racksMap) {
-
- String [] retVal = new String[replicationFactor];
-
- List <NodeInfo> rackList = new LinkedList<NodeInfo>();
-
- rackList.addAll(racksMap.values());
-
-
-
- sortInDescendingOrder(rackList);
-
- booleandone = false;
- intindex = 0;
-
-
-
-
- for(NodeInfo ni: rackList) {
-
- Set<NodeInfo> hostSet= ni.getLeaves();
-
- List<NodeInfo>hostList = new LinkedList<NodeInfo>();
- hostList.addAll(hostSet);
-
-
- sortInDescendingOrder(hostList);
-
-
- for(NodeInfo host: hostList) {
-
- retVal[index++] = host.node.getName().split(":")[0];
- if (index == replicationFactor) {
- done = true;
- break;
- }
- }
-
- if(done == true){
- break;
- }
- }
- returnretVal;
- }
通过上述选择主机的算法,我们可以知道,当一个分片包含的多个block的时候,总会从其他节点读取数据,也就是做不到所有的计算都是本地化。为了发挥计算本地化性能,应该尽量使InputSplit大小与块大小相当。
在旧版的接口中,InputSplit的大小会受maptask个数,和split参数的影响,需要具体情况具体调整。在新版的接口中,这个比较容易控制,因为不受maptask的影响,InputSplit大小计算公式如下: splitSize=max("mapred.min.split.size",min("mapred.max.split.size",blockSize))
两个参数都取默认配置的时候,分片大小就是blockSize
转载地址:http://blog.csdn.net/chlaws/article/details/22900141