3. RPC基础原理

在支付订单系统中,RPC(Remote Procedure Call,远程过程调用)是微服务间同步通信的核心手段。理解 RPC 的基础原理,是后续选型与调优的前提。本节从 RPC 模型、序列化、网络传输与动态代理四个维度展开。

3.1 RPC 调用模型

RPC 的核心目标是让开发者像调用本地方法一样调用远程服务。其完整调用链路包含 5 个角色:客户端、客户端 Stub、网络传输层、服务端 Stub、服务端。

关键流程: 客户端调用本地 Stub → Stub 将方法名、参数序列化为二进制 → 通过网络传输到服务端 → 服务端 Stub 反序列化 → 调用实际服务方法 → 结果序列化返回。
RPC 调用模型完整流程 RPC 调用模型完整流程 客户端 客户端 Stub 网络传输 服务端 Stub 服务端 ① 客户端调用本地 Stub → ② Stub 序列化方法名+参数 → ③ 网络传输 → ④ 服务端 Stub 反序列化 → ⑤ 调用实际方法 ⑥ 结果序列化 → ⑦ 网络回传 → ⑧ 客户端 Stub 反序列化 → ⑨ 返回给客户端调用方 ① 调用 ② 序列化 ③ 传输 ④ 反序列化 ⑤ 执行 ⑥ 结果序列化回传(虚线路径)

3.2 序列化与反序列化

序列化是将内存中的对象转换为二进制字节流的过程,反序列化则是逆过程。在支付订单系统中,序列化性能直接影响 TPS(每秒事务处理数)。

3.2.1 常见序列化协议对比

序列化协议 数据格式 空间效率 时间效率 跨语言 典型场景
JSON 文本 低(冗余大) 中 是 HTTP API、调试
Protobuf 二进制 高(压缩比高) 高 是 高性能 RPC(gRPC)
Thrift 二进制 高 高 是 跨语言服务框架
Hessian 二进制 中 中 部分 Java 生态 RPC
Java 原生 二进制 低 低 否 仅限 Java 内部
支付场景建议: 订单核心链路(创建、支付、退款)推荐使用 Protobuf 或 Thrift,以降低序列化延迟和带宽消耗。非核心查询场景可降级为 JSON 以方便调试。

3.2.2 序列化性能指标

  • 序列化时间: 对象 → 字节流的 CPU 耗时,直接影响 RPC 调用延迟。
  • 反序列化时间: 字节流 → 对象的 CPU 耗时,服务端处理瓶颈常在此处。
  • 压缩比: 序列化后字节数 / 原始对象大小,影响网络带宽占用。
  • 兼容性: 字段新增/删除是否影响旧版本客户端,对支付系统灰度发布至关重要。

3.3 网络传输协议:TCP vs HTTP

RPC 框架的底层传输协议通常选择 TCP 或 HTTP。两者在连接模型、性能、穿透性上存在显著差异。

对比维度 TCP 协议 HTTP 协议
连接模型 长连接,连接复用 短连接 / 长连接(HTTP/1.1 Keep-Alive)
传输效率 高,无冗余头部 低,HTTP 头部开销大
穿透性 差,防火墙可能拦截 好,默认 80/443 端口
协议复杂度 需自定义协议编解码 标准协议,生态丰富
典型 RPC 框架 Dubbo(默认)、Thrift gRPC(HTTP/2)、Spring Cloud(HTTP)
选型注意: 支付订单系统内部服务间调用(高并发、低延迟)优先选择 TCP 长连接 协议;对外暴露的 API 网关或跨网络调用,建议使用 HTTP/2 以兼顾穿透性与性能。

3.4 动态代理

动态代理是 RPC 框架实现“透明远程调用”的关键技术。客户端通过动态代理生成接口的代理对象,代理对象内部封装了序列化、网络传输、反序列化等细节,对调用方完全透明。

3.4.1 动态代理实现方式

  • JDK 动态代理: 基于接口,使用 java.lang.reflect.Proxy 和 InvocationHandler。仅支持接口代理。
  • CGLIB 动态代理: 基于类继承,使用字节码增强技术。可代理普通类,性能略高于 JDK 代理。
  • Javassist: 直接操作字节码,性能极高,常用于框架底层。

3.4.2 动态代理在 RPC 中的工作流程

// 伪代码:RPC 客户端动态代理核心逻辑
public class RpcProxyInvocationHandler implements InvocationHandler {
    private final Class<?> interfaceClass;
    private final String serviceAddress;

    public RpcProxyInvocationHandler(Class<?> interfaceClass, String serviceAddress) {
        this.interfaceClass = interfaceClass;
        this.serviceAddress = serviceAddress;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1. 构建 RPC 请求对象(方法名、参数类型、参数值)
        RpcRequest request = new RpcRequest();
        request.setInterfaceName(interfaceClass.getName());
        request.setMethodName(method.getName());
        request.setParameterTypes(method.getParameterTypes());
        request.setParameters(args);

        // 2. 序列化请求对象(例如使用 Protobuf)
        byte[] requestBytes = Serializer.serialize(request);

        // 3. 通过 TCP/HTTP 连接发送到服务端
        byte[] responseBytes = Transport.send(serviceAddress, requestBytes);

        // 4. 反序列化响应对象
        RpcResponse response = Serializer.deserialize(responseBytes, RpcResponse.class);

        // 5. 返回结果或抛出异常
        if (response.getException() != null) {
            throw response.getException();
        }
        return response.getResult();
    }
}

// 客户端使用示例
OrderService orderService = RpcProxyFactory.create(OrderService.class, "192.168.1.100:20880");
Order order = orderService.getOrderById("20250315001"); // 透明远程调用
动态代理在 RPC 中的角色 动态代理在 RPC 中的角色 调用方 动态代理 (InvocationHandler) 序列化/反序列化 网络传输 服务端 调用方只看到接口(如 OrderService),动态代理拦截所有方法调用 代理内部自动完成:构建请求 → 序列化 → 网络发送 → 反序列化 → 返回结果 调用方无需感知远程通信细节,实现“透明 RPC” 核心价值:将远程调用伪装成本地调用,降低开发复杂度

3.5 小结