Skip to content

1. 客户端发起调用

1.1 定义服务接口

  • 客户端和服务端约定统一的接口(如 Java Interface),接口定义了方法名、参数和返回值类型。

1.2 客户端代理(Stub)

  • 动态代理:客户端通过动态代理(如 JDK Proxy、CGLib)生成接口的代理对象。
  • 封装请求:调用代理对象的方法时,代理会将方法名、参数等信息封装为请求对象(Request)。

1.3 服务发现与负载均衡

  • 客户端通过服务注册中心(如 ZooKeeper、Nacos、Consul)获取服务端的地址列表。
  • 根据负载均衡策略(如轮询、随机、一致性哈希)选择一个目标服务节点。

2. 序列化与编码

2.1 序列化

  • 请求对象 → 二进制数据:将请求对象(方法名、参数等)转换为字节流,以便网络传输。

  • 常见序列化协议

    • JSON/XML:可读性好,性能较低。
    • Protobuf(Google)、Thrift(Facebook)、Hessian:二进制协议,性能高。
    • AvroKryo:支持 Schema 定义,适合复杂数据结构。

2.2 协议编码

  • 将序列化后的数据按通信协议封装为网络报文(如添加 Header 描述数据长度、版本、校验码等)。

  • 示例协议格式

    plaintext
    plaintext
    
    复制代码| Header(Magic Number + 版本号 + 数据长度) | Body(序列化后的请求数据) |

3. 网络传输(Socket 通信)

3.1 建立连接

  • 客户端通过 TCPHTTP/2(如 gRPC)与服务端建立 Socket 连接。
  • 连接池管理:为减少频繁建立连接的开销,通常使用连接池复用 TCP 连接。

3.2 发送请求

  • 客户端通过 Socket 将编码后的二进制数据发送到服务端。

  • 可靠性保障

    • 超时重试:设置超时时间,超时后重试或降级处理。
    • ACK 确认:部分协议要求服务端返回 ACK 确认数据接收。

4. 服务端处理请求

4.1 接收请求

  • 服务端通过 Socket Server 监听端口,接收客户端请求。

  • IO 模型

    • BIO:阻塞式,每个连接占用一个线程(性能低)。
    • NIO:非阻塞,基于事件驱动(如 Netty 框架)。
    • AIO:异步 IO(较少使用)。

4.2 解码与反序列化

  • 协议解析:从报文中提取 Header 和 Body,验证数据完整性(如校验码)。
  • 反序列化:将 Body 的二进制数据还原为服务端可理解的请求对象

4.3 路由与方法调用

  • 根据请求中的接口名和方法名,通过反射预生成代理调用服务端的具体实现类。

  • 示例代码

    java
    java
    
    复制代码Method method = service.getClass().getMethod(request.getMethodName(), paramTypes);
    Object result = method.invoke(service, request.getArgs());

4.4 执行业务逻辑

  • 服务端执行实际业务逻辑(如数据库操作、计算等)。

5. 返回响应

5.1 序列化响应

  • 将执行结果(或异常信息)序列化为二进制数据。

5.2 编码与发送

  • 按协议封装响应数据,通过 Socket 返回给客户端。

6. 客户端处理响应

6.1 接收响应

  • 客户端 Socket 接收服务端返回的二进制数据。

6.2 反序列化与解码

  • 解析协议,反序列化响应数据为客户端可理解的响应对象

6.3 返回结果

  • 动态代理将响应对象转换为接口定义的返回值类型,返回给客户端调用方。

7. 关键问题与优化

7.1 序列化优化

  • 压缩算法:对大数据使用 Snappy、GZIP 压缩。
  • 兼容性:通过版本号或 Schema 演进(如 Protobuf 的字段可选性)支持接口升级。

7.2 网络优化

  • 长连接:复用 TCP 连接减少握手开销。
  • 零拷贝:使用 Netty 的 ByteBuf 或 Linux 的 sendfile 减少内存复制。

7.3 容错与高可用

  • 熔断降级:通过 Hystrix 或 Sentinel 防止服务雪崩。
  • 重试机制:对可重试的异常(如网络超时)自动重试。

7.4 异步调用

  • 客户端通过 FutureCompletableFuture 实现异步非阻塞调用。

完整流程图

客户端调用 → 动态代理封装请求 → 序列化 → 协议编码 → Socket 发送 → 网络传输 → 
服务端接收 → 协议解码 → 反序列化 → 反射调用 → 执行业务 → 序列化响应 → 
协议编码 → Socket 返回 → 客户端接收 → 反序列化 → 返回结果

示例:基于 Netty 的 RPC 框架

  1. 客户端:使用 Netty 的 Channel 发送请求,通过 Future 异步等待响应。
  2. 服务端:基于 Netty 的 EventLoopGroup 处理请求,利用线程池执行业务逻辑。
  3. 协议设计:自定义协议头(如 length + requestId + serializationType)。

常见 RPC 框架

  • gRPC:基于 HTTP/2 和 Protobuf,支持多语言。
  • Dubbo:阿里开源的 Java RPC 框架,集成服务治理功能。
  • Thrift:Facebook 开源,支持多种序列化协议和传输层。
  • Spring Cloud Feign:基于 HTTP 的声明式 RPC 客户端。

总结

RPC 的核心是通过网络透明地调用远程服务,其实现依赖序列化、网络通信、动态代理和服务治理等关键技术。优化方向包括性能(序列化效率、网络模型)、可靠性(重试、熔断)和扩展性(服务发现、负载均衡)。

文章来源于自己总结和网络转载,内容如有任何问题,请大佬斧正!联系我