0%

Hadoop RPC

Hadoop RPC:NameNode 和 DataNode 之间,是怎么”打电话”的?

Hadoop 是分布式系统,组件散落在不同节点上。

  • NameNode 在 hadoop1 上
  • DataNode 在 hadoop2、hadoop3 上
  • ResourceManager 在 hadoop4 上

它们之间要通信——DataNode 要定期给 NameNode 发心跳、汇报块信息;Client 要向 NameNode 查文件位置。

它们怎么通信? 靠 RPC(远程过程调用)。

RPC 做的事:让你调用远程机器上的方法,像调用本地方法一样。

1
2
3
4
5
// 本地调用:直接调
dataNode.sendHeartbeat(blockReport);

// 远程调用:看起来也是直接调,但实际上跨了网络
nameNodeProxy.sendHeartbeat(blockReport); // 这个调用会跑到 NameNode 那台机器上执行

RPC 屏蔽了网络通信的细节——你不用管 Socket、不用管序列化、不用管连接管理,写代码的时候感觉像是在调本地方法。

四层架构概览:RPC 是怎么把本地调用”包装”成网络调用的

Hadoop RPC 采用分层设计,从下到上分为 序列化层函数调用层网络传输层服务器端处理框架,每层专注于特定功能,共同支撑远程调用流程。

graph TD
    A[应用层
远程方法调用] --> B[函数调用层
反射+动态代理] B --> C[序列化层
对象-字节流转换] C --> D[网络传输层
TCP/IP 通信] D --> E[服务器端处理框架
Reactor 事件驱动] note[四层协同 将本地方法调用转为跨节点通信]
层级 解决什么问题 关键技术
序列化层 对象怎么变成字节流传过去 Writable 接口
函数调用层 远程机器怎么知道”调哪个方法” 动态代理 + 反射
网络传输层 字节流怎么送到目标机器 TCP 长连接
服务器处理层 服务器怎么高效处理大量请求 Reactor 模型 + 线程池

逐层拆解:每一层在干什么

1. 序列化层:把对象变成字节流,收回来再还原

Java 有自带的 Serializable,但 Hadoop 没用——太重了,序列化结果里带类名、父类信息、字段签名,体积大、速度慢。

Hadoop 自己搞了一套 Writable 接口:

1
2
3
4
public interface Writable {
void write(DataOutput out) throws IOException; // 对象 → 字节流
void readFields(DataInput in) throws IOException; // 字节流 → 对象
}

一个自定义类型要实现 Writable,手动定义”怎么写出、怎么读回”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class UserInfo implements Writable {
private String name;
private int age;

@Override
public void write(DataOutput out) throws IOException {
Text.writeString(out, name);
out.writeInt(age);
}

@Override
public void readFields(DataInput in) throws IOException {
name = Text.readString(in);
age = in.readInt();
}
}

好处: 只存数据本身,不存类元数据,比 Java 原生序列化快、体积小。

2. 函数调用层:客户端”假装”在调本地方法,其实是远程

客户端拿到的不是真正的实现类,而是一个 动态代理对象

1
2
3
4
5
// 客户端拿到的是代理,不是真实对象
NameNodeProxy proxy = RPC.getProxy(NameNode.class, ...);

// 调用代理的方法
proxy.sendHeartbeat(blockReport);

代理做的事情:

  1. 拦截方法调用 → 拿到方法名、参数类型、参数值
  2. 封装成 RPC 请求对象 → 交给序列化层
  3. 发出去 → 等响应 → 反序列化 → 返回结果

服务器端收到请求后,通过反射找到目标类和方法,执行,返回结果。

3. 网络传输层:字节流怎么送到目标机器

Hadoop RPC 基于 TCP 长连接

  • DataNode 启动时跟 NameNode 建立连接,保持住,不反复握手
  • 每条 RPC 请求走这个连接发过去,响应从同一个连接回来

为什么用长连接? 短连接每次都要三次握手,心跳 3 秒一次,握手开销占比太高。

4. 服务器处理层:高并发请求怎么扛住

NameNode 要同时处理几百上千个 DataNode 的请求,每个请求还要做元数据操作。不能用”每个请求开一个线程”的简单模型——线程数爆炸。

Hadoop RPC 用 Reactor 模型

  • 一个 Reactor 线程 监听网络事件(新连接、数据可读)
  • 数据到了,交给 Handler 线程池 处理(反序列化 → 反射调用 → 序列化响应)

Reactor 模型的好处: 一个线程处理所有 I/O 事件,真正耗时的业务逻辑交给线程池并发执行。既高效,又不阻塞 I/O。

完整调用流程:从 DataNode 到 NameNode 的心跳

以 DataNode 向 NameNode 发心跳为例:

sequenceDiagram
    participant Client (DataNode)
    participant Proxy (动态代理)
    participant Server (NameNode)
    participant Reactor (事件驱动)
    participant Handler (请求处理)

    Client->>Proxy: 调用 sendHeartbeat(参数)
    Proxy->>Proxy: 拦截调用,封装请求信息
    Proxy->>Proxy: 序列化参数为字节流
    Proxy->>Server: 通过 TCP 发送请求字节流
    Server->>Reactor: 监听并接收请求事件
    Reactor->>Handler: 分发请求给线程池处理
    Handler->>Handler: 反序列化字节流为请求对象
    Handler->>Handler: 反射调用 NameNode 的 sendHeartbeat 方法
    Handler->>Handler: 序列化返回结果为字节流
    Server->>Proxy: 通过 TCP 返回响应字节流
    Proxy->>Proxy: 反序列化响应为返回值
    Proxy->>Client: 返回结果给客户端

整个流程,DataNode 的代码感觉只是调了一个方法。 RPC 把网络通信全包了。

Hadoop RPC 解决了什么问题

问题 Hadoop RPC 怎么解决的
分布式节点要通信 封装成 RPC,调用远程像调用本地
通信要高效 Writable 序列化 + TCP 长连接 + Reactor 模型
通信要可靠 基于 TCP + 重试机制
开发者不用管 Socket 动态代理 + 反射,写接口就行

RPC 在 Hadoop 里用在哪

几乎所有的组件间通信都靠 RPC:

  • NameNode ↔ DataNode:心跳、块报告、数据块操作
  • ResourceManager ↔ NodeManager:资源汇报、容器启动
  • Client ↔ NameNode:文件创建、块位置查询
  • ApplicationMaster ↔ ResourceManager:资源申请

可以说,RPC 是 Hadoop 分布式系统的”神经系统”。 没有它,组件就是孤岛。

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