本文基于Hadoop 3.X
简介
Hadoop Distributed File System (HDFS) , 参考GFS论文 ^1
是一种具备高度容错性和高吞吐量的分布式文件系统,可部署在多数普通服务器上。HDFS 适合一次写入,多次读取且无需进行文件随机修改的场景。通常用作大数据分析,不适用于低延时的数据访问。
系统架构
HDFS 采用 Master / Slave 的架构来存储数据,主要由四个部分组成,分别为 HDFS Client、NameNode、Secondary NameNode 以及 DataNode。
HDFS集群是由一个NameNode (可支持1个StandBy) 和多个DataNode组成的。NameNode是一个中心服务,负责管理文件系统的命名空间 (Namespace,MetaData等) 及客户端对文件的访问。
集群中一般由一个独立节点运行一个DataNode进程,负责管理它所在节点上的存储。
优缺点
- 优点
- 高容错性: 数据自动保存多个副本 (文件将被分块,存放于多个节点),若一个副本丢失,可从其他副本中恢复。
- 大数据容量: 可支持TB,PB等更高的容量;以及百万规模以上的文件数量。
- 强一致性: 流式数据访问,一次写入,多次读取,只支持追加或整体删除。
- 兼容性高: 可适用于普通机器,用于副本存储,提高可靠性且节约成本。
- 缺点
- 延时性: 不支持低延时的数据访问 (毫秒级)。
- 小文件存储: 无法高效的针对大量小文件存储。
- 随机修改: 不支持并发写入以及随机修改。
组成模块
NameNode
NameNode是 HDFS 架构中的 Master 节点,为中心服务器,负责管理整个分布式文件系统的命名空间和客户端对文件的访问。 NameNode并不保存文件的内容,只保存文件的元数据(文件名称,所在目录,权限,拥有者,文件块数量,副本数量,文件块所在节点)。
NameNode全权管理数据块的复制,周期性的从集群中的每个Datanode接收心跳信号和块状态报告。
心跳信号: 意味着该Datanode节点工作正常
块状态报告: 包含了一个该Datanode上所有数据块的列表
Secondary NameNode
Secondary NameNode 辅助 NameNode 工作(非NameNode热备份)。主要协助将fsimage, editor log合并成 fsimage 并推送NameNode。
- 辅助 NameNode,分担部分工作;在紧急情况下,可辅助恢复 NameNode。
- 定期合并 fsimage 和 edits 日志,将edits日志文件大小控制在一定限度;将新的 fsimage 推送给 NameNode。
fsimage: HDFS元数据的一个永久性检查点。包含整个文件系统所有目录和inode的序列化信息。
edits: 存放HDFS文件系统所有更新操作记录。
checkpoint: 在一定周期(默认3600s)或者操作次数达到最大限制(默认100万)。可通过dfs.namenode.checkpoint.txns
和 dfs.namenode.checkpoint.check.period
设置。
DataNode
DataNode是 HDFS 架构中的 Slaver 节点,负责接收NameNode命令以及实际文件的存储和操作。
- 向NameNode注册,定期上报状态等信息。
- 数据读取,写入,删除等操作。
HDFS Client
HDFS Client主要负责远端对HDFS文件的操作交互。
- 文件上传 HDFS 时,Client 将原始文件切分成多个Block,然后进行存储。
- 与NameNode交互,获取文件的元信息。
- 与DataNode交互,读取或写入数据。
- 支持HDFS管理或其他操作。
Block
HDFS中的文件在物理磁盘上以块(Block)存储。HDFS的块比磁盘的块大,主要是为了最小化寻址开销。如果块设置得足够大,从磁盘传输数据的时间会明显大于定位这个块开始位置所需的时间。因而,传输一个由多个块组成的文件的时间取决于磁盘传输速率。
HDFS块大小设置主要取决于磁盘读写速率。 通常128M 或 256M。可根据 dfs.blocksize
设置
HDFS Block不可设置过小或者过大。设置过小会增加寻址时间以及NameNode元信息数据量。设置过大则会导致磁盘传输数据时间增高。
工作流程
HDFS文件读取
- 客户端调用 DistributedFileSystem 实例的 open 方法请求读取文件。
- DistributedFileSystem 通过RPC调用 NameNode 以获得该文件对应的数据块所在的 location,包括这个文件的副本信息等元数据 (各块所在DataNode的地址) 。
- 打开输入流 FSDataInputStream 之后,客户端调用 read 方法读取数据。 选择最近的 DataNode 建立连接并读取数据。
- DataNode 传输数据给客户端(从磁盘里面读取数据输入流,以Packet为单位来做校验),客户端先缓存至本地,最后写入目标文件
- 若文件分为多块存储,则按串行读取方式,读取所有块信息。 若节点负载过大,也会由负载均衡从其他 DataNode 中读取。
- 客户端调用 close 方法, 关闭输入流 FSDataInputStream。
HDFS文件写入
- 客户端调用 DistributedFileSystem 的 create 方法请求创建文件。
- DistributedFileSystem 通过 RPC 调用 NameNode,NameNode在元数据检查通过后将创建一条记录(在edits log中)。
- DistributedFileSystem 返回 FSDataOutputStream 给客户端用于数据流写入,FSDataOutputStream 封装了一个DFSOutputStream 用于客户端与 DataNode,NameNode间的通信。客户端 DFSOutputStream 将文件切分成多个 packets,并在内部以数据队列”Data Queue”的形式管理 packets。
- 客户端向 NameNode 申请 Block 和用来存储 replicas 的 DataNode 列表。 DataStreamer 会处理接受”Data Queue”并将数据以流方式写入DataNode。其中第一个 DataNode 将 packet 写入成功后,会传递给在 pipeline 中的下一个 DataNode,直到最后一个。 只要dfs.replication.min的副本 (默认是1) 写入完成,写操作即成功)
- 最后一个 DataNode 存储成功之后将返回 ack packet 并通过 pipeline 传递至客户端。 在客户端的内部同样维护的”Ack Queue”在收到 DataNode 返回的 ack packet 后从”Data Queue”中移除相应的 packet。
- 客户端调用 close 方法关闭 FSDataOutputStream。
- 发送完成信号给 NameNode。
Edits与Fsimage合并流程:
NameNode将更新记录写入一个新的 edit.new 文件。
NameNode通过Http协议将fsimage和editlog发送至Secondary NameNode。
Secondary NameNode将fsimage与editlog合并,生成一个新的 fsimage.ckpt文件。该过程比较耗时,若在NameNode进行,可导致系统卡顿。
Secondary NameNode将生成的 fsimage.ckpt 通过Http协议发送至NameNode。
NameNode 重命名 fsimage.ckpt 为 fsimage,edits.new 为 edit。
API
详情参考以下Class
1 | <dependency> |
1 | .Public |
其他
HDFS的可用性问题
在HDFS集群中,NameNode 依然是单点故障 (SPOF: Single Point Of Failure)。元数据同时写到多个文件系统以及Second NameNode定期checkpoint有利于保护数据丢失,但是并不能提高可用性。
这是因为 NameNode是唯一一个对文件元数据和 file-block 映射负责的地方, 当它挂了之后,包括MapReduce在内的作业都无法进行读写。
Hadoop高可用 (HA)方案
采用HA的HDFS集群配置2个NameNode,分别处于Active和Standby状态。当Active NameNode故障之后,Standby接过责任继续提供服务,用户没有明显的中断感觉,一般耗时在几十秒到数分钟。
HA涉及到的主要实现逻辑有
主备需共享Edits Log存储
主NameNode和待命的NameNode共享一份edits log,当主备切换时,Standby通过回放edits log同步数据。 共享存储通常有2种选择:NFS:传统的网络文件系统
QJM:Quorum Journal Manager
QJM 是专门为HDFS的HA实现而设计的,用来提供高可用的edits log。QJM运行一组journal node,edits log必须写到大部分的 Journal Nodes。通常使用3个节点,因此允许一个节点失败,类似ZooKeeper。注意QJM没有使用ZK,虽然HDFS HA的确使用了ZK来选举主Namenode,但一般推荐使用QJM。
DataNode 需要同时往主备发送 Block Report
因为Block映射数据存储在内存中(不在磁盘上),为了在Active NameNode挂掉之后,新的NameNode能够快速启动,不需要等待来自Datanode的Block Report,DataNode需要同时向主备两个NameNode发送Block Report。客户端需要配置 failover 模式(失效备援模式,对用户透明)
Namenode的切换对客户端来说是无感知的,通过客户端库来实现。客户端在配置文件中使用的HDFS URI是逻辑路径,映射到一对Namenode地址。客户端会不断尝试每一个Namenode地址直到成功。
Standby 替代 Secondary NameNode