当前位置: 首页 > article >正文

【分布式理论六】分布式调用(4):服务间的远程调用(RPC)

文章目录

    • 一、RPC 调用过程
    • 二、RPC 动态代理:屏蔽远程通讯细节
      • 1. 动态代理示例
      • 2. 如何将动态代理应用于 RPC
    • 三、RPC 序列化
    • 四、RPC 协议编码
      • 1. 协议编码的作用
      • 2. RPC 协议消息组成
    • 五、RPC 网络传输
      • 1. 网络传输流程
      • 2. 关键优化点

一、RPC 调用过程

RPC(Remote Procedure Call,远程过程调用)是一种让不同网络节点上的服务相互调用的技术。它的核心目标是屏蔽远程调用的复杂性,使远程服务的调用方式如同本地调用一样简单。在分布式系统中,RPC 通过封装底层网络通信细节,提高了服务调用的可用性和开发效率。

RPC 调用流程包括:

在这里插入图片描述

  1. 动态代理:客户端通过代理对象调用远程方法。
  2. 序列化:将请求数据转换为二进制格式,便于传输。
  3. 协议编码:增加数据包的协议标识和长度信息。
  4. 网络传输:通过网络传递数据包。
  5. 协议解码:服务端解析请求包。
  6. 反序列化:将二进制数据转换回原始对象。
  7. 执行方法:调用对应的远程方法并处理请求。
  8. 响应返回:按照相同的序列化、网络传输等流程将响应结果返回给调用方。

 

二、RPC 动态代理:屏蔽远程通讯细节

动态代理(Dynamic Proxy)是 Java 提供的一种机制,允许在运行时动态创建代理对象,拦截方法调用,并在调用前后执行额外的逻辑。

在 RPC 场景中,动态代理的主要作用是屏蔽底层的远程通信细节,让客户端可以像调用本地方法一样调用远程服务。

1. 动态代理示例

示例代码:

public interface ServerProvider {
    void sayHello(String str);
}

public class ServerProviderImpl implements ServerProvider {
    @Override
    public void sayHello(String str) {
        System.out.println("Hello " + str);
    }
}

import java.lang.reflect.*;


/**
- `DynamicProxy` 实现了 `InvocationHandler`,用于拦截方法调用并执行代理逻辑。
- `invoke` 方法中:
    1. `method.invoke(realObject, args);` 通过反射调用真实对象的方法。
*/
public class DynamicProxy implements InvocationHandler {
    private Object realObject;
    
    public DynamicProxy(Object object) {
        this.realObject = object;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(realObject, args);
    }
}

public class Client {
    public static void main(String[] args) {
        ServerProvider realServer = new ServerProviderImpl();
        InvocationHandler handler = new DynamicProxy(realServer);
        ServerProvider proxyInstance = (ServerProvider) Proxy.newProxyInstance(
            handler.getClass().getClassLoader(),
            realServer.getClass().getInterfaces(),
            handler);
        proxyInstance.sayHello("world");
    }
}

通过动态代理,客户端不直接依赖于 ServerProviderImpl,而是通过接口和代理类进行调用,这样:

  • 解耦了客户端和服务端,不需要在客户端硬编码调用远程方法。
  • 方便在代理类中加入 RPC 逻辑,比如序列化、网络传输等。
  • 增强扩展性,可以在 invoke 方法中添加日志、权限校验、负载均衡等功能。

 

2. 如何将动态代理应用于 RPC

(1) 在代理类中加入远程调用逻辑
(2) 客户端使用代理调用远程服务

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 1. 构造 RPC 请求
    RpcRequest request = new RpcRequest();
    request.setMethodName(method.getName());
    request.setParameters(args);

    // 2. 发送请求到远程服务
    RpcResponse response = RpcClient.sendRequest(request);

    // 3. 解析响应并返回结果
    return response.getResult();
}


ServerProvider serverProvider = (ServerProvider) Proxy.newProxyInstance(
    getClass().getClassLoader(),
    new Class[]{ServerProvider.class},
    new RpcDynamicProxy("http://remote-server")
);
serverProvider.sayHello("world");

如果想进一步实现 RPC 的完整流程,可以加入序列化、网络传输、反序列化等模块,搭建一个真正的 RPC 组件!

 

三、RPC 序列化

序列化是将对象转换成字节流的过程,而反序列化则是恢复对象的过程。常见的序列化方式包括:

  • JSON:易读易用,但额外空间开销较大。
  • Hessian:二进制格式,序列化后字节数小,性能优于 JSON。
  • Protobuf:高效、跨语言支持,适用于大规模分布式应用。
  • Thrift:Facebook 开源的高效序列化框架,结合了 RPC 服务框架。

 

四、RPC 协议编码

1. 协议编码的作用

有了序列化功能,就可以将客户端的请求对象转化成字节流在网络上传输了,这个字节流转换为二进制信息以后会写入本地的 Socket 中,然后通过网卡发送到服务端。从编程角度来看,每次请求只会发送一个请求包,但是从网络传输的角度来看,网络传输过程中会将二进制包拆分成很多个数据包,这一点也可以从 TCP 传输数据的原理看出。拆分后的多个二进制包会同时发往服务端,服务端接收到这些数据包以后,将它们合并到一起,再进行反序列化以及后面的操作。

实际上,协议编码要做的事情就是对同一次网络请求的数据包进行拆分,并且为拆分得到的每个数据包定义边界、长度等信息。

 

2. RPC 协议消息组成

RPC 协议消息由 消息头消息体 组成:

  • 消息头 包含协议标识、数据长度、请求类型等信息。
  • 消息体 是序列化后的数据。

协议编码的核心目标是确保数据包正确地分片、合并,并提供必要的描述信息,保障网络传输的可靠性。

消息头部分主要存放消息本身的描述信息,如图所示。
在这里插入图片描述

名称描述
魔术位(magic)协议魔术,为解码设计
消息头长度(header size)用来描述消息头长度,为扩展设计
协议版本(version)协议版本,用于版本兼容
消息体序列化类型(st)描述消息体的序列化类型,例如 JSON、gRPC
心跳标记(hb)每次传输都会建立一个长连接,隔一段时间向接收方发送一次心跳请求,保证对方始终在线
单向消息标记(ow)标记是否为单向消息
响应消息标记(rp)用来标记是请求消息还是响应消息
响应消息状态码(status code)标记响应消息状态码
保留字段(reserved)用于填充消息,保证消息的字节是对齐的
消息 Id(message id)用来唯一确定一个消息的标识
消息体长度(body size)描述消息体的长度

 

五、RPC 网络传输

1. 网络传输流程

在 RPC 调用中,服务调用方(Client)需要发送请求给服务提供方(Server),然后等待服务器处理并返回响应数据。在这个过程中,数据在应用程序、操作系统内核、网络传输三个层次之间流动,并涉及多个数据复制操作。

从示意图中可以看出,数据的流转主要分为两部分:

  • 请求发送过程(客户端 -> 服务器)
  • 响应接收过程(服务器 -> 客户端)

在这里插入图片描述

 

对于请求发送流程(Client -> Server),服务调用方(Client)发起 RPC 请求,其数据流动过程如下:

步骤操作数据位置
1应用程序写入数据业务代码执行RPC调用,将数据写入应用缓冲区(User Space)
2数据复制到内核缓冲区操作系统将应用缓冲区的数据复制到内核缓冲区(Kernel Space)
3通过网络发送数据从内核缓冲区被传输到网卡(Network Card),并通过网络协议(如TCP)拆分成数据包发送到远程服务器
4服务器接收数据服务器端网卡接收数据包,并将其存入内核缓冲区
5数据复制到应用缓冲区服务器的内核将数据复制到应用缓冲区
6应用程序读取数据服务器端应用程序从应用缓冲区中获取数据,执行请求逻辑(如数据库查询、业务处理)

响应接收流程(Server -> Client):服务提供方(Server)处理完请求后,将结果返回给客户端,数据流动过程如下:

步骤操作具体内容
7应用程序写入数据服务器应用程序生成响应数据,并写入应用缓冲区
8数据复制到内核缓冲区服务器操作系统将数据从应用缓冲区复制到内核缓冲区,准备发送
9通过网络发送服务器的网卡将数据包发送到客户端
10客户端接收数据客户端网卡接收数据包,操作系统将其存入内核缓冲区
11数据复制到应用缓冲区数据从内核缓冲区复制到应用缓冲区,以便应用程序使用
12应用程序读取数据客户端应用程序从应用缓冲区中获取响应数据,完成 RPC 调用

 

2. 关键优化点

RPC 网络传输过程涉及多个阶段,包括数据在应用缓冲区、内核缓冲区、网络传输中的流转。优化 RPC 传输的关键在于减少数据复制、优化网络通信、使用异步 I/O 机制,提高整体性能。

操作具体内容
减少数据复制采用 零拷贝(Zero-Copy) 技术,如 mmapsendfile,避免数据在用户态和内核态之间频繁复制
优化网络传输使用 长连接(Keep-Alive) 避免频繁建立 TCP 连接;采用 批量发送数据压缩 来减少数据传输的开销
异步 I/O 处理使用 异步 I/O(如 Netty、epoll),避免同步阻塞,提高并发处理能力
优化缓冲区管理采用 池化缓冲区(Buffer Pool) 避免频繁申请和释放内存

 


http://www.kler.cn/a/534912.html

相关文章:

  • HTML学习笔记(6)
  • 手机上运行AI大模型(Deepseek等)
  • unordered_map/set的哈希封装
  • 【实践案例】基于大语言模型的海龟汤游戏
  • 线性调整器——耗能型调整器
  • 排序算法--归并排序
  • 网站服务器如何御防恶意网络爬虫攻击?
  • ALU与寄存器设计与运算优化
  • graylog初体验
  • iOS 音频录制、播放与格式转换
  • Linux常见问题解决方法--2
  • k8s中,一.pod污点,二.pod容器污点容忍策略,三.pod优先级(PriorityClass类)
  • 深度学习 | 表示学习 | 卷积神经网络 | Batch Normalization 在 CNN 中的示例 | 20
  • RFID隧道机:提升生产流水线效率与精准度
  • 【Java报错解决】警告: 源发行版 11 需要目标发行版 11
  • 教育系统软件正版化:信创替换的加速引擎
  • Linux里的容器被OOM killed的两种情况
  • 100.8 AI量化面试题:如何使用自监督学习方法从原始市场数据中挖掘新的alpha因子?
  • 我用Ai学Android Jetpack Compose之CircularProgressIndicator
  • MongoDB学习笔记-解析jsonCommand内容
  • Unix/Linux编程:fcntl函数总结
  • vscode 如何通过Continue引入AI 助手deepseek
  • 国产编辑器EverEdit - 自定义标记使用详解
  • python爬虫--简单登录
  • 无界构建微前端?NO!NO!NO!多系统融合思路!
  • HTML 复习