知识卡片
RPC试图让远程调用貌似本地调用是根本性缺陷
内容
远程过程调用(RPC)的核心理念是让向远程服务发请求看起来和调用同一进程内的函数 一样(”位置透明”),这个抽象自20世纪70年代起就反复被各种技术尝试(EJB、Java RMI、 DCOM、CORBA),但这个理念本身有根本性缺陷,因为网络请求和本地函数调用在五个维度上 存在本质差异。第一,可预测性:本地调用成不成功只取决于你控制的参数;网络请求可能 因为网络问题、远程机器慢或宕机而失败,这些完全不在你的控制范围内,必须主动预期 (比如重试)。第二,失败模式:本地调用要么返回结果要么抛异常要么死循环,但网络请求 还有第三种结果——因超时而”没有响应”,这种情况下你根本不知道请求到底有没有被处理。 第三,重试的正确性:重试一个失败的网络请求,可能实际上服务端已经处理了、只是响应 丢失了,此时重试会导致操作被执行多次,除非协议里显式引入幂等机制来去重。第四,延迟 的可变性:本地调用每次耗时基本恒定,网络请求的延迟波动极大,网络拥塞或远程服务过载 时可能慢上千倍。第五,参数传递的本质差异:本地调用能高效传递内存指针,网络请求所有 参数都必须先编码成字节序列才能通过网络传送,对大对象这会成为实实在在的问题,还要 处理不同语言间数据类型转换可能造成的精度损失(如JSON大整数问题)。这五点共同说明 “让远程服务看起来像本地对象”这个目标从根子上就是缘木求鱼,因为两者是根本不同的事情 ——这也是REST受欢迎的部分原因:它不掩饰自己是一个网络协议。
结构图:
flowchart LR
A[本地函数调用] --> A1[可预测: 只取决于自身参数]
A --> A2[失败模式: 返回/抛异常/死循环]
A --> A3[延迟稳定]
A --> A4[传指针, 零编码开销]
B[网络请求] --> B1[不可预测: 网络/远程机器故障不可控]
B --> B2[新增失败模式: 超时=不知道是否执行]
B2 -.重试需幂等机制去重.-> B2
B --> B3[延迟波动极大]
B --> B4[参数必须编码成字节序列, 跨语言类型转换有损]
参考来源
- 位置:《数据密集型应用系统设计》第四章《编码与演化》"远程过程调用(RPC)的问题"
(源文件:_epub-src/ch4_split_003.html)
- 结论依据:原文逐条列出网络请求相比本地函数调用在可预测性、失败模式(超时)、
重试幂等性、延迟可变性、参数传递编码开销五个方面的本质差异,并总结"尝试使远程
服务看起来像编程语言中的本地对象一样毫无意义,因为这是一个根本不同的事情",
直接支撑本卡片结构梳理。
- 原始内容:本地函数调用是可预测的……网络请求是不可预知的……网络请求有另一个可能
的结果:由于超时,它可能会返回没有结果……如果您重试失败的网络请求……重试将导致
该操作被执行多次……所有这些因素意味着尝试使远程服务看起来像编程语言中的本地
对象一样毫无意义。