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

文章目录

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

一、RPC 调用过程

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

RPC 调用流程包括:

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

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

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

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

1. 动态代理示例

示例代码:

java 复制代码
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) 客户端使用代理调用远程服务

java 复制代码
@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) 避免频繁申请和释放内存
相关推荐
roman_日积跬步-终至千里3 小时前
【分布式理论五】分布式调用(3):服务注册与发现
分布式
黄名富4 小时前
Kafka 可靠性探究—副本刨析
java·分布式·微服务·zookeeper·kafka
别致的影分身6 小时前
Linux网络 HTTPS 协议原理
网络·网络协议·https
( •̀∀•́ )9207 小时前
RabbitMQ 深度解析与最佳实践
分布式·rabbitmq·ruby
星如雨グッ!(๑•̀ㅂ•́)و✧7 小时前
HTTP异步Client源码解析
网络·网络协议·http
william0820127 小时前
网站打开提示不安全
网络·网络协议·安全·网络安全·https·ssl·软件需求
隔着天花板看星星8 小时前
Flink-WordCount源码解析
大数据·分布式·flink
CDialog9 小时前
8266使用websocket库
网络·websocket·网络协议
记录测试点滴10 小时前
【中间件】 Kafka
分布式·中间件·kafka