一、💡 一句话理解
核心结论
RPC(Remote Procedure Call,远程过程调用)让一个程序通过网络调用另一个程序提供的方法,业务代码写起来接近本地方法调用。
记住这条线:找谁调用 → 选哪个实例 → 编码发送 → 远端执行 → 返回结果;等不到结果时,还要处理不确定性。
知识归属:架构设计 → 分布式 → 服务间通信 → RPC。SOFA RPC、Dubbo、gRPC 是具体实现;序列化、超时、服务发现和负载均衡是理解这些实现的基础。
本文按“服务约定 → 调用流程 → 网络失败 → 多实例寻址”的依赖顺序讲解。以订单服务访问库存服务为主线,围绕一次调用,逐步理解各个机制的职责与边界。官方资料查阅日期:2026-11-03。
二、🧭 理论:它是什么
2.1 从订单查询库存说起
一个应用中,订单模块和库存模块一起运行,订单代码可以直接调用库存对象的方法。系统拆成两个独立进程后,订单进程里的对象引用无法直接指向库存进程中的对象。
即使两个进程在同一台机器上,它们也有独立的内存空间。订单服务需要通过约定好的通信方式,把“调用哪个方法、传什么参数”告诉库存服务。
| 场景角色 | 技术对象 | 职责 |
|---|---|---|
| 订单服务 | 调用方 / Consumer / Client | 发起库存查询或扣减请求 |
| 库存服务 | 提供方 / Provider / Server | 执行库存业务并返回结果 |
| 调用方持有的库存代理对象 | 客户端代理 / Stub | 把方法调用转换成网络请求 |
| 服务接口或 IDL | 服务契约 | 约定方法、参数、返回值等 |
| 服务地址目录 | 注册中心或名称解析系统 | 帮调用方找到提供方 |
这是一个业务示例。真实框架中“代理”“注册中心”的具体实现不同,也可以直接配置地址,不要求每次调用都经过注册中心。
2.2 先约定双方能说什么
跨进程调用前,双方需要约定:服务名称、方法名称、参数与返回值结构,以及错误怎样表达。Java RPC 框架常使用共享接口和 DTO;gRPC 通常使用 .proto 描述服务和消息,再生成代码。
例如双方约定一个“按商品编号查询库存”的操作:输入商品编号 1001,输出商品编号和可用库存。只有方法名相同还不够,参数类型和字段含义也必须一致。
2.3 远程调用与本地调用有什么不同
| 对比项 | 本地方法调用 | 远程方法调用 |
|---|---|---|
| 执行位置 | 当前进程内 | 另一个进程,可能跨机器 |
| 数据传递 | 参数值或本进程对象引用 | 编码成可传输的数据 |
| 时间开销 | 不包含网络往返 | 包含编码、排队、网络和远端执行 |
| 失败情况 | 主要是程序执行异常 | 还包括断网、超时、远端不可用 |
| 失败后的结果 | 普通同步调用中通常较容易定位 | 可能无法判断对方是否执行成功 |
“像本地调用”的边界
RPC 简化的是调用方式。网络延迟、进程故障、数据复制和结果未知仍然存在,不能把远程调用当成一次便宜且必然成功的普通方法调用。
三、⚙️ 理论:它是怎么工作的
3.1 一次调用走过哪些步骤
订单业务代码
↓ 调用代理对象的查询方法
客户端代理
↓ 获取可用地址,选择一个库存实例
↓ 编码方法信息和参数,通过网络发送
库存服务框架
↓ 解码请求,找到对应业务方法
库存业务方法
↓ 查询库存,生成返回值
库存服务框架
↓ 编码结果,通过网络返回
客户端代理
↓ 解码结果,交还给订单业务代码代理对象不是远程业务对象本身。它在本地接住调用,负责组织请求、通信和处理响应。不同框架可能使用动态代理、生成代码或显式客户端实现这一层。
请求中通常需要能识别目标服务和操作的信息、参数,以及把响应关联回调用的机制。具体信息放在消息体、请求路径还是协议头里,由所用协议决定,并非都发送同一种“Java 方法名字符串”。
3.2 序列化:对象怎样穿过网络
订单进程中的对象只存在于自己的内存里。传一个内存地址给库存进程没有意义,对方无法用这个地址访问订单进程的对象。
序列化把结构化数据编码成可保存、可传输的表示;反序列化把收到的数据恢复为接收方可使用的结构。
调用方参数 → 序列化 → 字节数据 → 网络 → 反序列化 → 提供方参数
提供方结果 → 序列化 → 字节数据 → 网络 → 反序列化 → 调用方结果| 常见方案 | 直观理解 | 学习重点 |
|---|---|---|
| JSON | 用文本表达数据结构 | 易观察,关注数字、日期等类型约定 |
| XML | 用标签表达数据结构 | XML-RPC 等协议会使用它 |
| Protocol Buffers | 按预先定义的消息结构编码 | 关注字段编号、类型和演进兼容性 |
序列化格式不是整个 RPC 协议。完整通信还需要约定消息如何识别、请求怎样对应响应、错误怎样表达等。序列化也不是加密,编码成字节不代表数据已经保密。
双方无需使用同一种编程语言,但必须遵守兼容的数据和通信约定。升级接口时,不要随意改变既有字段的含义或类型;具体兼容规则取决于序列化方案。
3.3 超时:调用方最多愿意等多久
库存服务可能正在排队、查询较慢,或者响应在网络中延迟。如果订单服务一直等待,线程、连接等资源可能长期被占用。
超时用于限制等待时长。截止时间(deadline)则表示最晚愿意等到哪个时间点。两者表达方式不同,但都用于约束时间预算。
超时不等于没执行
客户端超时只能说明它没有在规定时间内拿到可接受的结果。请求可能没到服务端,也可能已经执行成功,只是响应回来得太晚。
例如订单服务要求 500 毫秒内完成扣库存,库存服务在 400 毫秒时已提交修改,但响应在 600 毫秒才到。订单侧会超时,库存却已经扣了。
取消调用也不意味着已经提交的业务自动回滚。框架发出取消信号后,业务任务是否停止,还取决于实现和任务是否配合取消。
时间预算要覆盖整个调用链。假设一次请求总预算为 1 秒,前面已经消耗 700 毫秒,下游和可能的重试应共享剩余预算,而不是各自再等待完整的 1 秒。这些数字仅用于理解,实际配置应结合延迟分布、业务目标和压测。
3.4 重试:再调用一次之前先考虑什么
网络瞬时故障有时可以通过重试恢复。但超时后的结果未知,直接再发一次扣库存请求,可能把库存扣两次。
幂等表示同一个业务操作重复执行,业务效果与执行一次一致。典型做法是给同一操作一个稳定的业务唯一标识,服务端识别重复请求,并返回已处理结果或当前处理状态。
订单 ORD-001 发起一次扣库存操作 → 服务端处理并记录结果
响应丢失 → 调用方使用同一操作标识重试
服务端识别为同一操作 → 返回已有结果,不重复扣减真正落地时,去重记录和库存变更需要有可靠的一致性约束。只在内存里放一个标记,或先查“有没有处理”再无保护地修改,都不足以解决并发重复请求。
重试时至少判断:
- 错误是否可能恢复;参数非法、库存不足等业务拒绝通常不该原样重试。
- 操作是否幂等,尤其是扣款、扣库存、创建订单。
- 剩余时间和最大尝试次数是否允许。
- 是否需要退避和随机抖动,避免大量请求同时重试。
还要避免每一层都重试。如果三层调用各自最多尝试三次,最坏情况下最下游可能收到 27 次调用,放大故障压力。配置里的“重试次数”是否包含首次调用,要看具体框架定义。
3.5 服务注册与发现:地址从哪里来
起初只有一个库存实例,可以直接配置它的地址。实例扩容、重启或迁移后,固定地址越来越难维护。
常见注册发现模型如下:
库存实例 A、B → 向注册中心登记服务与地址
订单服务 → 查询或订阅库存服务地址列表
注册中心 → 返回地址,后续按机制通知变化
订单服务 → 选择地址,直接请求库存实例注册解决“我提供什么服务、在哪里”;发现解决“我要的服务有哪些地址”。地址变化可能通过推送、轮询等方式传播,具体机制由系统决定。
注册中心通常不转发每次业务调用。客户端往往缓存地址列表,因此注册中心短时不可用时,已有调用不一定立即失败;但新实例发现、地址更新和新应用启动可能受影响,行为取决于框架及配置。
注册中心里的地址也不保证当前一定可用:故障检测和变更传播存在时间差,还需要连接状态、健康检查和失败处理配合。RPC 可以采用直连或 DNS 等寻址方式,独立注册中心不是 RPC 的必需组成。
3.6 负载均衡:找到三个地址后选哪个
服务发现得到库存实例 A、B、C 的地址,负载均衡负责从符合路由条件且可用的候选实例里选一个。
| 策略 | 怎样选择 | 注意什么 |
|---|---|---|
| 轮询 | 依次选 A、B、C | 请求数均匀不代表资源消耗均匀 |
| 随机 | 随机选择候选实例 | 少量请求可能不均匀 |
| 加权选择 | 权重高的实例分到更多请求 | 权重需要符合实例能力与运行状态 |
| 最少活跃请求等 | 根据客户端可见的负载指标选择 | 本地观测不一定代表实例全局负载 |
| 一致性哈希 | 根据业务键等映射到实例 | 适用于需要一定访问亲和性的场景 |
客户端负载均衡是在调用方选实例;也可以通过代理或网关转发,让中间层参与选择。不同 RPC 框架支持的策略不同。
记忆方式:服务发现给候选名单,路由缩小范围,负载均衡做本次选择。
3.7 把这些概念放回同一次下单
- 订单服务通过服务发现得到库存服务的实例地址。
- 根据路由和负载均衡策略,选择一个可用实例。
- 代理把服务操作、参数和必要元信息编码成请求。
- 库存服务解码请求,执行查询或扣减,返回编码后的结果。
- 调用方在预算内收到响应,解析结果;超时或失败则按业务规则处理。
- 如果要重试扣减,使用稳定操作标识,并依赖服务端幂等机制防止重复扣减。
RPC 负责完成通信,不自动保证多个服务的数据一起成功。 订单已创建但扣库存失败时,还需要业务协调机制,见 分布式事务。
3.8 RPC、HTTP、REST、MQ 和 SOFA RPC 怎样区分
| 名称 | 所在层面 | 与当前场景的关系 |
|---|---|---|
| RPC | 远程调用模型 | 订单服务调用库存服务的操作 |
| HTTP | 应用层通信协议 | 可以承载 RPC 请求,例如 XML-RPC 和 gRPC 使用的 HTTP 协议族 |
| REST | 架构风格 | 通常围绕资源及统一接口组织交互 |
| MQ | 消息通信机制 | 可以发布事件,让消费方异步处理 |
| SOFA RPC / Dubbo / gRPC | 具体框架 | 提供远程调用及相关能力,功能范围各不相同 |
RPC 与 HTTP 不处在同一层面,不能简单说“用了 RPC 就不用 HTTP”。RPC 也支持异步和流式调用,不能简单说“RPC 都同步,MQ 都异步”。选择时要看业务是否需要立即拿结果、怎样处理失败和如何解耦。
SOFA RPC 是 Java RPC 框架,具备协议、注册发现和负载均衡等相关能力。它使用的具体配置和 API 适合后续单独记录;本篇先建立通用原理。
四、🎤 面试时怎么说
4.1 回答路线
按“是什么 → 一次调用怎么走 → 多实例怎么找和选 → 失败怎样处理”回答,最后点出网络调用与本地调用的边界。
4.2 30 秒回答
RPC 是远程过程调用,让应用通过网络调用另一个应用的方法。调用方通常通过代理组织请求,对参数进行序列化,服务端收到后反序列化并执行业务,再返回结果。部署多个实例时,可以通过服务发现获取地址,通过负载均衡选择实例。它虽然写起来像本地调用,但会遇到网络超时和结果未知,所以要设置合理的时间预算,并结合幂等设计决定是否重试。
4.3 常见追问
超时后能不能直接重试?
不能一概而论。对方可能已经执行成功;扣款、扣库存等操作需要可靠的幂等机制,还要限制重试次数、时间预算,并判断错误是否可恢复。
注册中心挂了,RPC 就完全不能调用了吗?
不一定。已有客户端可能使用缓存地址继续调用,但地址变化、新实例发现和新应用启动可能受影响,要结合框架和配置判断。
RPC 能保证订单和库存一起提交吗?
不能。通信成功和跨资源事务一致性是不同问题,需要结合 分布式事务 中的协调机制处理。
五、📌 快速回顾
| 概念 | 先记住这个问题 |
|---|---|
| 远程调用 | 怎样让另一个进程执行约定的方法? |
| 序列化 | 参数和结果怎样变成能传输的数据? |
| 超时 | 我最多愿意等待多久? |
| 重试与幂等 | 再试一次,会不会把业务做两遍? |
| 服务发现 | 服务有哪些候选地址? |
| 负载均衡 | 本次请求选择哪个候选实例? |
学习时先沿着一次调用串起这些概念,再去看 SOFA RPC 或 Dubbo 的配置。内容积累多了,再把序列化、注册发现或负载均衡拆成独立笔记。
六、📚 官方资料
本篇将多份官方资料按初学所需的前置关系组织,gRPC 的特定行为不代表所有 RPC 框架都相同。业务示例用于帮助理解。
- gRPC 核心概念与调用生命周期:服务定义、客户端代理、调用流程和双方结果可能不同。
- gRPC Deadlines:时间预算、截止时间传播与业务任务取消。
- gRPC Retry:重试策略与退避。
- gRPC 自定义负载均衡:客户端选择实例的机制。
- SOFARPC 介绍:Java RPC 框架定位、注册发现与调用模型。