0%

HDFS工作流程

HDFS读写流程详解:从客户端到集群的完整路径

HDFS 的读写流程是分布式存储系统的核心机制,涉及客户端、NameNode 和 DataNode 三者的协同工作。本文将通过时序图和代码示例,深入解析 HDFS 的写入和读取流程,帮助理解数据在集群中的流动路径和关键技术点。

HDFS 写入流程详解

HDFS 的写入流程采用 流水线复制(Pipeline Replication) 机制,确保数据高效且可靠地存储到多个 DataNode 上。

1. 写入流程时序图

sequenceDiagram  
    participant Client  
    participant NN[NameNode]  
    participant DN1[DataNode 1]  
    participant DN2[DataNode 2]  
    participant DN3[DataNode 3]  

    Client->>NN: 创建文件请求(/user/data.txt)  
    NN->>Client: 确认创建,返回文件句柄  
    Client->>NN: 请求分配第一个数据块  
    NN->>Client: 分配块 ID(blk_123),返回 DataNode 列表(DN1→DN2→DN3)  
    Client->>DN1: 建立写入流水线(blk_123)  
    DN1->>DN2: 建立连接  
    DN2->>DN3: 建立连接  
    Client->>DN1: 流式写入数据(64KB 数据包)  
    DN1->>DN2: 转发数据  
    DN2->>DN3: 转发数据  
    DN3->>DN2: 确认接收  
    DN2->>DN1: 确认接收  
    DN1->>Client: 确认接收  
    loop 直至所有数据写入  
        Client->>DN1: 发送下一个数据包  
        DN1->>DN2: 转发  
        DN2->>DN3: 转发  
        DN3->>DN2: 确认  
        DN2->>DN1: 确认  
        DN1->>Client: 确认  
    end  
    Client->>NN: 关闭文件请求  
    NN->>Client: 确认关闭,持久化元数据

关键步骤解析

(1)客户端与 NameNode 交互
  • 创建文件:客户端向 NameNode 发送创建文件请求,NameNode 验证权限并在元数据中创建文件节点(不分配块);
  • 分配块:客户端写入数据时,NameNode 动态分配新的数据块,并返回可用的 DataNode 列表(按机架感知策略选择)。
(2)流水线复制机制
  • 建立流水线:客户端连接主 DataNode(如 DN1),DN1 依次连接后续 DataNode(DN2→DN3)形成写入流水线;
  • 数据传输:客户端将数据流式发送给 DN1,DN1 边接收边转发给 DN2,DN2 再转发给 DN3,形成 顺序写入
  • 确认机制:数据在流水线中反向确认(DN3→DN2→DN1→Client),确保所有副本写入成功。
(3)元数据持久化
  • 文件关闭:客户端完成写入后,通知 NameNode 关闭文件;
  • 元数据更新:NameNode 将文件的块信息(块 ID、位置)持久化到 FsImage 和 Edits。

代码示例:HDFS 文件写入

以下是使用 Java API 写入 HDFS 文件的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.apache.hadoop.conf.Configuration;  
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;

public class HDFSWriter {
public static void main(String[] args) throws Exception {
// 配置 HDFS 连接
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://namenode:8020");
FileSystem fs = FileSystem.get(conf);

// 创建文件并获取输出流
Path filePath = new Path("/user/data.txt");
org.apache.hadoop.fs.FSDataOutputStream out = fs.create(filePath);

// 写入数据
String content = "Hello, HDFS!";
out.write(content.getBytes());

// 关闭流,触发元数据持久化
out.close();
fs.close();
}
}

HDFS 读取流程详解

HDFS 的读取流程采用 就近原则(Data Locality),优先从离客户端最近的节点读取数据,减少网络开销。

1. 读取流程时序图

sequenceDiagram  
    participant Client  
    participant NN[NameNode]  
    participant DN1[DataNode 1]  
    participant DN2[DataNode 2]  
    participant DN3[DataNode 3]  

    Client->>NN: 请求文件元数据(/user/data.txt)  
    NN->>Client: 返回文件块信息(blk_123→DN1, blk_124→DN2)  
    Client->>DN1: 读取块 blk_123  
    DN1->>Client: 返回数据  
    Client->>DN2: 读取块 blk_124  
    DN2->>Client: 返回数据  
    loop 直至所有块读取完成  
        Client->>DataNode: 请求下一个块  
        DataNode->>Client: 返回数据  
    end  
    Client->>NN: 关闭文件(可选)

关键步骤解析

(1)元数据获取
  • 文件定位:客户端向 NameNode 请求文件元数据(如块列表、块位置);
  • 就近选择:NameNode 返回块的位置信息,客户端优先选择网络距离最近的 DataNode(如同一机架内的节点)。
(2)数据读取优化
  • 并行读取:客户端可同时连接多个 DataNode 并行读取不同块,提升吞吐量;
  • 本地优先:若客户端与 DataNode 位于同一节点(如 MapReduce 任务),直接读取本地磁盘,避免网络传输;
  • 校验和验证:客户端读取数据时,自动验证块的校验和,确保数据完整性。
(3)故障处理
  • 副本选择:若主副本不可用,自动选择其他副本节点;
  • 重试机制:读取失败时,客户端会重试其他副本节点(最多 3 次)。

代码示例:HDFS 文件读取

以下是使用 Java API 读取 HDFS 文件的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import org.apache.hadoop.conf.Configuration;  
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.FSDataInputStream;

public class HDFSReader {
public static void main(String[] args) throws Exception {
// 配置 HDFS 连接
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "hdfs://namenode:8020");
FileSystem fs = FileSystem.get(conf);

// 打开文件并获取输入流
Path filePath = new Path("/user/data.txt");
FSDataInputStream in = fs.open(filePath);

// 读取数据
byte[] buffer = new byte[1024];
int bytesRead = in.read(buffer);
while (bytesRead > 0) {
System.out.write(buffer, 0, bytesRead);
bytesRead = in.read(buffer);
}

// 关闭流
in.close();
fs.close();
}
}

读写流程的关键特性

1. 机架感知(Rack Awareness)

  • 原理:NameNode 根据 DataNode 的机架位置分配副本,默认策略为:
    • 第一个副本:优先本地节点,若无则随机选择;
    • 第二个副本:不同机架的随机节点;
    • 第三个副本:同第二个副本机架的不同节点;
  • 优势:跨机架副本提升可靠性,同机架读取减少网络开销。

2. 数据完整性保障

  • 写入时:客户端计算校验和并存储在 DataNode;
  • 读取时:客户端自动验证校验和,发现不一致时请求其他副本;
  • 后台扫描:DataNode 定期扫描本地块,验证校验和并报告损坏块。

3. 安全模式保护

  • 启动阶段:NameNode 启动后进入安全模式,仅读取元数据,不接受写入请求;
  • 条件检查:当可用块比例达到阈值(默认 99.9%)且超过最小副本数时,退出安全模式。

常见问题与优化

1. 写入性能瓶颈

  • 原因:网络带宽不足、DataNode 磁盘 I/O 饱和;
  • 优化
    • 增加 DataNode 节点或提升网络带宽;
    • 配置多磁盘存储(dfs.datanode.data.dir),分散 I/O 压力;
    • 调整块大小(dfs.blocksize),减少块数量(如 256MB 或 512MB)。

2. 读取延迟高

  • 原因:热点数据集中在少数节点、网络拥塞;
  • 优化
    • 启用数据预取(dfs.client.read.shortcircuit),绕过 DataNode 直接访问本地磁盘;
    • 配置数据均衡器(hdfs balancer),避免数据倾斜;
    • 使用缓存机制(如 Alluxio)加速热点数据访问。

3. 副本不一致

  • 现象hdfs fsck 检查发现块副本数不足;
  • 解决
    • 检查故障 DataNode 并修复;
    • 手动触发块复制(hdfs fsck / -delete 删除损坏块,NameNode 会自动补充)。

欢迎关注我的其它发布渠道