1 HDFS 1.0 架构
NameNode
NameNode 负责管理整个分布式系统的元数据,主要包括:
- 目录树结构;
- 文件到 Block 的映射关系;
- Block 副本及其存储位置等管理数据;
- DataNode 的状态监控,两者通过心跳来传递管理信息和数据信息,通过这种方式的信息传递,NameNode 可以获知每个 DataNode 保存的 Block 信息、DataNode 的健康状况、命令 DataNode 启动停止等(如果发现某个 DataNode 节点故障,NameNode 会将其负责的 block 在其他 DataNode 上进行备份)。
这些数据保存在内存中,同时在磁盘保存两个元数据管理文件:fsimage 和 editlog。这两个文件相结合可以构造完整的内存数据。
- fsimage:是内存命名空间元数据在外存的镜像文件;
- editlog:则是各种元数据操作的 write-ahead-log 文件,在体现到内存数据变化前首先会将操作记入 editlog 中,以防止数据丢失。
Secondary NameNode
Secondary NameNode 并不是 NameNode 的热备机,而是定期从 NameNode 拉取 fsimage 和 editlog 文件,并对两个文件进行合并,形成新的 fsimage 文件并传回 NameNode,这样做的目的是减轻 NameNode 的工作压力,本质上 SNN 是一个提供检查点功能服务的服务点。
DataNode
负责数据块的实际存储和读写工作,Block 默认是64MB(HDFS2.0改成了128MB),当客户端上传一个大文件时,HDFS 会自动将其切割成固定大小的 Block,为了保证数据可用性,每个 Block 会以多备份的形式存储,默认是3份。
缺陷:
- NameNode 的单点问题,如果 NameNode 挂掉了,数据读写都会受到影响,HDFS 整体将变得不可用,这在生产环境中是不可接受的;
- 水平扩展问题,随着集群规模的扩大,1.0 时集群规模达到3000时,会导致整个集群管理的文件数目达到上限(因为 NameNode 要管理整个集群 block 元信息、数据目录信息等)。
为了解决上面的两个问题,Hadoop2.0 提供一套统一的解决方案:
- HA(High Availability 高可用方案):这个是为了解决 NameNode 单点问题;
- NameNode Federation:是用来解决 HDFS 集群的线性扩展能力。
2 HDFS写数据流程
-
1.客户端向NameNode发出写文件请求,通过RPC与NameNode建立通信。
-
2.namenode收到客户端的请求后,首先会检测元数据的目录树;检查权限并判断待上传的文件是否已存在,如果已存在,则拒绝client的上传。如果不存在,则响应客户端可以上传。
(注:WAL,write ahead log,先写Log,再写内存,因为EditLog记录的是最新的HDFS客户端执行所有的写操作。如果后续真实写操作失败了,由于在真实写操作之前,操作就被写入EditLog中了,故EditLog中仍会有记录,我们不用担心后续client读不到相应的数据块,因为在第5步中DataNode收到块后会有一返回确认信息,若没写成功,发送端没收到确认信息,会一直重试,直到成功) -
3.客户端收到可以上传的响应后,会把待上传的文件切块(hadoop2.x默认块大小为128M);然后再次给namenode发送请求,上传第一个block块。
-
4.namenode收到客户端上传block块的请求后,首先会检测其保存的datanode信息,确定该文件块存储在哪些节点上;最后,响应给客户端一组datanode节点信息。
-
5.客户端根据收到的datanode节点信息,首先就近与某个datanode节点例如A建立网络连接,本质上就是RPC调用,建立pipeline,A收到请求后会继续调用B,B会继续调用C,将整个pipeline建立完成,连通后逐级返回确认信息给客户端,表示通道已连通,可以传输数据。
-
6.客户端收到确认信息后,开始向A上上传第一个block(先从磁盘读取数据后放到本地缓存),以packet(数据包,64kb)为单位,A收到一个packet就会发送给B,然后B发送给C。
(注:并不是写好一个块或一整个文件后才向后分发)
每个DataNode写完一个块后,会返回确认信息。
(注:并不是每写完一个packet后就返回确认信息,个人觉得因为packet中的每个chunk都携带校验信息,没必要每写一个就汇报一下,这样效率太慢。正确的做法是写完一个block块后,对校验信息进行汇总分析,就能得出是否有块写错的情况发生。pipeline正向传输的是数据流,反向传输的是ack确认) -
7.第一个block块写入完毕,若客户端还有剩余的block未上传;则客户端会从(3)开始,继续执行上述步骤;直到整个文件上传完毕。
写完数据,关闭输输出流。
发送完成信号给NameNode。
(注:发送完成信号的时机取决于集群是强一致性还是最终一致性,强一致性则需要所有DataNode写完后才向NameNode汇报。最终一致性则其中任意一个DataNode写完后就能单独向NameNode汇报,HDFS一般情况下都是强调强一致性)
3 HDFS读数据流程
HDFS 文件读取过程
其具体过程总结如下(简单总结一下):
- Client 通过 DistributedFileSystem 对象与集群的 NameNode 进行一次 RPC 远程调用,获取文件 block 位置信息;
- NameNode 返回存储的每个块的 DataNode 列表;
- Client 将连接到列表中最近的 DataNode;
- Client 开始从 DataNode 并行读取数据;
- 一旦 Client 获得了所有必须的 block,它 就会将这些 block 组合起来形成一个文件。
在处理 Client 的读取请求时,HDFS 会利用机架感知选举最接近 Client 位置的副本,这将会减少读取延迟和带宽消耗。
4 HDFS2.0 高可用
HDFS 高可用架构主要由以下组件所构成:
- Active NameNode 和 Standby NameNode:两台 NameNode 形成互备,一台处于 Active 状态,为主 NameNode,另外一台处于 Standby 状态,为备 NameNode,只有主 NameNode 才能对外提供读写服务。
- 主备切换控制器 ZKFailoverController:ZKFailoverController 作为独立的进程运行,对 NameNode 的主备切换进行总体控制。ZKFailoverController 能及时检测到 NameNode 的健康状况,在主 NameNode 故障时借助 Zookeeper 实现自动的主备选举和切换,当然 NameNode 目前也支持不依赖于 Zookeeper 的手动主备切换。
- Zookeeper 集群:为主备切换控制器提供主备选举支持。
- 共享存储系统:共享存储系统是实现 NameNode 的高可用最为关键的部分,共享存储系统保存了 NameNode 在运行过程中所产生的 HDFS 的元数据。主 NameNode 和 NameNode 通过共享存储系统实现元数据同步。在进行主备切换的时候,新的主 NameNode 在确认元数据完全同步之后才能继续对外提供服务。
- DataNode 节点:除了通过共享存储系统共享 HDFS 的元数据信息之外,主 NameNode 和备 NameNode 还需要共享 HDFS 的数据块和 DataNode 之间的映射关系。DataNode 会同时向主 NameNode 和备 NameNode 上报数据块的位置信息。
5 HDFS HA中的共享存储如何实现
NameNode的共享存储方案有很多,比如Linux HA,WMware FT, QJM等,基于QJM的共享存储已经成为默认实现。
基于QJM 的共享存储系统主要用于保存EditLog,并不保存FSImage 文件。FSImage 文件还是在NameNode 的本地磁盘上。QJM 共享存储的基本思想来自于Paxos 算法,采用多个称为JournalNode 的节点组成的JournalNode 集群来存储EditLog。每个JournalNode保存同样的EditLog 副本。每次NameNode 写EditLog 的时候,除了向本地磁盘写入EditLog 之外,也会并行地向JournalNode 集群之中的每一个JournalNode 发送写请求,只要大多数(majority) 的JournalNode 节点返回成功就认为向JournalNode 集群写入
EditLog 成功。如果有2N+1 台JournalNode,那么根据大多数的原则,最多可以容忍有N台JournalNode 节点挂掉。
6 NameNode 主备切换
-
1,HealthMonitor 初始化完成之后会启动内部的线程来定时调用对应 NameNode 的 HAServiceProtocol RPC 接口的方法,对 NameNode 的健康状态进行检测。
-
2,HealthMonitor 如果检测到 NameNode 的健康状态发生变化,会回调 ZKFailoverController 注册的相应方法进行处理。
-
3,如果 ZKFailoverController 判断需要进行主备切换,会首先使用 ActiveStandbyElector 来进行自动的主备选举。
-
4,ActiveStandbyElector 与 Zookeeper 进行交互完成自动的主备选举。
-
5,ActiveStandbyElector 在主备选举完成后,会回调 ZKFailoverController 的相应方法来通知当前的 NameNode 成为主 NameNode 或备 NameNode。
-
6,ZKFailoverController 调用对应 NameNode 的 HAServiceProtocol RPC 接口的方法将 NameNode 转换为 Active 状态或 Standby 状态。
7 HDFS 2.0 Federation 实现
Federation 的核心思想是将一个大的 namespace 划分多个子 namespace,并且每个 namespace 分别由单独的 NameNode 负责,这些 NameNode 之间互相独立,不会影响,不需要做任何协调工作(其实跟拆集群有一些相似),集群的所有 DataNode 会被多个 NameNode 共享。
其中,每个子 namespace 和 DataNode 之间会由block pools建立映射关系,block pools由若干pool构成,一个子 namespace 可以对应1个pool,一个pool可以对应多个DataNode。每个 DataNode 需要向集群中所有的 NameNode 注册,且周期性地向所有 NameNode 发送心跳和块报告,并执行来自所有 NameNode 的命令。
- 一个 block pool 由属于同一个 namespace 的数据块组成,每个 DataNode 可能会存储集群中所有 block pool 的数据块;
- 每个 block pool 内部自治,也就是说各自管理各自的 block,不会与其他 block pool 交流,如果一个 NameNode 挂掉了,不会影响其他 NameNode;
- 某个 NameNode 上的 namespace 和它对应的 block pool 一起被称为 namespace volume,它是管理的基本单位。当一个 NameNode/namespace 被删除后,其所有 DataNode 上对应的 block 也会被删除,当集群升级时,每个 namespace volume 可以作为一个基本单元进行升级。
8 HDFS 脑裂问题
脑裂(split-brain),指在一个高可用(HA)系统中,当联系着的两个节点断开联系时,本来为一个整体的系统,分裂为两个独立节点,这时两个节点开始争抢共享资源,结果会导致系统混乱,数据损坏。比如NameNode 可能会出现这种情况,NameNode 在垃圾回收(GC)时,可能会在长时间内整个系统无响应,因此,也就无法向 zk 写入心跳信息,这样的话可能会导致临时节点掉线,备 NameNode 会切换到 Active 状态,这种情况,可能会导致整个集群会有同时有两个 NameNode,这就是脑裂问题。
zookeeper社区对这种问题的解决方法叫做fencing,中文翻译为隔离,也就是想办法把旧的active namenode隔离起来,使它不能正常对外提供服务。
进行fencing的时候会执行以下操作:
(1)首先尝试调用这个旧的active NameNode的哈HAServiceProtocol RPC接口的transitionToStandby方法,看能不能把它转换为Standby状态。
(2)如果transitionToStandby方法调用失败,那么就执行Hadoop配置文件之中预定义的隔离措施,Hadoop目前主要提供两种隔离措施,通常会选择sshfence:
- sshfencing:通过ssh登录到目标主机上,执行命令fuser将对应的进程杀死
- shellfencing:执行一个用户自定义的shell脚本来将对应的进程隔离。
9 QJM 设计
10小文件过多会有什么危害,如何避免
Hadoop上大量hdfs元数据信息存储在NameNode内存中,因此过多的小文件必定会压垮NameNode的内存。
显而易见的解决方案就是合并小文件,可以选择在客户端上传时执行一定的策略先合并,或者是使用Hadoop的CombineFileInputFormat<K,V>实现小文件的合并。
11 HDFS在读取文件的过程中,如果其中一个快突然损坏了怎么办
客户端读取完DataNode 上的块之后会进行checksum 验证,也就是把客户端读取到本地的块与HDFS 上的原始块进行校验,如果发现校验结果不一致,客户端会通知NameNode,然后再从下一个拥有该block 副本的DataNode 继续读。
12 HDFS 在上传文件的时候,如果其中一个DataNode 突然挂掉了怎么办?
客户端上传文件时与DataNode 建立pipeline管道,管道正向是客户端向DataNode发送的数据包,管道反向是DataNode 向客户端发送ack 确认,也就是正确接收到数据包之后发送一个已确认接收到的应答,当DataNode 突然挂掉了,客户端接收不到这个DataNode 发送的ack 确认,客户端会通知NameNode,NameNode 检查该块的副本与规定的不符,NameNode 会通知DataNode 去复制副本,并将挂掉的DataNode 作下线处理,不再让它参与文件上传与下载。
13 NameNode在启动的时候会做哪些操作
首次启动NameNode。
- 格式化文件系统,为了生成hsimage文件
- 启动NameNode
2.1 读取fsimage文件,将内容加载进内存
2.2 等待DataNode注册与发送block report - 启动DataNode
3.1 向NameNode注册
3.2 发送block report
3.3 检查fsimage 中记录的块的数量和block report 中的块的总数是否相同 - 对文件系统进行操作(创建目录,上传文件,删除文件等)
4.1 此时内存中已经有文件系统改变的信息,但是磁盘中没有文件系统改变的信息,此时会将这些改变信息写入edits 文件中,edits 文件中存储的是文件系统元数据改变的信息。
第二次启动NameNode
- 读取fsimage 和edits 文件
- 将fsimage 和edits 文件合并成新的fsimage 文件
- 创建新的edits 文件,内容为空
- 启动DataNode
14 HDFS的优点
- 高吞吐量访问:HDFS的每个Block分布在不同的Rack上,在用户访问时,HDFS会计算使用最近和访问量最小的服务器给用户提供。由于Block在不同的Rack上都有备份,所以不再是单数据访问,所以速度和效率是非常快的。另外HDFS可以并行从服务器集群中读写,增加了文件读写的访问带宽。
- 高容错性:系统故障是不可避免的,如何做到故障之后的数据恢复和容错处理是至关重要的。HDFS通过多方面保证数据的可靠性,多份复制并且分布到物理位置的不同服务器上,数据校验功能、后台的连续自检数据一致性功能都为高容错提供了可能。
- 线性扩展:因为HDFS的Block信息存放到NameNode上,文件的Block分布到DataNode上,当扩充的时候仅仅添加DataNode数量,系统可以在不停止服务的情况下做扩充,不需要人工干预。
15 HDFS故障检测机制
- 针对节点失败问题,,主要指DataNode失效,HDFS使用了心跳机制,DataNode定期向NameNode发送心跳信息,NameNode根据心跳信息判断DataNode是否存活;
- 针对网络故障而导致无法收发数据的问题,HDFS提供了ACK的机制,在发送端发送数据后,如果没有收到ACK并且经过多次重试后仍然如此,则认为网络故障;
- 针对数据损坏问题,所有DataNode会定期向NameNode发送自身存储的块清单,在传输数据的同时会发送总和校验码,NameNode依次来判断数据是否丢失或损坏。
16 容错机制
读容错
由于在读HDFS的过程中会从NameNode获取到数据块位置列表,如果某个DataNode失效,换个DataNode读即可。
写容错
写HDFS的过程中会对多个DataNode建立管道进行写入,如果数据发送者没有收到其中某个DataNode的ACK,则认为该DataNode失效,会跳过该DataNode并将数据写入剩余DataNode。NameNode收集DataNode信息时发现文件的副本数与设置值不一致,会重新寻找一个DataNode保存副本。
DataNode失效
在NameNode中会持有数据块表和DataNode两张表。数据块表存储着某个数据块(包括副本)所在的DataNode,DataNode表存储着每个DataNode中保存的数据块列表。由于DataNode会周期性地给NameNode发送自己所持有的数据块信息,因此NameNode会持续更新数据块表和DataNode表。如果发现某个DataNode上的数据块错误,NameNode会从数据块表删除该数据块;如果发现某个DataNode失效,NameNode会对两张表进行更新。NameNode还会周期性地扫描数据块表,如果发现数据块表中某个数据库的备份数量低于所设置的备份数,则会协调从其它DataNode复制数据到另一个DataNode上完成备份。