RPC
RPC
RPC(Remote Procedure Call,远程过程调用)是一种通信抽象:调用方可以像调用本地函数一样,请求另一个进程、服务或主机执行某个方法,并获得返回值。
这里的“远程”不一定指另一台机器。只要调用方和实现方不在同一个执行上下文中,就可能使用 RPC;同一台 Windows 主机上的两个进程之间,也可以通过 RPC 通信。
调用方
↓ 调用本地代理
客户端代理(Proxy / Stub)
↓ 参数编码、网络或本地传输
RPC 运行时
↓
服务端 RPC 运行时
↓ 参数解码、方法分发
服务实现
↓ 返回结果或错误
调用方获得结果
RPC 的核心不是某一种固定协议,而是“以方法调用为中心”的通信方式。gRPC、Apache Thrift、Dubbo、Windows RPC 等方案都可以实现 RPC,但它们的接口描述、序列化格式、传输协议和治理能力并不相同。
RPC 的基本组成
接口契约
接口契约规定了:
- 服务有哪些方法。
- 每个方法接收什么参数。
- 返回什么结果。
- 错误如何表达。
- 哪些字段允许新增、删除或保持兼容。
成熟的 RPC 框架通常使用 IDL(Interface Definition Language,接口定义语言)描述契约,然后自动生成客户端和服务端代码。
客户端代理与服务端存根
- 客户端代理(Proxy):对调用方隐藏序列化、连接管理和网络传输细节。
- 服务端存根(Stub):接收请求、解码参数,并把请求分发给真正的业务实现。
- RPC 运行时:负责连接复用、请求 ID、超时、取消、错误传递和传输层交互。
调用方写下的可能只是:
user = userClient.GetUser(id)
但实际过程中还会发生参数编码、服务发现、负载均衡、连接建立或复用、服务端解码和结果编码等操作。
序列化与反序列化
参数和返回值必须转换成可以跨进程、跨主机传输的字节序列,这个过程称为序列化;接收端再将字节恢复成对象或结构体,称为反序列化。
常见格式包括:
- Protocol Buffers:体积小、速度快、字段类型明确,常与 gRPC 一起使用。
- JSON:可读性好、调试方便,但通常比二进制格式占用更多空间。
- Thrift Binary Protocol:Apache Thrift 常用的二进制协议。
- 自定义二进制协议:可以针对业务优化,但维护成本和兼容性风险更高。
RPC、API、IPC 与消息队列
这些概念经常同时出现,但关注点不同:
| 概念 | 关注点 | 常见场景 |
|---|---|---|
| RPC | 像调用方法一样调用远端能力 | 微服务之间的同步调用、系统组件调用 |
| API | 对外暴露的功能或数据接口 | HTTP REST API、开放平台接口 |
| IPC | 同一主机上的进程间通信 | 命名管道、共享内存、Unix Socket、Windows RPC |
| 消息队列 | 异步传递消息,解耦生产者和消费者 | 任务队列、事件通知、削峰填谷 |
RPC 可以建立在网络传输之上,也可以建立在本机 IPC 机制之上;HTTP、TCP、命名管道也可能只是 RPC 使用的底层传输。因此,“TCP”“命名管道”和“RPC”并不是同一层的概念。
RPC 默认给人的感觉是“调用并等待结果”,但一些 RPC 框架也支持异步调用、服务端流、客户端流和双向流。
微服务中的 RPC
基本架构
微服务中,RPC 常用于服务之间的内部调用。例如订单服务需要查询用户服务:
订单服务
↓ UserService.GetUser()
RPC 客户端代理
↓ 服务发现、负载均衡
网络连接
↓
用户服务实例
↓ 服务端存根
UserService.GetUser() 的实现
当用户服务有多个实例时,调用方通常不会写死某一个 IP,而是通过注册中心、DNS、配置中心或服务网格找到可用实例,再进行负载均衡。
一次微服务 RPC 调用的流程
- 定义接口:用 IDL 或代码约定方法、参数、返回值和错误类型。
- 生成或实现代理:客户端和服务端获得对应的调用代码。
- 发现服务:根据服务名找到一个或多个可用实例。
- 选择实例:使用轮询、随机、权重或一致性哈希等策略。
- 编码请求:把参数和元数据转换成字节。
- 发送请求:通过 TCP、HTTP/2、QUIC 或其他传输方式发送。
- 服务端分发:服务端解码参数,并调用业务处理函数。
- 返回结果:服务端编码返回值或错误,客户端再将其恢复成对象。
常见 RPC 框架
| 框架或风格 | 主要特点 | 适合场景 |
|---|---|---|
| gRPC | 通常使用 Protocol Buffers,支持多语言、HTTP/2 和流式调用 | 跨语言微服务、内部高性能调用 |
| Apache Thrift | 使用 IDL 和代码生成,支持多种语言及多种协议 | 跨语言服务、已有 Thrift 生态的系统 |
| Dubbo | 提供服务注册、路由、集群容错等治理能力,常见于 Java 生态 | Java 微服务和服务治理 |
| JSON-RPC | 使用“方法名 + 参数 + 请求 ID”的调用模型,格式直观 | 轻量工具、脚本、管理接口 |
不同框架之间通常不能直接互相调用。即使方法名看起来相同,序列化格式、请求头、错误码和连接协议也可能不同,需要使用兼容的客户端、网关或适配层。
gRPC 接口示例
下面是一个简化的 Protocol Buffers 接口定义:
syntax = "proto3";
package user;
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
}
message GetUserRequest {
uint64 id = 1;
}
message GetUserResponse {
uint64 id = 1;
string name = 2;
}
根据这个定义,工具可以生成不同语言的客户端和服务端代码。业务代码只需要实现 GetUser,调用方则通过生成的客户端代理发起调用。
微服务 RPC 中需要重点处理的问题
超时与取消
网络调用可能因为服务过载、网络抖动或下游依赖阻塞而迟迟不返回。每次调用都应设置合理的超时或 deadline,并在上游取消时及时取消下游请求。
重试与幂等性
超时不一定代表服务端没有执行成功。如果调用方盲目重试,可能造成重复下单、重复扣款或重复写入。
通常只有明确具备幂等性的操作才适合自动重试,并应配合指数退避、随机抖动、最大重试次数和熔断机制。
版本兼容
服务升级时,旧客户端可能仍然存在。新增字段通常比复用旧字段编号或改变字段类型更安全;删除字段时也要考虑旧客户端和缓存数据。
错误分类
至少要区分:
- 网络连接失败。
- DNS 或服务发现失败。
- 超时和取消。
- 认证或授权失败。
- 服务端内部错误。
- 业务校验失败。
不能把所有错误都包装成“调用失败”,否则排障时很难判断问题发生在哪一层。
安全与可观测性
生产环境通常需要:
- TLS 或其他传输加密。
- 服务身份认证和方法级授权。
- 请求 ID、Trace ID 和结构化日志。
- 调用耗时、错误率、超时率和重试次数指标。
- 请求大小限制、并发限制和速率限制。
RPC 只是把网络调用包装得像本地函数,并不会消除网络的不可靠性。设计 RPC 时仍然要把每次调用视为一次可能失败的网络操作。
frpc 与 RPC 的关系
frpc 是 frp 的客户端程序。frp 是一个反向代理和内网穿透工具,frpc 负责在内网机器上主动连接公网或可达网络中的 frps,再由 frps 将外部连接转发到内网服务。
因此:
frpc不是 RPC 框架。frpc不负责定义方法、序列化参数或分发 RPC 请求。frpc通常只转发 TCP、UDP、HTTP 等连接或字节流。- RPC 服务可以被 frpc 转发,但服务本身仍然使用 gRPC、Windows RPC、HTTP API 等协议。
frpc 转发 RPC 服务的结构
外部 RPC 客户端
↓ 访问公网服务器:15051
公网服务器上的 frps
⇅ frp 隧道
内网机器上的 frpc
↓ 127.0.0.1:50051
本地 RPC 服务
例如,内网机器上的 gRPC 服务监听 127.0.0.1:50051,可以通过 frpc 映射到公网服务器的 15051 端口。外部客户端访问公网服务器时,数据经过 frps 和 frpc 转发,最终到达本地 gRPC 服务。
基本 TCP 转发示例
以下是新版本 frp 常见的 TOML 配置示意。frps.toml 和 frpc.toml 中的认证配置应保持一致,具体字段仍应以所使用版本的官方示例为准。
公网服务器上的 frps.toml:
bindPort = 7000
auth.method = "token"
auth.token = "replace-with-a-strong-token"
内网机器上的 frpc.toml:
serverAddr = "public.example.com"
serverPort = 7000
auth.method = "token"
auth.token = "replace-with-a-strong-token"
[[proxies]]
name = "grpc-user-service"
type = "tcp"
localIP = "127.0.0.1"
localPort = 50051
remotePort = 15051
分别启动:
./frps -c ./frps.toml
./frpc -c ./frpc.toml
外部客户端随后连接 public.example.com:15051。frp 只负责把连接转到 127.0.0.1:50051,gRPC 的 TLS、认证、服务方法和错误处理仍由 gRPC 服务自己负责。
frpc 使用中的注意事项
- 只映射确实需要访问的端口,不要为了方便把整台内网主机暴露出去。
- frp 的 token 认证只用于控制 frpc 与 frps 的连接,不等于 RPC 服务本身的用户认证。
- 对外提供服务时仍应配置 RPC 层的 TLS、身份认证和授权。
- 通过防火墙限制来源地址,必要时使用
stcp等需要访问方提供密钥的模式。 xtcp等模式可能受 NAT 类型影响,不能把它当成所有网络环境下都可靠的普通端口转发。- 配置文件中的 token、私钥和证书不应提交到公开仓库。
frpc 与 Windows RPC 的特殊关系
简单的 TCP 转发适合单端口、协议边界清晰的服务,例如 gRPC 或普通 TCP 服务。但 Windows RPC 可能先访问 Endpoint Mapper,再根据协商结果连接动态端口;如果只转发一个端口,后续调用可能仍然无法建立。
此外,本机 Windows RPC 使用的 ncalrpc 属于本地 RPC 传输,不能简单理解成“把一个 TCP 端口转发出去就能访问”。如果确实需要跨主机访问 Windows RPC,应明确使用的协议序列、固定端点、动态端口范围、防火墙规则和认证方式,并避免直接把高权限管理接口暴露到公网。
Windows 进程之间的 RPC
Windows RPC 是 Windows 提供的一套远程过程调用机制,既可以用于同一台主机上的进程间通信,也可以用于跨主机通信。许多系统服务、管理工具和 COM/DCOM 组件都会在不同程度上使用 RPC。
本机 RPC 与网络 RPC
Windows RPC 的“远程”是相对于调用进程而言的:
- 本机 RPC:客户端和服务端位于同一台机器,常使用本地 RPC 传输,例如
ncalrpc。它通常不需要通过 TCP135与对方通信。 - 网络 RPC:客户端和服务端位于不同机器,可能使用基于 TCP 的
ncacn_ip_tcp、基于命名管道的ncacn_np等协议序列。
网络 RPC 常见的连接过程是:客户端先通过 RPC Endpoint Mapper 查询接口对应的端点,再连接实际服务端点。Windows 中 TCP 135 通常与 Endpoint Mapper 相关,但 RPC 实际调用不一定一直使用 135,也可能使用动态分配的端口。
Windows RPC 的调用链
客户端进程
↓ 调用生成的客户端代理
客户端 RPC 运行时
↓ ncalrpc、命名管道或 TCP
服务端 RPC 运行时
↓ 调用生成的服务端存根
服务端进程中的接口实现
开发者通常通过接口定义和代码生成工具描述 RPC 方法。生成的代理和存根负责处理参数编解码,RPC 运行时负责绑定、传输、认证和调用分发,业务代码只实现真正的服务逻辑。
接口、UUID 与端点
Windows RPC 接口通常包含:
- 接口定义:方法、参数类型和方向,例如输入参数或输出参数。
- 接口 UUID:用于唯一标识接口,避免仅依靠字符串名称判断服务。
- 版本号:用于区分接口版本。
- 端点(Endpoint):服务实际监听的通信位置,可以是本地端点、命名管道或网络端口。
- 绑定(Binding):客户端定位并连接某个 RPC 接口的过程。
服务端需要注册接口并监听端点,客户端则根据接口信息建立绑定,然后调用接口方法。一个 Windows 服务可以暴露多个 RPC 接口,也可能通过同一传输层承载多个接口。
RPC 与 COM、DCOM
三者的关系可以简单理解为:
- RPC:解决“如何把一次方法调用传到另一个执行单元”。
- COM:定义组件、对象和接口的模型,支持客户端通过接口使用组件。
- DCOM:将 COM 的使用范围扩展到跨进程、跨主机,并依赖 RPC 等机制完成远程对象调用。
因此,跨进程 COM 调用通常会涉及 RPC,但 RPC 并不等同于 COM;一个自定义 Windows RPC 接口也不一定是 COM 对象。
Windows 常见进程间通信方式对比
| 方式 | 特点 | 是否天然是 RPC |
|---|---|---|
| Windows RPC | 有接口、方法和参数语义,可支持本机或跨主机调用 | 是 |
| COM / DCOM | 面向对象和接口的组件模型,跨进程时通常借助 RPC | COM 不是 RPC,但常使用 RPC |
| 命名管道 | 面向字节流或消息的通信机制,也可作为 RPC 的承载 | 否,可作为 RPC 传输 |
| 共享内存 | 传输效率高,但需要额外的同步和协议设计 | 否 |
| Socket | 通用网络通信接口,需要自行定义应用层协议 | 否 |
| ALPC | Windows 内核支持的本机消息通信机制,常用于系统组件 | 否,不等同于 Windows RPC |
看到命名管道、TCP 连接或某个进程间句柄时,不能仅凭传输方式就断定这是 RPC;还要看上层是否存在明确的接口和方法调用协议。
Windows RPC 的安全问题
本机 RPC 并不意味着天然安全,网络 RPC 也不一定就是恶意行为。判断风险时应结合:
- 调用方和服务端进程的路径、签名、账户和完整性级别。
- RPC 接口 UUID、服务名称和实际调用的方法。
- 身份认证、授权检查和模拟(impersonation)级别。
- 端点的 ACL、命名管道权限和服务运行账户。
- TCP
135、动态端口或命名管道是否被不必要地暴露。 - 调用是否与远程管理、域服务、打印、软件分发或安全软件的正常行为匹配。
安全调查中不能把“出现 RPC 通信”直接等同于“已经提权”或“已经横向移动”。更可靠的做法是把 RPC 事件与进程创建、登录、服务创建、文件写入、网络连接和认证结果关联起来。
三类场景的区别
| 场景 | RPC 承载的对象 | 典型位置 | 关键问题 |
|---|---|---|---|
| 微服务 RPC | 业务方法和结构化数据 | 不同服务或主机之间 | 超时、重试、版本、发现、观测 |
| frpc 转发 RPC | 已存在的 RPC 网络连接 | NAT 或防火墙后的服务 | 暴露面、认证、端口和协议兼容 |
| Windows 进程间 RPC | 系统或应用进程的方法调用 | 同一台 Windows 主机或 Windows 网络 | 接口 UUID、端点、ACL、模拟和权限 |
最容易混淆的一点是:微服务 RPC 是一种应用通信方式,frpc 是一个连接转发工具,Windows RPC 是一套具体的系统 RPC 实现。它们可以组合使用,但不属于同一个层次。
常见误区
“RPC 就是 HTTP API”
HTTP API 是一种接口承载方式,RPC 是一种调用模型。HTTP 上可以承载 JSON-RPC、gRPC 或自定义 RPC,但 HTTP REST API 不一定是 RPC。
“frpc 就是 RPC 的客户端”
frpc 是 frp 的客户端。它连接的是 frps,负责建立隧道和转发流量,并不会理解 GetUser()、CreateOrder() 这类业务方法。
“本机 RPC 不需要权限控制”
同一主机上的其他进程仍可能尝试连接本地端点、访问命名管道或调用暴露的接口。本机 RPC 也应根据调用方身份、权限和方法敏感度进行授权。
“TCP 端口通了,RPC 就一定可用”
RPC 还可能涉及服务发现、端点协商、TLS、认证、版本和序列化。TCP 连接成功只能说明底层连接的一部分可达,不能证明完整 RPC 调用成功。
“所有 RPC 调用都可以重试”
重试可能导致重复写入或重复执行。只有明确具备幂等性的操作,才适合在清晰的超时和退避策略下自动重试。