目录
[2.1 RPC的四个核心组件](#2.1 RPC的四个核心组件)
[2.2 访问流程](#2.2 访问流程)
[3.1 接口定义语言 (IDL - Interface Definition Language)](#3.1 接口定义语言 (IDL - Interface Definition Language))
[3.2 序列化与反序列化 (Serialization & Deserialization - Marshalling/Unmarshalling)](#3.2 序列化与反序列化 (Serialization & Deserialization - Marshalling/Unmarshalling))
[3.3 网络传输 (Transport)](#3.3 网络传输 (Transport))
[3.4 服务发现 (Service Discovery)](#3.4 服务发现 (Service Discovery))
[3.5 容错与重试 (Fault Tolerance & Retry):](#3.5 容错与重试 (Fault Tolerance & Retry):)
[3.6 安全 (Security)](#3.6 安全 (Security))
[3.7 异步与流式 (Asynchronous & Streaming)](#3.7 异步与流式 (Asynchronous & Streaming))
1、概念
背景:分布式系统的组件分散在不同的虚拟机器上,需通过网络进行交互协作以完成共同的任务。
网络远程通信的基本原理:基于传输协议和网络IO来实现。其中传输协议有tcp、udp等,基于socket概念上扩展的协议。网络IO主要有bio、nio、aio三种方式。
RPC概念:远程过程调用的机制,不是一个具体的技术,RPC的核心目标,是让开发者调用远程机器上的函数(或方法)就像调用本地函数一样简单。开发者只关注于业务逻辑,而不是处理底层的Socket连接、数据序列化、反序列化、错误处理等繁琐细节。

2、RPC架构
2.1 RPC的四个核心组件
-
客户端 (Client): 发起远程调用的应用程序。
-
客户端存根 (Client Stub / Proxy): RPC 框架在客户端生成的代码(或动态代理),存放服务端的地址消息,将客户端的请求打包成网络消息(序列化),然后通过网络远程发送给服务方。
-
服务端存根 (Server Stub / Skeleton): RPC 框架在服务端生成的代码(或动态代理),接收客户端发送过来的消息,将消息解包(反序列化),并调用本地的方法。
-
**服务端(Server):**真正的服务提供者。
2.2 访问流程

-
客户端(client)以本地调用方式(即以接口的方式)调用服务;
-
客户端存根(client stub)接收到调用后,负责将方法、参数等组装成能够进行网络传输的消息体(将消息体对象序列化为二进制);
-
客户端通过socket将消息发送到服务端;
-
服务端存根( server stub)收到消息后进行解码(将消息对象反序列化);
-
服务端存根( server stub)根据解码结果调用本地的服务;
-
服务处理
-
本地服务执行并将结果返回给服务端存根( server stub);
-
服务端存根( server stub)将返回结果打包成消息(将结果消息对象序列化);
-
服务端(server)通过socket将消息发送到客户端;
-
客户端存根(client stub)接收到结果消息,并进行解码(将结果消息反序列化);
-
客户端(client)得到最终结果。
RPC的目标就是将黄色部分的步骤封装起来。
java中的RPC框架:常见的有Hessian、gRPC、Dubbo等
3、关键概念
3.1 接口定义语言**(IDL - Interface Definition Language)**
-
核心概念: 一种与具体编程语言无关的语言,用于严格定义服务接口(方法、参数、返回值、数据结构、甚至异常)。
-
作用:
-
跨语言支持: 不同语言的客户端和服务器可以通过 IDL 文件生成各自语言的 Stub 和 Skeleton 代码。
-
明确契约: 作为服务提供者和消费者之间的明确、可共享的合同。
-
代码生成: RPC 框架提供编译器/工具,根据 IDL 文件自动生成客户端代理代码和服务器端骨架代码,减少手写网络通信代码的工作量和错误。
-
-
常见 IDL: Protocol Buffers (
.proto
), Apache Thrift (.thrift
), Avro IDL (.avdl
), CORBA IDL, Web Services Description Language (WSDL - 用于 SOAP)。 -
示例:**Apache Thrift--**Facebook的跨语言RPC框架,支持多种传输协议(TCP/HTTP等)。
bash
// 定义服务 (calculator.thrift)
service Calculator {
i32 add(1:i32 num1, 2:i32 num2),
i32 subtract(1:i32 num1, 2:i32 num2)
}
Java使用步骤:
1> 用Thrift编译器生成代码:
bash
thrift --gen java calculator.thrift
2> 实现接口:
java
public class CalculatorHandler implements Calculator.Iface {
public int add(int num1, int num2) { return num1 + num2; }
public int subtract(int num1, int num2) { return num1 - num2; }
}
3> 启动Thrift服务端,客户端通过Client类调用。
3.2 序列化与反序列化 (Serialization & Deserialization - Marshalling/Unmarshalling)
-
核心概念: 将内存中的数据结构或对象转换为适合网络传输或存储的字节流 的过程称为序列化。将接收到的字节流还原为内存中对象的过程称为反序列化。
-
关键要求:
-
高效性: 编码/解码速度快,产生的数据体积小(减少网络带宽和延迟)。
-
跨语言性: 序列化后的数据能被不同语言编写的程序正确解析(IDL 是基础)。
-
兼容性: 支持数据模式的演进(向前/向后兼容),允许服务接口和数据结构逐步升级而不破坏现有客户端或服务器。
-
安全性: 防止恶意构造的数据导致反序列化漏洞。
-
-
常见技术:
-
二进制协议: Protocol Buffers (Protobuf), Apache Thrift (Binary), Apache Avro, MessagePack, Hessian。通常体积小、速度快。
-
文本协议: JSON, XML。可读性好,但体积较大、解析较慢。常用于 RESTful API 和调试。
-
语言内置: Java Serializable, Python Pickle。通常缺乏跨语言支持和良好的兼容性控制,不推荐用于生产环境 RPC。
-
-
示例: **Apache Thrift (Binary Protocol),**支持多种传输协议(Binary/Compact/JSON)
bash
// person.thrift
struct Person {
1: string name,
2: i32 id,
3: list<string> emails
}
java
// 序列化
Person person = new Person("Alice", 123, Arrays.asList("alice@example.com"));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
TBinaryProtocol protocol = new TBinaryProtocol(new TIOStreamTransport(baos));
person.write(protocol); // 序列化
byte[] bytes = baos.toByteArray();
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
TBinaryProtocol inProto = new TBinaryProtocol(new TIOStreamTransport(bais));
Person decoded = new Person();
decoded.read(inProto); // 反序列化
System.out.println(decoded.getName()); // 输出: Alice
3.3 网络传输 (Transport)
-
核心概念: 负责在客户端 Stub 和服务器 Skeleton 之间可靠地传输序列化后的请求和响应数据。
-
协议选择:
-
TCP: 最常用,提供可靠的、面向连接的字节流传输。是大多数 RPC 框架的基础。
-
HTTP/1.1: 基于 TCP,无状态,文本协议(虽然 Body 可以是二进制)。通用性强,易于通过防火墙和代理。但性能开销相对大,有队头阻塞问题。
-
HTTP/2: 解决了 HTTP/1.1 的队头阻塞问题,支持多路复用、头部压缩、服务器推送等特性,显著提升性能,已成为现代 RPC 框架(如 gRPC)的首选传输层。
-
HTTP/3 (QUIC): 基于 UDP,旨在进一步减少延迟,解决 TCP 队头阻塞问题,提供更好的移动网络体验,是未来趋势。
-
自定义协议: 一些高性能框架(如 Dubbo)可能使用自定义的二进制协议直接在 TCP 上传输。
-
3.4 服务发现 (Service Discovery)
-
核心概念: 客户端如何找到它想调用的服务实例的网络位置(IP + Port)?尤其是在服务实例动态变化(扩缩容、故障重启)的分布式环境中。
-
机制:
-
静态配置: 直接在客户端配置服务端地址列表。简单但缺乏弹性,不适合动态环境。
-
动态发现:
-
注册中心 (Registry): 服务实例启动时向一个中心化的注册中心(如 ZooKeeper, etcd, Consul, Nacos, Eureka)注册自己的元数据(服务名、IP、Port、健康状态等)。客户端通过查询注册中心获取可用服务实例列表。
-
DNS: 通过 DNS SRV 记录或自定义域名解析来发现服务。更新可能不够实时。
-
-
-
负载均衡 (Load Balancing): 通常与服务发现紧密结合。客户端从注册中心获取服务实例列表后,需要选择一个实例来发送请求。策略包括:轮询 (Round Robin)、随机 (Random)、加权轮询/随机、最少连接数 (Least Connections)、一致性哈希 (Consistent Hashing - 保证相同请求路由到相同实例,利于缓存)。
3.5 容错与重试 (Fault Tolerance & Retry):
-
网络是不可靠的,服务实例可能临时故障。RPC 框架通常提供容错机制:
-
**超时 (Timeout):**客户端设置一个等待响应的最大时间。防止因服务器挂起或网络问题导致客户端无限期等待,释放资源。
-
重试 (Retry): 在遇到可重试错误(如网络瞬时故障、目标实例暂时不可用)时自动重试请求。需要谨慎设计(重试次数上限、重试间隔(固定、指数退避 - Exponential Backoff)、可重试错误类型判断),避免造成服务雪崩。
-
熔断 (Circuit Breaking): 当某个服务失败率达到阈值时,暂时停止对其的请求(熔断器打开),直接返回失败或降级结果,给故障服务恢复的时间。过一段时间后尝试放行少量请求(半开状态),如果成功则关闭熔断器。
-
降级 (Fallback): 当调用失败或熔断时,提供一种备选方案(如返回缓存数据、默认值、简化结果)以保证核心功能的可用性。
-
3.6 安全 (Security)
-
认证 (Authentication): 验证调用者的身份(你是谁?)。常用方式:API Key/Secret, OAuth 2.0, JWT (JSON Web Tokens), mTLS (双向 TLS)。
-
授权 (Authorization): 验证调用者是否有权限执行该操作(你能做什么?)。常用方式:基于角色 (RBAC) 或基于属性 (ABAC) 的访问控制。
-
传输加密 (Encryption): 使用 TLS/SSL 对网络传输的数据进行加密,防止窃听和篡改。
3.7 异步与流式 (Asynchronous & Streaming)
-
异步调用: 客户端发起调用后不阻塞等待结果,而是立即返回一个 Future/Promise 对象或注册一个回调函数。当结果准备好时,通过 Future/Promise 获取或调用回调函数通知客户端。提高资源利用率和吞吐量。
-
流式 RPC: 允许在单个连接上建立双向的、持续的数据流。客户端可以发送一个请求流,服务器可以返回一个响应流,或者建立双向流。
-
优势: 非常适合传输大量数据集(如文件上传/下载)、实时消息推送、聊天等场景。
-
技术基础: HTTP/2 或 HTTP/3 的多路复用特性是实现高效流式 RPC 的关键(如 gRPC Stream)。
-
4、RPC优缺点分析
优点:
-
开发效率高: 屏蔽了底层网络复杂性,开发者像调用本地函数一样开发分布式调用,降低心智负担,代码更简洁。
-
抽象清晰: IDL 明确定义了服务接口,强类型约束,减少接口不一致的错误。
-
跨语言能力: 基于 IDL 的代码生成天然支持多语言互操作,方便构建异构系统。
-
性能潜力: 使用高效的二进制序列化协议(如 Protobuf, Thrift)和基于 TCP 的传输(或 HTTP/2),通常比基于文本的 RESTful API(尤其是 JSON over HTTP/1.1)具有更高的性能和更低的延迟。
-
适合内部通信: 在微服务架构内部,服务之间需要频繁、高效、结构化的通信,RPC 是非常自然和高效的选择。
缺点:
-
耦合性增加: 服务接口一旦通过 IDL 发布并生成代码,修改就需要同步更新客户端和服务端代码(虽然 Schema Evolution 可以缓解,但仍有成本)。相比 REST 的松散耦合(通过 URI 和 HTTP 动词),RPC 的接口耦合更紧密。
-
调试复杂性: 跨进程、跨机器的调用使得调试比本地调用更困难。需要分布式追踪工具(如 Jaeger, Zipkin)来跟踪请求链路。
-
技术栈绑定: 客户端和服务端需要支持相同的 RPC 框架和 IDL 生成器。虽然框架本身支持多语言,但选择了一个框架,整个系统通常就绑定在这个框架的生态上。
-
防火墙穿透: RPC 框架通常使用非 HTTP 标准端口或自定义协议,可能在企业防火墙环境中遇到阻碍。基于 HTTP/2 的 RPC(如 gRPC)在这方面有优势。
-
复杂性下沉: 虽然对业务开发者隐藏了网络复杂性,但 RPC 框架本身的实现、部署、监控、治理(服务发现、负载均衡、熔断限流)带来了额外的运维复杂性。需要一个成熟的微服务基础设施来支撑。
RPC vs REST:
特性 | RPC | REST (Representational State Transfer) |
---|---|---|
核心范式 | 动作/过程 (调用远程函数) | 资源 (操作资源的状态) |
接口定义 | 强契约 (IDL),严格定义方法签名 | 弱契约,通过 URI 和 HTTP 动词隐含定义 |
耦合性 | 较高 (客户端依赖特定服务接口) | 较低 (客户端只依赖资源 URI 和 HTTP 语义) |
通信协议 | 自定义协议或基于 HTTP/1.1/HTTP/2 | 几乎总是基于 HTTP/1.1 |
数据格式 | 高效二进制 (Protobuf, Thrift) 或文本 | 文本为主 (JSON, XML) |
性能 | 通常更高 (二进制 + 高效协议) | 通常较低 (文本解析 + HTTP/1.1 开销) |
适用场景 | 内部服务间高效、结构化通信 (微服务) | 对公 API、需要松散耦合、更易调试/理解的场景 |
可发现性 | 弱 (通常需要文档或 IDL) | 强 (利用 HTTP OPTIONS, HATEOAS 理念) |
技术栈绑定 | 强 (需要特定 RPC 框架支持) | 弱 (任何能发 HTTP 请求的语言/工具) |
例子 | gRPC, Thrift, Dubbo 方法调用 | GET /users/123 , POST /orders |
5、常见RPC框架及其特点
-
gRPC (Google): 基于 HTTP/2 和 Protocol Buffers (IDL & 序列化)。高性能,跨语言支持极好,原生支持流式调用。云原生领域事实标准。
-
Apache Dubbo (Alibaba): Java 生态高性能框架。提供丰富的服务治理能力(注册中心、配置中心、监控等),插件化扩展能力强。
-
Apache Thrift (Facebook): 跨语言 RPC 框架,有自己的 IDL 和多种序列化/传输协议选择。相对成熟稳定。
-
JSON-RPC / XML-RPC: 简单、轻量级、基于文本(JSON/XML),易于调试和集成,但性能通常不如二进制协议。
-
.NET Remoting / WCF: 微软 .NET 平台的 RPC 技术(WCF 更强大,支持多种通信协议和模式)。
6、现代趋势
-
云原生集成: 与 Kubernetes、Service Mesh (Istio, Linkerd) 深度集成,利用其服务发现、负载均衡、安全、可观测性能力。
-
Protocol Buffers / gRPC 主导: 因其高效性、强类型契约和良好的跨语言支持,成为新项目的主流选择。
-
异步非阻塞 & 流式优先: 高性能框架普遍采用 NIO/协程模型,并加强流式处理能力。
-
可观测性内置: 指标、日志、追踪成为现代 RPC 框架的标配或易集成项。
-
多协议支持: 一些框架(如 Dubbo)支持同时暴露多种协议(如 Dubbo, gRPC, REST)的端点,方便不同消费者接入。