远程代理和 RPC 框架有什么关系?
简化版
远程代理是代理模式的一种,它让调用方像调用本地对象一样调用远程服务。RPC 框架通常会为接口生成代理对象,代理对象负责序列化、网络通信、负载均衡、超时重试和结果反序列化。
详细版
远程代理的目标是屏蔽远程调用细节。
调用方看到的是:
UserService userService = rpcClient.getProxy(UserService.class);
User user = userService.getUser(1001L);
看起来像本地方法调用,但实际上代理对象背后做了很多事情:
- 拦截接口方法调用;
- 解析方法名、参数类型、参数值;
- 将请求序列化;
- 选择服务实例;
- 通过网络发送请求;
- 等待远程响应;
- 反序列化返回值;
- 处理超时、异常、重试、熔断等问题。
调用链可以理解为:
客户端 -> 本地代理对象 -> 网络请求 -> 服务端真实对象
所以 RPC 框架中的客户端 Stub、Feign Client、Dubbo 接口代理,本质上都能看到远程代理思想。
需要注意:远程代理虽然让调用形式像本地调用,但远程调用仍然有网络延迟、失败、超时、序列化成本,不能真的当成本地方法一样随意调用。
完整版教学
一、远程代理解决的核心问题
在分布式系统中,服务不一定在同一个进程里。
订单服务可能要调用用户服务:
订单服务进程 -> 用户服务进程
如果每次都手写 HTTP 请求、序列化、反序列化、错误处理,业务代码会非常重:
String json = httpClient.post(url, body);
User user = objectMapper.readValue(json, User.class);
远程代理希望把这些细节藏起来,让调用方只依赖接口:
User user = userService.getUser(userId);
调用方关心业务语义,代理对象关心通信细节。
二、RPC 代理对象通常做了什么
一个 RPC 客户端代理对象通常要处理:
- 接口方法映射:知道调用的是哪个服务、哪个方法;
- 参数封装:把参数名、类型、值放进请求;
- 序列化:把对象转成字节流或 JSON;
- 服务发现:找到可用服务实例;
- 负载均衡:多个实例中选一个;
- 网络传输:通过 TCP、HTTP、HTTP/2 等发送;
- 超时控制:避免调用无限等待;
- 重试策略:失败时是否重试;
- 熔断降级:远程服务不稳定时保护调用方;
- 反序列化:把响应转回 Java 对象。
这些都不是业务逻辑,却是远程调用必须面对的访问过程。代理模式非常适合承载这些逻辑。
三、为什么远程调用不能完全等同本地调用
远程代理会让代码写起来像本地调用,但语义上不能真的当成本地调用。
本地调用通常:
- 延迟很低;
- 成功率高;
- 异常模型简单;
- 参数传引用或值由语言决定;
- 不涉及网络分区。
远程调用则可能:
- 网络超时;
- 服务不可用;
- 请求重复发送;
- 响应丢失;
- 序列化失败;
- 版本不兼容;
- 部分成功。
因此,使用 RPC 代理时要特别注意超时、幂等、重试、限流和降级。
四、远程代理和本地代理的区别
本地代理通常增强的是权限、日志、事务、缓存等逻辑。
远程代理增强的是通信过程:
本地代理:控制访问本地对象
远程代理:把访问转发到远程对象
不过二者本质一样:客户端都不是直接访问真实对象,而是访问代理对象。
远程代理只是代理对象和真实对象之间隔了一层网络。
五、面试中怎么回答 RPC 与代理模式
一个比较完整的回答可以这样组织:
RPC 框架通常会给服务接口生成客户端代理对象,调用接口方法时进入代理,代理负责把本地方法调用转换成远程请求,包括序列化、服务发现、负载均衡、网络传输、超时重试和反序列化。它体现了远程代理模式,让调用方以接口方式使用远程服务,但工程上不能忽略网络失败和分布式语义。
这个回答既讲了模式,也讲了工程边界。
六、本地接口背后的远程失败语义
本地方法可能 1 ms 返回,RPC 经序列化 1 ms、网络往返 20 ms、服务处理 5 ms,总耗时约 26 ms,且任一阶段都可能超时。
stub -> encode -> network -> server dispatch -> target -> encode -> network -> stub
这个推演把模式带来的收益和成本放在同一条调用链上。设计评审时既要确认结果正确,也要确认额外层次没有改变原有契约。
七、边界、代价与验证
| 检查维度 | 应确认的内容 |
|---|---|
| 正确性 | 位置透明不等于语义透明:看到普通方法签名,也要按分布式调用设计超时和幂等。 |
| 适用边界 | 远程代理只能隐藏通信细节,不能抹平超时、部分失败、重试重复执行和版本兼容。 |
| 测试证据 | 覆盖正常路径、异常路径、空值或未知类型,并断言调用次数与顺序 |
| 运行成本 | 记录额外对象、调用层数、延迟、线程或内存开销,不用模式名称代替测量 |
模式落地后要用单元测试验证独立职责,用集成测试验证对象装配和真实调用入口。涉及并发、远程或异步时,还要补充竞态、超时、重复执行与资源释放测试。
易错点:位置透明不等于语义透明:看到普通方法签名,也要按分布式调用设计超时和幂等。
八、常见误区与追问
- 误区:RPC 代理让远程调用和本地调用完全等价。 网络带来延迟、不确定失败和序列化边界,调用方必须显式治理。
- 误区:代理类存在,就代表所有调用都会经过代理。 只有客户端持有并调用代理引用时增强才生效;绕过代理直接调用目标对象,调用链自然不会出现增强。
- 误区:示例代码能运行,就代表模式边界设计正确。 能运行只证明一条路径,仍要检查异常、并发、顺序、生命周期以及新增实现时是否修改稳定代码。
- 追问:为什么重试可能造成一次调用执行两次? 客户端超时不代表服务端未完成;若再次发送且服务端没有幂等键,就可能重复执行。
- 追问:代理模式和装饰器模式能只靠类图区分吗? 不能;两者都可能包装同一接口,必须结合“控制访问”还是“叠加职责”的设计意图区分。
- 追问:怎样验证这套设计没有改变原有语义? 先做基线行为测试,再对新增层验证返回值、异常类型、调用次数和顺序;性能敏感处还要比较改造前后的量化指标。
九、加强记忆
远程代理就是“本地门面,远程执行”。RPC 让你写起来像本地方法,但代理背后跑的是网络通信;调用形式被简化了,分布式风险没有消失。