RPC 与 REST 的本质区别看这一篇就够了:别再只看 URL 里有没有动词

一次 API 评审里,有人问:"为什么这些接口全是 POST,不能改成 RESTful API 吗?"

这个问题很常见,却把三件事混在了一起:HTTP 方法、URL 命名和接口的设计思想。结果往往是团队花了一下午争论 URL 里能不能出现动词,最后只是把 /createOrder 改成 /orders,系统的耦合方式却一点没变。

要真正分清 RPC 和 REST,先记住一句话:

RPC 围绕"动作"组织接口;REST 围绕"资源"组织接口,并用一套统一语义操作这些资源。

这不是文字游戏。选择动作还是资源,会影响调用方需要知道多少业务流程,也会影响重试、缓存、状态恢复和接口演进。

先用同一个需求看懂区别

假设我们要提供"生成报告"的远程能力。生成过程可能持续几分钟,调用方还要查询进度和取消任务。

RPC 风格会把它设计成几个远程方法:

text 复制代码
startReportExport(params)
getReportExportStatus(exportId)
cancelReportExport(exportId)

如果使用 HTTP 承载,接口可能是:

http 复制代码
POST /report-service/start-export
POST /report-service/get-export-status
POST /report-service/cancel-export

调用方看到的是一组能力:开始、查询、取消。服务端每增加一种业务动作,通常就增加一个方法。

REST 风格会先问另一个问题:生成报告以后,系统里出现了什么值得被识别和管理的东西?

答案是"一次导出任务"。于是它被建模为资源:

http 复制代码
POST /exports
GET /exports/export-123
DELETE /exports/export-123

第一次请求创建导出任务,服务端可以返回:

http 复制代码
HTTP/1.1 201 Created
Location: /exports/export-123
json 复制代码
{
  "id": "export-123",
  "status": "running",
  "progress": 20
}

之后,调用方读取或终止的是同一个资源。

两种设计最后可能调用完全相同的业务代码。区别不在服务端"做了什么",而在它给调用方展示了什么模型:

RPC REST
调用方首先看到 可以执行的方法 可以识别的资源
接口的主要词汇 startcancelpublish URI、资源表示、HTTP 方法
新能力通常如何出现 增加方法 增加资源、关系或表示
调用方如何了解状态 调用专用查询方法 获取资源当前表示

最简洁的判断方法是:

如果接口主要在回答"服务能替我做什么",它更接近 RPC;如果接口主要在回答"系统中有哪些对象,它们现在是什么状态",它更接近 REST。

RPC 是什么:把网络藏在函数调用后面

RPC 是 Remote Procedure Call,也就是远程过程调用。

它希望让调用远端服务尽量接近调用本地函数:

ts 复制代码
const result = await paymentService.transfer({
  from: 'account-a',
  to: 'account-b',
  amount: 100,
})

真实执行会经过序列化、网络传输、鉴权、超时和服务端处理,但调用方主要面对的是:

  • 服务名;
  • 方法名;
  • 输入参数;
  • 返回值;
  • 方法级错误。

gRPC 是 RPC 的一种具体实现。它通过接口定义描述服务和方法,再生成客户端与服务端代码。JSON-RPC、Dubbo,以及大量 POST /service/doSomething 的 HTTP 接口,也都采用了相似的动作模型。

RPC 的优势很直接:业务意图清楚、类型契约集中、代码生成方便,也适合流式调用和内部高频通信。

它的代价是调用方容易依赖服务端的操作流程。例如调用方必须知道:先 prepare,再 commit,最后 activate。如果服务端以后想合并步骤或调整顺序,客户端也可能需要变化。

REST 是什么:用统一接口操作资源

REST 是 Representational State Transfer。它不是某个框架,也不是一种报文格式,而是一组面向分布式系统的架构约束。

REST 包含客户端与服务端分离、请求无隐藏会话依赖、缓存、分层系统和统一接口等约束。这里最关键的是"统一接口"。

RPC 可以为每种业务定义专用方法:

text 复制代码
approveOrder
cancelOrder
refundOrder
archiveOrder

REST 则刻意收敛操作词汇。不同资源尽量共享 GETPOSTPUTDELETE 等方法语义。资源之间的差异主要通过 URI、表示和链接表达。

这样做并不是为了让 URL 好看,而是让浏览器、网关、代理、缓存和监控系统在不了解具体业务代码的情况下,仍能理解请求的基本意图。

例如,GET 应该表示读取资源,而不是删除数据;PUT 表示用请求中的表示创建或替换目标资源状态;DELETE 表示移除目标 URI 与当前功能之间的关联。中间系统因此可以对不同服务复用相同处理规则。

REST 为这种通用性付出了代价:统一接口不一定是某个具体业务最高效、最直接的表达。计算型操作、实时流和高度专用的内部调用,往往用 RPC 更自然。

REST 的资源不是数据库表

"资源"是理解 REST 最容易卡住的地方。

资源不是数据库表,也不要求与某一行数据一一对应。它是一个可以被稳定命名和引用的概念。

下面这些都可以是资源:

  • 一个用户;
  • 一份文档;
  • 某个订单的当前状态;
  • 一次异步导出任务;
  • 某份文件的指定版本;
  • 一次审批申请;
  • 一组搜索结果。

判断一个概念是否值得成为资源,可以问:

  • 它是否需要稳定身份?
  • 创建后是否还要查询?
  • 是否会被其他对象引用?
  • 是否有创建、运行、完成、取消或过期等生命周期?
  • 失败后是否需要恢复或审计?

如果多数答案是"是",把它藏在一个动作接口后面,调用方通常会失去观察和治理它的能力。

例如下面这个接口名叫预览:

http 复制代码
POST /imports/preview

如果它真的只计算并返回结果,动作式设计没有明显问题。

但如果它还会写数据库、预分配 ID,并永久占用一个幂等键,那么它实际上创建了持久状态。更诚实的模型可能是:

http 复制代码
POST /import-preparations
GET /import-preparations/preparation-123
DELETE /import-preparations/preparation-123

这时需要讨论的不再是"为什么用了 POST",而是:准备记录能不能查询、取消、过期和恢复。

为什么只看 URL 会判断错

下面这个接口使用名词,但本质仍然可能是 RPC:

http 复制代码
POST /operations

{
  "method": "cancelOrder",
  "params": {
    "orderId": "123"
  }
}

因为调用方仍然是在选择远程方法。

反过来,URL 里出现业务含义,也不代表它一定违背 REST:

http 复制代码
POST /orders/123/refunds

这可以解释为在订单的退款集合中创建一笔退款资源。退款有自己的身份、金额、状态和审计记录,是一个真实资源,而不是为了躲避动词生造出来的名词。

因此不要用下面这些口诀机械判断:

  • 有动词就是 RPC;
  • 全是名词就是 REST;
  • 使用 POST 就不 REST;
  • 做完 CRUD 就是 REST。

更可靠的问题是:接口是否让重要状态获得稳定身份,并且 HTTP 方法是否保持一致、可预期的语义。

HTTP、RPC 和 REST 到底是什么关系

HTTP、RPC 和 REST 可以放在不同层次理解:

text 复制代码
业务模型
  动作模型 RPC
  资源模型 REST

应用协议
  HTTP
  gRPC protocol

数据格式
  JSON
  Protobuf

因此:

  • RPC 可以通过 HTTP 传输;
  • REST API 通常使用 HTTP,但 REST 不等于 HTTP;
  • 使用 JSON 与采用哪种风格无关;
  • 一个系统可以对外提供资源 API,对内使用 gRPC;
  • 同一个 HTTP API 也可以同时包含资源接口和命令接口。

HTTP 规范本身甚至指出,请求方法有点像对目标对象发起远程方法调用。两者真正的分界是:HTTP 的标准方法不是每个业务对象私有的方法,GETPUT 应该在不同资源上保持相同的基本语义。

换句话说,RPC 的方法词典通常跟着业务增长;REST 的方法词典相对稳定,增长的是资源和资源关系。

两种抽象会带来哪些实际差异

1. 调用方依赖什么

RPC 调用方通常依赖"正确的动作顺序":

text 复制代码
create
publish
activate

REST 调用方通常依赖"资源当前状态以及允许的状态变化":

text 复制代码
draft
published
active

RPC 更容易暴露流程;REST 更容易暴露结果和状态。但如果 REST 客户端仍然硬编码全部步骤,它同样会与服务端状态机紧密耦合。

2. 状态能否被重新发现

远程调用随时可能遇到这种情况:服务端已经执行成功,但响应在网络中丢失。

RPC 需要专门设计查询或重放方法:

text 复制代码
getOperationResult(requestId)

如果操作本身已经成为资源,REST 可以直接读取:

http 复制代码
GET /exports/export-123

这不是说 REST 自动解决了分布式失败,而是资源模型通常会迫使设计者给重要状态一个可重新查询的地址。

3. 幂等和重试如何表达

HTTP 中,"安全"和"幂等"是两件事:

  • 安全表示调用方没有请求改变业务状态,例如 GET
  • 幂等表示相同请求执行多次,其预期最终效果与执行一次相同,例如符合语义的 PUTDELETE

幂等方法便于客户端在响应丢失后自动重试。但方法名称只是契约,不会替应用实现数据库唯一约束、并发控制和结果重放。

同样,POST 默认不承诺幂等,却可以通过幂等键和请求摘要实现业务幂等;RPC 方法也可以是幂等的。

所以幂等不是 REST 独有能力。REST 的价值是把一部分通用语义公开给客户端和中间系统。

4. 缓存和通用基础设施能否参与

一个稳定、可读取的资源:

http 复制代码
GET /exports/export-123

可以自然使用缓存控制、ETag、条件请求和标准监控。

下面这种接口虽然也能返回状态,但通用缓存通常很难理解它:

http 复制代码
POST /report-service/get-export-status

RPC 不是不能缓存,而是需要调用双方或框架额外约定;REST 希望更多组件只理解通用协议就能参与。

5. 接口如何演进

RPC 通常以方法和消息结构为演进单位:增加方法、增加字段、保留旧方法或发布新版本。

REST 通常以资源表示和关系为演进单位:增加可选字段、增加子资源、增加新的链接关系,同时保持既有 URI 和方法语义。

二者都可能设计出兼容性很差的 API。REST 不会自动消除版本问题,RPC 也不必然意味着紧耦合。真正关键的是,对外暴露的概念是否稳定。

不要追求纯 REST 而制造假资源

把任何动作都改写成名词,并不会自动改善设计。

例如一次无状态计算:

text 复制代码
calculateTax(input)
compileSource(input)
validateExpression(input)

调用方关心的是执行能力和结果,没有后续需要查询、引用或治理的对象。RPC 往往更直接。

相反,下面这些概念更适合资源化:

  • 要运行几分钟的任务;
  • 可以暂停、取消或重试的操作;
  • 需要审计的审批和退款;
  • 会被其他对象引用的不可变版本;
  • 失败后需要人工恢复的导入过程。

资源化的目的不是消灭动词,而是不要把持久状态藏起来。

实际项目通常应该混合使用

一个现实系统完全可以这样组合:

http 复制代码
GET  /orders/123
POST /orders/123/refunds
GET  /refunds/refund-456

订单和退款是资源,创建退款仍然使用 POST

服务内部则可以通过 RPC 调用支付系统:

text 复制代码
PaymentService.refund(paymentId, amount)

两者没有冲突:

  • 外部 API 用资源模型提供稳定身份、查询和生命周期;
  • 内部 RPC 用动作模型提供明确、高效的服务调用;
  • 事件系统再负责传播"退款已完成"这样的事实。

比"全系统必须统一成一种风格"更重要的是:每一层选择的抽象是否适合它要解决的问题。

一套真正可用的选择方法

设计接口时,可以按顺序问四个问题。

问题一:调用结果是否需要长期身份

如果结果需要被查询、引用、审计或恢复,优先考虑资源。

如果只是即时计算,优先考虑 RPC。

问题二:调用方关心过程还是状态

如果调用方天然在说"执行校验""计算价格""发送指令",动作模型更直接。

如果调用方更关心"任务现在运行到哪里""订单现在是什么状态",资源模型更清晰。

问题三:通用 HTTP 设施有没有价值

如果需要浏览器兼容、缓存、条件请求、网关治理和第三方客户端,资源化收益更大。

如果是组织内部、强类型、低延迟或流式服务调用,RPC 往往更合适。

问题四:失败后如何找回结果

如果响应丢失后只能重复执行一个动作,却无法查询原操作是否成功,说明接口缺少身份或恢复机制。

解决办法不一定是 REST,但必须补上请求身份、幂等、状态查询或结果重放。

最后可以把选择压缩成一张表:

更倾向 RPC 更倾向 REST
即时计算 长生命周期对象
强类型内部服务 开放或跨团队 API
流式通信 可缓存读取
调用方关心执行动作 调用方关心资源状态
方法语义比资源身份稳定 资源身份比内部流程稳定

最后再回到最初的问题

看到一组全部使用 POST 的接口时,不要立刻问"为什么不用 REST"。先问:

  1. 这些接口是在暴露动作,还是在管理有身份的状态?
  2. 某个动作执行后,是否产生了需要查询、取消、过期或恢复的对象?
  3. HTTP 方法表达的语义,是否和真实副作用一致?
  4. 响应丢失后,调用方能否安全重试或找回结果?

如果一个所谓的预览接口其实写入了长期状态,问题不是它用了 POST,而是它隐藏了自己的生命周期。

如果一个操作只是无状态计算,强行给它制造资源也不会让设计更高级。

RPC 与 REST 不是新旧技术之争,也不是动词和名词之争。它们是在回答两个不同的问题:

RPC 先问"远端能做什么";REST 先问"远端有哪些可识别的资源"。

先选对抽象,再讨论 URL 和 HTTP 方法,API 设计才不会停留在表面。

参考与数据源

相关推荐
catino1 小时前
spring-IOC、DI
java·spring·rpc
秋田君3 小时前
QT_HTTP协议编程
qt·http·iphone
头茬韭菜1 天前
功能点 11-12:客户端、RPC 通信、监控与安全
网络协议·安全·rpc·fluss
楷哥爱开发1 天前
HTTP代理是什么?性能分析、适用场景与配置教程
网络·网络协议·http
IPdodo_1 天前
2026年AI 数据采集代理 IP 选型:成功率、并发、轮换与成本评估
前端·网络·人工智能·chrome·python·http·网络调试
鱼很腾apoc1 天前
【Linux】第13期 详解应用层协议HTTP+Cookie/Session+HTTPS
linux·服务器·c++·学习·http·https
q567315231 天前
Curl 报 CONNECT tunnel failed, response 6xx:排查思路全解
数据库·网络协议·scrapy·http·中间件·http代理
瓦学妹1 天前
HTTP代理性能分析:如何利用 IPFoxy 快速灵活切换协议?
网络·网络协议·http
2601_956743681 天前
上海物联网应用开发技术选型指南:从HTTP/MQTT/Modbus协议适配、平台化架构设计到源代码交付与私有化部署,解析工程落地的关键考量点
物联网·网络协议·http·开发经验·上海