知识卡片

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) - 结论依据:原文逐条列出网络请求相比本地函数调用在可预测性、失败模式(超时)、 重试幂等性、延迟可变性、参数传递编码开销五个方面的本质差异,并总结"尝试使远程 服务看起来像编程语言中的本地对象一样毫无意义,因为这是一个根本不同的事情", 直接支撑本卡片结构梳理。 - 原始内容:本地函数调用是可预测的……网络请求是不可预知的……网络请求有另一个可能 的结果:由于超时,它可能会返回没有结果……如果您重试失败的网络请求……重试将导致 该操作被执行多次……所有这些因素意味着尝试使远程服务看起来像编程语言中的本地 对象一样毫无意义。