0%

HDFS工作流程

HDFS 读写流程:客户端、NameNode、DataNode 之间怎么配合

HDFS 的读写流程,本质上是三个角色配合完成一件事:

  • 客户端:我要读/写数据
  • NameNode:告诉我”数据在哪”或”数据该存哪”
  • DataNode:真正干活的——存数据或给数据

整个流程的核心逻辑是:NameNode 管”位置”,DataNode 管”数据”,客户端不经过 NameNode 直接跟 DataNode 传数据。

写入流程:数据怎么进 HDFS

写入的核心机制叫 流水线复制(Pipeline Replication)——客户端把数据发给第一个 DataNode,第一个转发给第二个,第二个转发给第三个,像流水线一样。

写入流程时序图

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: 确认关闭,持久化元数据
分步拆解:

第一步:客户端问 NameNode——“我要写文件”

客户端发送创建文件请求,NameNode 检查权限和路径合法性,在元数据中创建文件节点。此时不分配块,只是占个位置。

第二步:客户端申请块——NameNode 分配位置

客户端开始写数据时,向 NameNode 申请一个新的数据块。NameNode 按机架感知策略选出一组 DataNode(默认 3 个),返回给客户端。

第三步:建立流水线——客户端连 DN1,DN1 连 DN2,DN2 连 DN3

客户端收到 DataNode 列表后,依次建立连接形成一条链。这保证了数据按固定顺序流动。

第四步:数据流式写入——客户端 → DN1 → DN2 → DN3

数据以 64KB 数据包的形式从客户端流向 DN1,DN1 边收边转给 DN2,DN2 边收边转给 DN3。每个数据包发完后,确认信息从 DN3 反向传回客户端。

第五步:关闭文件——NameNode 记下块的位置

客户端写完所有数据后,通知 NameNode 关闭文件。NameNode 把块的 ID 和位置信息持久化到 Edits。

关键设计点:

  • 数据流和确认流分开:数据正向流,确认反向流,提高吞吐
  • NameNode 不参与数据传输:只负责分配位置,避免成为瓶颈
  • 管道中任何节点失败:客户端会关闭当前管道,用剩下的节点重建管道继续写

代码示例: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 出来

读取的核心原则是 就近读取(Data Locality)——哪个 DataNode 离客户端最近就读哪个。

读取流程时序图

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: 关闭文件(可选)
分步拆解:

第一步:客户端问 NameNode——“我要读文件”

客户端发送读取请求,NameNode 返回文件的块列表,每个块包含块 ID 和存储该块的 DataNode 地址列表。

第二步:客户端选 DataNode——“挑最近的那个”

客户端拿到块位置列表后,按”就近原则”选择 DataNode:

  • 同一节点优先(如果是本地任务)
  • 同一机架次之
  • 不同机架最后

第三步:客户端直连 DataNode 读数据

客户端选好 DataNode 后,直接建立连接读取数据,不走 NameNode。读取时自动验证校验和,确保数据完整。

第四步:多块并行读取

如果文件有多个块,客户端可以同时连接多个 DataNode 并行读取,提升吞吐。

关键设计点:

  • NameNode 只给位置,不传数据:数据流不经过 NameNode
  • 就近读取:减少网络传输,MapReduce 任务跑在数据所在的节点上
  • 读取失败自动切换副本:一个 DataNode 读不了,自动换另一个副本

代码示例: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. NameNode 不传数据,只传”在哪”

读写过程中,NameNode 只负责返回块的位置信息,实际数据传输都在客户端和 DataNode 之间直接进行。这是 HDFS 高吞吐的核心。

2. 机架感知决定副本分布和读取优先级

  • 写入时:第一个副本放本地机架,第二、三个放不同机架
  • 读取时:优先读同机架的数据,减少跨机架网络开销

3. 校验和保护数据完整性

  • 写入时:客户端计算校验和,存为 .meta 文件
  • 读取时:客户端验证校验和,不一致则换副本

几个常见问题

1. 写文件时,管道中的一个 DataNode 挂了怎么办?

客户端收不到确认,关闭当前管道,用剩下的节点重建管道继续写。NameNode 之后会补副本。

2. 读文件时,某个 DataNode 读不了怎么办?

客户端自动换到其他副本节点重试(最多 3 次)。如果所有副本都读不了,报错。

3. 写入过程中客户端挂了怎么办?

NameNode 不会收到关闭文件请求,文件处于”未完成”状态。NameNode 有后台线程定期清理未完成的文件(根据 fs.trash.intervaldfs.namenode.replication.work.multiplier.per.iteration 等配置)。

4. 为什么 NameNode 不参与数据读写?

让 NameNode 只管元数据,数据读写走 DataNode。如果 NameNode 也走数据,读写的网络压力会压垮它。

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