HDFS对文件的存储是分块来存储的,即HDFS对于客户端写入的数据先按照固定大小对这些数据进行分块,然后把每一个数据块的多个副本存储在不同的DataNode节点上,同时不同的数据块也可能存储在不同的DataNode节点上。那么,当客户端要从HDFS上读取某个文件时,它又是如何处理的呢?很明显,客户端也是按照分块来读取文件的数据的,关于客户端如何分块读取文件的详细原理与过程我已经在前面的博文中详细的阐述了,在这里就不多赘述了。但是,一个数据块有多个副本,客户端到底优先读取那个DataNode节点上的该数据块的副本呢?这将是本要所要讨论的重点了,即HDFS读取副本的选择策略,而这个工作具体是由NameNode来完成的。
当客户端读到一个文件的某个数据块时,它就需要向NameNode节点询问这个数据块存储在那些DataNode节点上,这个过程如下图:
当然,客户端至少需要向NameNode传输三个参数:文件路径、读取文件的起始位置、读取文件的长度,而NameNode会向该客户端返回它所要读取文件内容所在的位置——LocatedBlock对象,该对象包含对应的数据块所有副本所在的DataNode节点的位置信息,客户端接着就会依次从这些DataNode节点上读取该数据块,直到成功读取为止。这里就设计到了一个优化问题了:客户端应该总是选择从距离它最近的可用DataNode节点上读取需要的数据块,所以此时的关键就是如何来计算客户端与DataNode节点之间的距离。这个计算距离的问题本质上涉及到我前面介绍过的一种数据结构——NetworkTopology,这种数据结构被NameNode节点用来形象的表示HDFS集群中所有DataNode节点的物理位置,自然这个优化工作就交由NameNode来处理了。
NameNode对文件的读优化的实现很简单,基本原理就是按照客户端与DataNode节点之间的距离进行排序,距客户端越近的DataNode节点越被放在LocatedBlock的前面,该算法的基本思路如下:
1.如果该Block的一个副本存在于客户端,则客户端优先从本地读取该数据块;
2.如果该Block的一个副本与客户端在同一个机架上,且没有一个副本存放在客户端,则客户端优先读取这个同机架上的副本;否则客户端优先读取同机器的副本,失败的情况下然后再优先考虑这个同机架上的副本;
3.如果该Block既没有一个副本存在客户端,又没有一个副本与客户端在同一个机架上,则随机选择一个DataNode节点作为优先节点。
其详细实现如下:
/** * @param reader 客户端 * @param nodes 某个Block所在的DataNode节点 */ public void pseudoSortByDistance( Node reader, Node[] nodes ) { int tempIndex = 0; if (reader != null ) { int localRackNode = -1; //scan the array to find the local node & local rack node for(int i=0; i<nodes.length; i++) { if(tempIndex == 0 && reader == nodes[i]) { //local node //swap the local node and the node at position 0 if( i != 0 ) { swap(nodes, tempIndex, i); } tempIndex=1; if(localRackNode != -1 ) { if(localRackNode == 0) { localRackNode = i; } break; } } else if(localRackNode == -1 && isOnSameRack(reader, nodes[i])) { //local rack localRackNode = i; if(tempIndex != 0 ) break; } } // swap the local rack node and the node at position tempIndex if(localRackNode != -1 && localRackNode != tempIndex ) { swap(nodes, tempIndex, localRackNode); tempIndex++; } } // put a random node at position 0 if it is not a local/local-rack node if(tempIndex == 0 && nodes.length != 0) { swap(nodes, 0, r.nextInt(nodes.length)); } }