HDFS作为Hadoop中的一个分布式文件系统,而且是专门为它的MapReduce设计,所以HDFS除了必须满足自己作为分布式文件系统的高可靠性外,还必须为MapReduce提供高效的读写性能,那么HDFS是如何做到这些的呢?首先,HDFS将每一个文件的数据进行分块存储,同时每一个数据块又保存有多个副本,这些数据块副本分布在不同的机器节点上,这种数据分块存储+副本的策略是HDFS保证可靠性和性能的关键,这是因为:一.文件分块存储之后按照数据块来读,提高了文件随机读的效率和并发读的效率;二.保存数据块若干副本到不同的机器节点实现可靠性的同时也提高了同一数据块的并发读效率;三.数据分块是非常切合MapReduce中任务切分的思想。在这里,副本的存放策略又是HDFS实现高可靠性和搞性能的关键。

​ HDFS采用一种称为机架感知的策略来改进数据的可靠性、可用性和网络带宽的利用率。通过一个机架感知的过程,NameNode可以确定每一个DataNode所属的机架id(这也是NameNode采用NetworkTopology数据结构来存储数据节点的原因,也是我在前面详细介绍NetworkTopology类的原因)。一个简单但没有优化的策略就是将副本存放在不同的机架上,这样可以防止当整个机架失效时数据的丢失,并且允许读数据的时候充分利用多个机架的带宽。这种策略设置可以将副本均匀分布在集群中,有利于当组件失效的情况下的均匀负载,但是,因为这种策略的一个写操作需要传输到多个机架,这增加了写的代价。

在大多数情况下,副本系数是3,HDFS的存放策略是将一个副本存放在本地机架节点上,一个副本存放在同一个机架的另一个节点上,最后一个副本放在不同机架的节点上。这种策略减少了机架间的数据传输,提高了写操作的效率。机架的错误远远比节点的错误少,所以这种策略不会影响到数据的可靠性和可用性。与此同时,因为数据块只存放在两个不同的机架上,所以此策略减少了读取数据时需要的网络传输总带宽。在这种策略下,副本并不是均匀的分布在不同的机架上:三分之一的副本在一个节点上,三分之二的副本在一个机架上,其它副本均匀分布在剩下的机架中,这种策略在不损害数据可靠性和读取性能的情况下改进了写的性能。下面就来看看HDFS是如何来具体实现这一策略的。

​ NameNode是通过类来为每一分数据块选择副本的存放位置的,这个ReplicationTargetChooser的一般处理过程如下:

上面的流程图详细的描述了Hadoop-0.2.0版本中副本的存放位置的选择策略,当然,这当中还有一些细节问题,如:如何选择一个本地数据节点,如何选择一个本地机架数据节点等,所以下面我还将继续展开讨论。

1.选择一个本地节点
这里所说的本地节点是相对于客户端来说的,也就是说某一个用户正在用一个客户端来向HDFS中写数据,如果该客户端上有数据节点,那么就应该最优先考虑把正在写入的数据的一个副本保存在这个客户端的数据节点上,它即被看做是本地节点,但是如果这个客户端上的数据节点空间不足或者是当前负载过重,则应该从该数据节点所在的机架中选择一个合适的数据节点作为此时这个数据块的本地节点。另外,如果客户端上没有一个数据节点的话,则从整个集群中随机选择一个合适的数据节点作为此时这个数据块的本地节点。那么,如何判定一个数据节点合不合适呢,它是通过isGoodTarget方法来确定的:

 1. **private \**boolean isGoodTarget(DatanodeDescriptor node, \*\*long blockSize, \*\*int maxTargetPerLoc, \*\*boolean considerLoad, List<DatanodeDescriptor> results) {\*\*\*\*\*\**\*** 2. ​    3. Log logr = FSNamesystem.LOG; 4. // 节点不可用了  5. **if (node.isDecommissionInProgress() || node.isDecommissioned()) {** 6. logr.debug("Node "+NodeBase.getPath(node)+ " is not chosen because the node is (being) decommissioned"); 7. **return \**false;\**** 8. } 9. 10. **long remaining = node.getRemaining() - (node.getBlocksScheduled() \* blockSize);** 11. // 节点剩余的容量够不够  12. **if (blockSize\* FSConstants.MIN_BLOCKS_FOR_WRITE>remaining) {** 13. logr.debug("Node "+NodeBase.getPath(node)+ " is not chosen because the node does not have enough space"); 14. **return \**false;\**** 15. } 16. ​    17. // 节点当前的负载情况  18. **if (considerLoad) {** 19. **double avgLoad = 0;** 20. **int size = clusterMap.getNumOfLeaves();** 21. **if (size != 0) {** 22. ​    avgLoad = (**double)fs.getTotalLoad()/size;** 23. } 24. **if (node.getXceiverCount() > (2.0 \* avgLoad)) {** 25. ​    logr.debug("Node "+NodeBase.getPath(node)+ " is not chosen because the node is too busy"); 26. ​    **return \**false;\**** 27. } 28. } 29. ​    30. // 该节点坐在的机架被选择存放当前数据块副本的数据节点过多  31. String rackname = node.getNetworkLocation(); 32. **int counter=1;** 33. **for(Iterator<DatanodeDescriptor> iter = results.iterator(); iter.hasNext();) {** 34. Node result = iter.next(); 35. **if (rackname.equals(result.getNetworkLocation())) {** 36. ​    counter++; 37. } 38. } 39. **if (counter>maxTargetPerLoc) {** 40. logr.debug("Node "+NodeBase.getPath(node)+ " is not chosen because the rack has too many chosen nodes"); 41. **return \**false;\**** 42. } 43. 44. **return \**true;\**** 45. }

2.选择一个本地机架节点
实际上,选择本地节假节点和远程机架节点都需要以一个节点为参考,这样才是有意义,所以在上面的流程图中,我用红色字体标出了参考点。那么,ReplicationTargetChooser是如何根据一个节点选择它的一个本地机架节点呢?
这个过程很简单,如果参考点为空,则从整个集群中随机选择一个合适的数据节点作为此时的本地机架节点;否则就从参考节点所在的机架中随机选择一个合适的数据节点作为此时的本地机架节点,若这个集群中没有合适的数据节点的话,则从已选择的数据节点中找出一个作为新的参考点,如果找到了一个新的参考点,则从这个新的参考点在的机架中随机选择一个合适的数据节点作为此时的本地机架节点;否则从整个集群中随机选择一个合适的数据节点作为此时的本地机架节点。如果新的参考点所在的机架中仍然没有合适的数据节点,则只能从整个集群中随机选择一个合适的数据节点作为此时的本地机架节点了。

   1.   // 如果参考点为空,则从整个集群中随机选择一个合适的数据节点作为此时的本地机架节点  2.   **if (localMachine == \**null) {\**** 3.    **return chooseRandom(NodeBase.ROOT, excludedNodes, blocksize, maxNodesPerRack, results);** 4.   } 5. ​    6.   //从参考节点所在的机架中随机选择一个合适的数据节点作为此时的本地机架节点  7.   **try {** 8.    **return chooseRandom(localMachine.getNetworkLocation(), excludedNodes, blocksize, maxNodesPerRack, results);** 9.   } **catch (NotEnoughReplicasException e1) {** 10.    //若这个集群中没有合适的数据节点的话,则从已选择的数据节点中找出一个作为新的参考点  11.    DatanodeDescriptor newLocal=**null;** 12.    **for(Iterator<DatanodeDescriptor> iter=results.iterator(); iter.hasNext();) {** 13. ​    DatanodeDescriptor nextNode = iter.next(); 14. ​    **if (nextNode != localMachine) {** 15. ​     newLocal = nextNode; 16. ​     **break;** 17. ​    } 18.    } 19. ​    20.    **if (newLocal != \**null) {//找到了一个新的参考点\****  21. ​    **try {** 22. ​     //从这个新的参考点在的机架中随机选择一个合适的数据节点作为此时的本地机架节点  23. ​     **return chooseRandom(newLocal.getNetworkLocation(), excludedNodes, blocksize, maxNodesPerRack, results);** 24. ​    } **catch(NotEnoughReplicasException e2) {** 25. ​     //新的参考点所在的机架中仍然没有合适的数据节点,从整个集群中随机选择一个合适的数据节点作为此时的本地机架节点  26. ​     **return chooseRandom(NodeBase.ROOT, excludedNodes, blocksize, maxNodesPerRack, results);** 27. ​    } 28.    } **else {** 29. ​    //从整个集群中随机选择一个合适的数据节点作为此时的本地机架节点  30. ​    **return chooseRandom(NodeBase.ROOT, excludedNodes, blocksize, maxNodesPerRack, results);** 31.    } 32.   } 33.  }

3.选择一个远程机架节点
选择一个远程机架节点就是随机的选择一个合适的不在参考点坐在的机架中的数据节点,如果没有找到这个合适的数据节点的话,就只能从参考点所在的机架中选择一个合适的数据节点作为此时的远程机架节点了。

4.随机选择若干数据节点
这里的随机随机选择若干个数据节点实际上指的是从某一个范围内随机的选择若干个节点,它的实现需要利用前面提到过的NetworkTopology数据结构。随机选择所使用的范围本质上指的是一个路径,这个路径表示的是NetworkTopology所表示的树状网络拓扑图中的一个非叶子节点,随机选择针对的就是这个节点的所有叶子子节点,因为所有的数据节点都被表示成了这个树状网络拓扑图中的叶子节点。

5.优化数据传输的路径
以前说过,HDFS对于Block的副本copy采用的是流水线作业的方式:client把数据Block只传给一个DataNode,这个DataNode收到Block之后,传给下一个DataNode,依次类推,…,最后一个DataNode就不需要下传数据Block了。所以,在为一个数据块确定了所有的副本存放的位置之后,就需要确定这种数据节点之间流水复制的顺序,这种顺序应该使得数据传输时花费的网络延时最小。ReplicationTargetChooser用了非常简单的方法来考量的,大家一看便知:

   1. **private DatanodeDescriptor[] getPipeline( DatanodeDescriptor writer, DatanodeDescriptor[] nodes) {** 2. **if (nodes.length==0) \**return nodes;\**** 3. ​    4. **synchronized(clusterMap) {** 5. **int index=0;** 6. **if (writer == \**null || !clusterMap.contains(writer)) {\**** 7. ​    writer = nodes[0]; 8. } 9. ​    10. **for(;index<nodes.length; index++) {** 11. ​    DatanodeDescriptor shortestNode = nodes[index]; 12. ​    **int shortestDistance = clusterMap.getDistance(writer, shortestNode);** 13. ​    **int shortestIndex = index;** 14. ​    **for(\**int i=index+1; i<nodes.length; i++) {\**** 15. ​     DatanodeDescriptor currentNode = nodes[i]; 16. ​     **int currentDistance = clusterMap.getDistance(writer, currentNode);** 17. ​     **if (shortestDistance>currentDistance) {** 18. ​      shortestDistance = currentDistance; 19. ​      shortestNode = currentNode; 20. ​      shortestIndex = i; 21. ​     } 22. ​    } 23. ​    //switch position index & shortestIndex  24. ​    **if (index != shortestIndex) {** 25. ​     nodes[shortestIndex] = nodes[index]; 26. ​     nodes[index] = shortestNode; 27. ​    } 28. ​    writer = shortestNode; 29. } 30. } 31. **return nodes;** 32. }

可惜的是,HDFS目前并没有把副本存放策略的实现开放给用户,也就是用户无法根据自己的实际需求来指定文件的数据块存放的具体位置。例如:我们可以将有关系的两个文件放到相同的数据节点上,这样在进行map-reduce的时候,其工作效率会大大的提高。但是,又考虑到副本存放策略是与集群负载均衡休戚相关的,所以要是真的把负载存放策略交给用户来实现的话,对用户来说是相当负载的,所以我只能说Hadoop目前还不算成熟,尚需大踏步发展。

HDFS的副本存放策略(机架感知策略)相关推荐

  1. 华为云大数据存储的冗余方式是三副本_大数据入门:HDFS数据副本存放策略

    大数据处理当中,数据储存始终是一个重要的环节,从现阶段的市场现状来说,以Hadoop为首的大数据技术框架,仍然占据主流地位,而Hadoop的HDFS,在数据存储方面,仍然得到重用.今天的大数据入门分享 ...

  2. 浅析HDFS的副本存放策略

    在大多数情况下,副本系数是3,HDFS的存放策略是: 将一个副本存放在本地机架节点上, 一个副本存放在同一个机架的另一个节点上, 最后一个副本放在不同机架的节点上. 简单说:在我们的笔记本中,搭建ha ...

  3. HDFS副本存放策略(官方文档)

    HDFS副本存放策略(官方文档) 关于HDFS的副本位置默认存放策略(以3个副本为例),网络上一直流传着两种说法: 第一种说法: 副本1:在client端所处的节点(就近),若客户端在集群外则随机选一 ...

  4. 9、HDFS核心设计--心跳机制、安全模式、副本存放策略、负载均衡

    1.HADOOP 心跳机制(heartbeat) 1. Hadoop 是 主从(Master/Slave )结构,Master 中包括 NameNode 和 ResourceManager,Slave ...

  5. HDFS副本放置策略和机架感知

    副本放置策略 的副本放置策略的基本思想是: 第一block在复制和client哪里node于(假设client它不是群集的范围内,则这第一个node是随机选取的.当然系统会尝试不选择哪些太满或者太忙的 ...

  6. HDFS数据副本的摆放策略

    HDFS数据副本的摆放策略 副本的存放位置对于HDFS的性能和可靠性是非常重要的,如果副本的存放机制不好的话,在计算的过程中很大可能会产生数据传输,这样对于带宽和磁盘的IO影响非常巨大.因此对于优化副 ...

  7. HDFS详解(架构设计、副本放置策略、读写流程、进程、常用命令等)

    前言:当数据集的大小超过一台独立的物理计算机的存储能力时,就有必要对它进行分区(Partition)并存储到若干台单独的计算机上.管理网络中跨多台计算机存储的文件系统成为分布式文件系统(distrib ...

  8. [Hadoop]HDFS机架感知策略

    HDFS NameNode对文件块复制相关所有事物负责,它周期性接受来自于DataNode的HeartBeat和BlockReport信息,HDFS文件块副本的放置对于系统整体的可靠性和性能有关键性影 ...

  9. HDFS的写入流程及副本复制策略

    步骤补充     1.向namenode发送请求上传文件 然后在namenode里会进行检查是否存在该文件,权限问题 通过则给一个输出流对象     2.建立好pipeline管道后,客户端先把文件写 ...

最新文章

  1. 整体关闭screen(转)
  2. 英国已有500万宽带用户接入并开始使用IPv6技术
  3. tortoisesvn › prefer local prefer repository
  4. 大神接棒,YOLOv4来了!
  5. 网络优化实践探索文章
  6. Tablestore + Blink实战:交易数据的实时统计
  7. 传感器和单片机主板之间远距离通讯应该有哪些保障处理措施
  8. HMS Core 携优势亮相华为发布会,与苹果谷歌三足鼎立
  9. 2)Java中的==和equals
  10. 浪潮之巅--蓝色巨人读后感
  11. Duilib使用---颜色配置
  12. VC知识库文档中心嵌入开发WinCE 里面不少写的很好的WinCE的文章
  13. 破解密码很难?利用Python自动编写暴力破解字典,***必学技能!
  14. weka软件使用问题解决(一)
  15. python 找色点击_Python实现按键精灵(二)-找图找色
  16. 计算机服务器组装,一台家用虚拟化测试服务器组装
  17. java 计算年龄_java实现简单年龄计算器
  18. Redis 内存分析神器
  19. 【UE5】蓝图制作简单地雷教程
  20. 达人评测 赛扬J4105和赛扬N5095选哪个好

热门文章

  1. python源码精要(6)-CPython编译标志
  2. rust(43)-rust语言特点与版本发布
  3. 【NLP】从整体视角了解情感分析、文本分类!
  4. 大神接棒,YOLOv4来了!
  5. 作为算法工程师,在咨询公司工作的另类体验
  6. 云游戏打破硬件限制,传输体验或成发展掣肘!
  7. Wireshark对HTTPS数据的解密
  8. 样条表示---样条表示之间的转换
  9. 前端知识点总结——JS高级(持续更新中)
  10. 美国政府牵头6家公司开发新一代超算 与中国竞争