简介
本文是笔者在学习HDFS的时候的学习笔记整理, 将HDFS的核心功能的原理都整理在这里了。
【广告】 如果你喜欢本博客,请点此查看本博客所有文章:http://www.cnblogs.com/xuanku/p/index.html
HDFS的基础架构
见下图, 核心角色: Client, NameNode, Secondary NameNode, DataNode
-
Client: 对用户提供系列操作工具&API
-
NameNode:
- 包含
map<filename, list<block_id>>
, 以及map<block_id, list<DataNode>>
的数据结构 - 资源分配算法
- 包含
-
DataNode:
- 管理好自己的磁盘, 上报数据给NameNode
读取过程
- Client向NameNode读取数据分布式信息
- Client找到第一个数据块离自己最近的DataNode
- 跟这个DataNode交互并获取数据
- 读完之后开始跟下一个数据块离自己最近的DataNode交互
- 读完之后close连接
- 如果读取过程中读取失败, 将会依次读取该数据块下一个副本, 失败的节点将被记录, 不再连接
近的判断标准(NetworkTopology.sortByDistance):
- 如果客户端和某个Datanode在同一台机器上, 优先
- 如果客户端和某个Datanode在同一个rack上, 次优先
- 否则随机
详情请参考下图:
参考:
写入过程
流程
- 客户端通知NameNode创建目录
- 客户端开始写数据, 先写到本地, 然后定期分块
- 要写新块的时候再跟NameNode打交道, 获取到新块的目标地址
- 同一个数据块的不同副本是链式同步, 客户端只跟第一个副本打交道
- 只有所有副本都写入成功, 才开始下一个块的写操作
- 如果有一个写失败, 则:
- 失败的DataNode会加一个标记, 根据这个标记, 这份不完全的数据回头会被删除
- 不再往失败的DataNode上面写, 其他两个DataNode继续写
- 告诉NameNode这份数据副本数不足, NameNode回头会异步的补上
- 如果副本数少于某个配置(比如1个), 整个写入就算失败
详情参考下图:
副本分配策略
-
选择一个本地节点
-
选择一个本地机架节点
-
选择一个远程节点
-
随机选择一个节点
-
调整同步顺序(以节约带宽为目标)
参考: http://www.linuxidc.com/Linux/2012-01/50864.htm
如何感知机架位
通过NameNode的一个配置: topology.script.file.name 来控制的, 该配置的值对应一个脚本, 脚本输入是一个IP/字符串, 输出一个机架位名称, 该名称可以用"/xx/xx"的树形结构来表示网络拓扑。
如果没有配置该值, 则代表所有机器一个机架位, 会增加机器网络带宽消耗。
参考: http://www.cnblogs.com/ggjucheng/archive/2013/01/03/2843015.html
HDFS Append文件逻辑
首先要说明两个概念, block和replica, 在NN中叫block, 在DN中叫replica。
block有四种状态:
- complete. block的长度和时间戳都不再变化, 并且至少有一个DN对应的rpelica是finalized的状态。
- under_construction. 文件被create和append的时候, 该block就处于under_construction的状态。该状态下文件是可以被读取的, 读取的长度是保证在所有的DN上副本都能读取到的长度;
- under_recovery. 如果一个文件的最后一个block在under_construction的状态时, client异常掉线了, 那么需要有一段时间的lease过期和恢复释放锁和关闭文件的过程, 这段时间之内该block处于under_recovery的状态.
- committed. 介于under_construction和complete之间的状态。client收到所有DN写成功的ARK, 但是NN还没有收到任何一个DN报replica已经finalized的状态。
replica状态要复杂一些:
- Finalized. 写完事儿之后的状态。
- rbw(replica being written). 类似under_construction, 在创建和写入过程中的replica。同under_construction, 也是可以被读取的;
- rwr(rpelica waiting recovry). 异常之后, 等待lease过期的状态;
- rur(replica under recovery). 异常之后, lease过期之后修复数据的状态;
- Temporary. 类似rbw, 但是不是正常写的状态, 是比如集群间balance的状态。跟rbw不同的是, 同步中的文件不可读;
参考: http://yanbohappy.sinaapp.com/?p=175
SecondaryNameNode机制
- SecondaryNameNode不是说NameNode挂了的备用节点
- 他的主要功能只是定期合并日志, 防止日志文件变得过大
- 合并过后的镜像文件在NameNode上也会保存一份
SecondaryNameNode工作过程:
- SecondaryNameNode向NameNode发起同步请求, 此时NameNode会将日志都写到新的日志当中
- SecondaryNameNode向NameNode下载镜像文件+日志文件
- SecondaryNameNode开始Merge这两份文件并生成新的镜像文件
- SecondaryNameNode向NameNode传回新的镜像文件
- NameNode文件将新的镜像文件和日志文件替换成当前正在使用的文件
详情请参考下图:
BackupNode机制
跟Mysql的Master-Slave机制类似, BackupNode是作为热备而存在, 同步更新NameNode节点的数据。
NameNode HA机制
NameNode数据主要包含两类:
-
map<filename, list<block_id>>
即数据源信息这一类数据主要存储在两个文件中:
fsimg
,editlog
, 如上所说SecondaryNameNode的作用就是定期merge这两个文件。在HA机制中, 流程为将editlog写入一个共享存储, 一般为QJM(Quorum Journal Manager)节点, 一般为3个节点。active NN的editlog实时写入qjm节点, standby的NN定期从editlog中同步数据到自己的节点信息当中。
每次日志都有一个自增的epoch_id, jn会对比自己已有的epoch_id和NN给的epoch_id, 如果NN给的epoch_id比较小, 则会忽略该命令, 以此方式来达到避免脑裂问题。
NN通过ZKFC(ZooKeeper Fail Controller)来控制当前到底是哪个节点为ActiveNameNode, 在每个NN上都单独启动了一个ZKFC的进程, 该进程一方面监控NN的状态信息, 另一方面跟ZK保持连接说明自己的状态。类似租约, 一旦NN节点挂了, ZK会自动更新该节点状态信息, 同时通知另外的节点, 另外的节点就好接替前一个NN的工作。
-
map<block_id, list<DataNode>>
即block_id和DataNode的对应关系这个本身信息是通过DataNode给NameNode上报来做到的, 增加了节点之后, DataNode跟每个NameNode都会上报一份信息。
类似qjm, DataNode也会维护一个自增的id, 当有NN切换的时候, 也会增加这个id, DN会拒绝比当前id还要小的控制命令。
HA的数据流图如下:
参考:
- 当前HDFS HA介绍. http://www.it165.net/admin/html/201407/3465.html
- HDFS HA进化论. http://www.bubuko.com/infodetail_124006.html
HDFS Federation
本身实现不复杂, 就是将原来所有信息都放在一个NameNode节点里, 变成了可以将NameNode信息拆分放到多个节点里, 一人分一个目录。
注意有一个block pool的概念, 为了避免在分配DataNode上的打架, 为每个NameNode分配了一个专属的block pool, 这样大家就分开了, 需要一开始配置自己的NameNode需要多少空间, 即Namespace Volume。
如下图:
参考:
- http://zh.hortonworks.com/blog/an-introduction-to-hdfs-federation/
- http://blog.csdn.net/strongerbit/article/details/7013221/
- https://issues.apache.org/jira/secure/attachment/12453067/high-level-design.pdf
HDFS distcp
distcp1:
-
利用mapreduce来传输文件
-
为了保证文件内block的有序性, 所以map最小粒度为文件
问题出来了, 大文件会拖慢小文件, 导致整个拷贝效率不行
distcp2:
针对distcp1, 做了一些优化:
- 去掉了一些不必要的目录检查工作, 从而缩短了目录检查时间;
- 动态分配map task的工作量, 在运行过程中调整自己的任务量, 能优化部分distcp1的情况;
- 可对拷贝进行限速
- 支持HSFTP
fastcopy:
最主要是对federation机制的支持, 如果使用fastcp在同一个集群中不同的federation进行拷贝的时候, 则不需要再走一遍网络和删除, 只修改源数据即可, 但是distcp不行。
facebook的hadoop版本已经将fastcopy的这个特性集成到了distcp当中。
参考:
HDFS Balancer
是一个脚本, 该脚本做的事情很简单:
- 从NameNode获取DataNode数据分布信息
- 计算数据移动方案
- 执行移动方案
- 直到满足平衡要求
平衡要求是该脚本的一个输入(0~100), 代表不同机器的磁盘利用率差值。
注意:
- Balancer程序现在设计是不会将一个rack的数据移动到另外一个rack
- 也就是说跨rack的均衡是不能满足的, 除非有修改后的Balancer程序
- 一般不要将Balancer放到NameNode上运行
数据流图:
参考:
- balancer介绍. http://www.aboutyun.com/thread-7354-1-1.html
HDFS 快照
快照是给当前hdfs内容建立一个只读备份, 可以针对整个hdfs或者某个目录创建, 一般用于备份, 故障恢复, 避免人工故障等。
HDFS的快照有如下特点:
- 快照是瞬间创造的, 如果抛开inode的查询时间, 只需要O(1)
- 快照创建以后需要额外的内存来存储变化, 内存需要是O(M)
- 快照只是记录了block_list和文件大小信息, 不做任何的实际数据拷贝
- 快照不会影响到现在数据的增删改查, 查询快照的时候, 会根据当前结果以及记录的日志做减法来获取快照数据
HDFS NFSv3
就是在本地通过挂载NFS访问HDFS的文件
参考: http://blog.csdn.net/dmcpxy/article/details/18257065
HDFS dfsadmin
参考: http://blog.csdn.net/xiaojin21cen/article/details/42610697
hdfs 权限控制
认证:
支持两种方式, simple和kerberos, 通过hadoop.security.authentication
这个类配置。默认是simple模式, simple模式下, 用户名为whoami
, 组名为bash -c group
kerberos没有研究。
授权:
针对每个目录, 有读写一级可执行的权限设置, 权限有分为用户级别, 组级别, 以及其他。
同时也可以通过设置ACL来针对一个目录的某些用户设置特殊权限。
参考: http://demo.netfoucs.com/skywalker_only/article/details/40709447
代码阅读笔记
读取文件信息
客户端代码
- DFSClient.getFileInfo
- 到ClientProtocol类里查看getFileInfo
是接口, 用ctl+T查看其子类列表, 找到ClientNamenodeProtocolTranslatorPB类 - 再看ClientNamenodeProtocolTranslatorPB.getFileInfo函数
看到其调用了rpcProxy.getFileInfo类, 找rpcProxy的来源, 发现是构造函数传进来的, 所以用ctl+alt+H来找到其反向调用关系 - NameNodeProxies.createNNProxyWithClientProtocol函数
发现在该函数中调用了RPC.getProtocolProxy函数来获取该proxy, 在获得该proxy的时候传入了系列NN服务相关配置, 以及SocketFactory - 到RPC.getProtocolProxy函数
发现其是调用了getProtocolEngine(XX).getProxy(XX)的函数, 那么先看setProtocolEngine函数 - 到RPC.setProtocolEngine函数中看
发现其都设置的是RpcEngine的子类, 那么去看该RpcEngine的接口, 发现其实一个interface, 老办法, 用ctl+T看其子类, 找到ProtobufRpcEngine子类 - 找ProtobufRpcEngine.getProxy函数
发现其主要是使用了自己的一个Invoker的类, 该Invoker是一个动态代理类, 主要关注其invoke函数即可 - 看ProtobufRpcEngine.Invoker.invoke函数
该函数就是各种网络操作了, 可以看到他是将函数名拼成了一个字节流, 然后发给了NN, 然后hang住等待NN的返回结果
服务端代码
- ClientProtocol类是用来通信的类, 客户端和服务端都会用到, 直接看其子类即可
看到其子类有一个NameNodeRpcServer, 看起来肯定就是NN服务端这头的代码了 - NameNodeRpcServer.getFileInfo
发现其实调用了namesystem.getFileInfo函数 - namesystem.getFileInfo函数
一层一层往下面调用, 就可以找到其最终的逻辑了
NN启动代码
直接进入NameNode.main查看
- 参数获取和查看我们略过, 我们着重关注后面的逻辑
- createNameNode
调用了构造函数: NameNode(conf), conf已经根据argv参数初始化好了
该函数又调用了NameNode.initialize函数 - initialize(conf)
- 启动了http接口, 先启动http接口应该是说可以通过http接口来查看启动状态
- 调用loadNamesystem函数
- 该函数又调用了FSNamesystem.loadNamesystem函数
FSNamesystem就是非常重要的类了, 基本上所有的NN的核心数据结构都放在这个类里面了 - 主要关注该类的loadFSImage函数
- namenode.join()
参考文章
- hdfs流程简介. http://www.cnblogs.com/forfuture1978/archive/2010/03/14/1685351.html
- hdfs vs kfs. http://blog.csdn.net/Cloudeep/article/details/4467238
- hdfs的缺陷. http://www.cnblogs.com/wycg1984/archive/2010/03/20/1690281.html
- hdfs配置. http://cqfish.blog.51cto.com/622299/207766
- hdfs看分布式文件系统设计需求. http://dennis-zane.javaeye.com/blog/228537
- 利用Java API访问hdfs文件. http://blog.csdn.net/zhangzhaokun/article/details/5597433
- hdfs重大性能杀手——shell. http://blog.csdn.net/fly542/article/details/6819945
- DataNode的stale状态. http://www.tuicool.com/articles/RneQve
- hdfs源码阅读. http://www.linuxidc.com/Linux/2012-03/55966.htm