本文基于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进程,负责管理它所在节点上的存储。

HDFS-Architecture

优缺点

  • 优点
  1. 高容错性: 数据自动保存多个副本 (文件将被分块,存放于多个节点),若一个副本丢失,可从其他副本中恢复。
  2. 大数据容量: 可支持TB,PB等更高的容量;以及百万规模以上的文件数量。
  3. 强一致性: 流式数据访问,一次写入,多次读取,只支持追加或整体删除。
  4. 兼容性高: 可适用于普通机器,用于副本存储,提高可靠性且节约成本。
  • 缺点
  1. 延时性: 不支持低延时的数据访问 (毫秒级)。
  2. 小文件存储: 无法高效的针对大量小文件存储。
  3. 随机修改: 不支持并发写入以及随机修改。

组成模块

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.txnsdfs.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文件读取

HDFS-Read-Flow

  1. 客户端调用 DistributedFileSystem 实例的 open 方法请求读取文件。
  2. DistributedFileSystem 通过RPC调用 NameNode 以获得该文件对应的数据块所在的 location,包括这个文件的副本信息等元数据 (各块所在DataNode的地址) 。
  3. 打开输入流 FSDataInputStream 之后,客户端调用 read 方法读取数据。 选择最近的 DataNode 建立连接并读取数据。
  4. DataNode 传输数据给客户端(从磁盘里面读取数据输入流,以Packet为单位来做校验),客户端先缓存至本地,最后写入目标文件
  5. 若文件分为多块存储,则按串行读取方式,读取所有块信息。 若节点负载过大,也会由负载均衡从其他 DataNode 中读取。
  6. 客户端调用 close 方法, 关闭输入流 FSDataInputStream

HDFS文件写入

HDFS-Write-Flow

  1. 客户端调用 DistributedFileSystem 的 create 方法请求创建文件。
  2. DistributedFileSystem 通过 RPC 调用 NameNode,NameNode在元数据检查通过后将创建一条记录(在edits log中)。
  3. DistributedFileSystem 返回 FSDataOutputStream 给客户端用于数据流写入,FSDataOutputStream 封装了一个DFSOutputStream 用于客户端与 DataNode,NameNode间的通信。客户端 DFSOutputStream 将文件切分成多个 packets,并在内部以数据队列”Data Queue”的形式管理 packets。
  4. 客户端向 NameNode 申请 Block 和用来存储 replicas 的 DataNode 列表。 DataStreamer 会处理接受”Data Queue”并将数据以流方式写入DataNode。其中第一个 DataNode 将 packet 写入成功后,会传递给在 pipeline 中的下一个 DataNode,直到最后一个。 只要dfs.replication.min的副本 (默认是1) 写入完成,写操作即成功)
  5. 最后一个 DataNode 存储成功之后将返回 ack packet 并通过 pipeline 传递至客户端。 在客户端的内部同样维护的”Ack Queue”在收到 DataNode 返回的 ack packet 后从”Data Queue”中移除相应的 packet。
  6. 客户端调用 close 方法关闭 FSDataOutputStream
  7. 发送完成信号给 NameNode。

Edits与Fsimage合并流程:

NN-and-Secondary-NN

  1. NameNode将更新记录写入一个新的 edit.new 文件。

  2. NameNode通过Http协议将fsimage和editlog发送至Secondary NameNode。

  3. Secondary NameNode将fsimage与editlog合并,生成一个新的 fsimage.ckpt文件。该过程比较耗时,若在NameNode进行,可导致系统卡顿。

  4. Secondary NameNode将生成的 fsimage.ckpt 通过Http协议发送至NameNode。

  5. NameNode 重命名 fsimage.ckpt 为 fsimage,edits.new 为 edit。

API

详情参考以下Class

1
2
3
4
5
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>${hadoop.client.version}</version>
</dependency>
1
2
3
4
5
6
7
8
9
@InterfaceAudience.Public
@InterfaceStability.Stable
public abstract class FileSystem extends Configured implements Closeable {
// ----
}

public class DistributedFileSystem extends FileSystem {
// ----
}

其他

HDFS的可用性问题

在HDFS集群中,NameNode 依然是单点故障 (SPOF: Single Point Of Failure)。元数据同时写到多个文件系统以及Second NameNode定期checkpoint有利于保护数据丢失,但是并不能提高可用性。
这是因为 NameNode是唯一一个对文件元数据和 file-block 映射负责的地方, 当它挂了之后,包括MapReduce在内的作业都无法进行读写。

Hadoop高可用 (HA)方案

采用HA的HDFS集群配置2个NameNode,分别处于ActiveStandby状态。当Active NameNode故障之后,Standby接过责任继续提供服务,用户没有明显的中断感觉,一般耗时在几十秒到数分钟。
HA涉及到的主要实现逻辑有

  1. 主备需共享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。

  1. DataNode 需要同时往主备发送 Block Report
    因为Block映射数据存储在内存中(不在磁盘上),为了在Active NameNode挂掉之后,新的NameNode能够快速启动,不需要等待来自Datanode的Block Report,DataNode需要同时向主备两个NameNode发送Block Report。

  2. 客户端需要配置 failover 模式(失效备援模式,对用户透明)

    Namenode的切换对客户端来说是无感知的,通过客户端库来实现。客户端在配置文件中使用的HDFS URI是逻辑路径,映射到一对Namenode地址。客户端会不断尝试每一个Namenode地址直到成功。

  3. Standby 替代 Secondary NameNode

Medivh's Diary