☰
有HTTP了为什么还要RPC?一文讲透远程调用选型与差异
2026/10/15 4:57:09 网站建设 项目流程

最近我们内部系统做服务化拆分,后端团队开了一场技术讨论会,主题就一句话:有 HTTP 了为什么还要用 RPC?说实话,这个问题在刚工作头两年也困扰过我,当时我以为 RPC 是个和 HTTP 对立的“新东西”,后来深入了解才发现,两者压根不是一个维度。这文章不打算给你讲一堆高深理论,而是从一个一线开发者的实际视角,把 HTTP 和 RPC 掰开揉碎,说说它们各自解决什么问题、有什么坑、怎么选型。如果你正在纠结要不要给项目引入 RPC,或者面试时被问到这个问题卡壳,这篇应该能帮到你。

1. 先拆开 HTTP 和 RPC 这两个词

1.1 HTTP 其实是“资源协议”,不是“远程调用协议”

我们平时说的 HTTP,全称是超文本传输协议,但它真正定义的是一套“资源操作语义”。URL 是资源地址,GET、POST、PUT、DELETE 是资源动作,状态码是操作结果,Content-Type是媒体类型。浏览器能直接访问,curl 能直接敲,中间经过的 Nginx、网关、负载均衡全都认这套东西。因为它足够标准、足够开放,HTTP 才成了互联网上事实的 API 规范。

举个例子,你要拿订单,就是GET /orders/12345,返回一个 JSON。下游要创建订单,就是POST /orders,带上订单字段。RESTful 风格把业务实体映射成资源,动作就是那几个 HTTP 方法,非常简单。但注意,HTTP 并没有规定你的数据格式、错误格式、鉴权方式、超时重试策略,这些都靠业务层去“商量”。所以你会看到各家 API 风格五花八门:有的用code/message/data,有的直接返回数据体,有的在 header 带 traceId。HTTP 的核心追求是“资源表述”,说白了就是“你操作什么”,而不是“你调用哪个方法”。

1.2 RPC 的目标是让你像写本地代码一样调用远程

RPC 全称是远程过程调用,它关心的不是“资源”,而是“过程”。所谓过程,就是函数、方法。RPC 想做到的是:调用远端服务,就像调用本地函数一样简单。客户端写好result = calculator.add(1, 2),剩下的网络地址、消息编码、传输、超时、异常处理,全部由 RPC 框架接管。

这事听起来容易,做起来要解决三件事:寻址,就是告诉框架要调到哪台机器的哪个方法上;编解码,就是参数和返回值要序列化成能在网络上传输的字节流;传输,就是底层的 TCP、HTTP 或者其他通道。成熟的 RPC 框架还会在这个基础上叠加服务注册发现、负载均衡、熔断限流、链路追踪。你可以把 RPC 框架理解成“远程函数的电话交换机”,你只负责拨号说要找谁,剩下路由和接通全是系统的事。

1.3 概念误区:HTTP 和 RPC 不在一个层级

比较“HTTP vs RPC”,本身是一个概念错位。HTTP 是协议,RPC 是设计理念,两者并不是猫和狗的关系。RPC 可以构建在 HTTP 之上,比如使用 HTTP/2 作为传输层的框架;也可以直接跑在 TCP 长连接上,甚至 Unix Domain Socket 上。所以“有 HTTP 为什么还要 RPC”更准确的说法应该是:“已经有了 HTTP/1.1 + JSON 这套十分通用的 API 方案,为什么还需要一套带序列化、治理、代码生成的远程调用方案?”

这个澄清很重要。实际项目里说“用 HTTP”,通常默认是 RESTful + JSON,大家 curl 就能调试;说“用 RPC”,通常默认是一套框架,带注册中心、IDL 文件、服务发现。只有把讨论聚焦到这个层面,后面的选型才有意义,否则就会像问“有了自行车为什么还要汽车”一样,只能得到“因为距离不同、载重不同”的模糊答案。

2. 从三个典型痛点看 RPC 出现的必然

2.1 服务拆分后,调用链路的治理成本暴涨

我们单体系统变成微服务之后,最直接的问题就是:原来进程内的一个方法调用,变成了网络请求。一次调用,你要做 URL 拼接、设超时、处理异常、判断重试、打日志、找服务地址。服务少的时候,这都还能靠手写硬扛。但我经历过一个模拟项目 X,内部服务从十几个迅速涨到两百多个,调用链变成网状结构之后,HTTP 调用工具类就被改成了“万能胶水”,两千多行,每个上游接新接口都是复制粘贴 URL。

更难受的是硬编码地址。一旦服务重启换 IP,调用方就全部报错。那段时间我们光是维护服务地址表就换了好几个方案,从配置文件到数据库再到“人工口口相传”。后来才开始接触注册中心和客户端负载均衡,这才意识到,原来“寻址、重试、路由、监控”这些共性逻辑完全可以下沉到框架里。RPC 框架走的就是这条路,它把网络调用的脏活累活封装掉,业务代码只需要关心接口和方法签名。这是“为什么需要 RPC”最真实、也最常被忽略的原因。

2.2 高并发低延迟场景,HTTP/1.1 + JSON 不再从容

HTTP/1.1 是文本协议,而且是个“话痨”。一个 GET 请求,光请求头就有 Host、User-Agent、Accept 等一堆字段,200 字节是常态。返回体如果是 JSON,字段名还会反复出现,比如一个用户对象,"userId":123这种字符串占的空间远大于二进制编码。我做过一个粗略对比:同样传一个User对象,JSON 可能要 300 字节,而二进制序列化可能只要 60 字节。当 QPS 到万级时,这个差距直接转化为带宽成本和 CPU 开销。

更麻烦的是 HTTP/1.1 的连接模型。一个 TCP 连接同一时刻只能处理一个请求,有队头阻塞。要做并发就只能多开连接,而 TCP 连接建立本身要三次握手、四次挥手,哪怕有 keep-alive,长连接下的并发能力也有限。我记得一次线上压测,某个 HTTP 服务在 3000 QPS 左右连接池就爆了,大量线程阻塞在httpclient.execute,Latency 直接从 20ms 飙到 2s。换成基于 TCP 长连接 + 紧凑二进制协议的 RPC 后,同样压测环境轻松扛到 8000 QPS,机器学习准确我们认为整条链路的资源占用下去了。当然,这也不是说 HTTP 就永远慢,而是“HTTP/1.1 + JSON + 手写连接池”这套组合在高并发下确实吃力。

2.3 团队协作时,强契约比文档沟通可靠得多

HTTP 接口通常没有内建的“契约”。两个服务之间对齐接口靠什么?靠文档、靠curl试、靠 Common API 样板。改动字段类型,漏改文档,线上跑挂了才发现。我印象很深的一次事故是:下游服务把userId从字符串改成了整数,但调用方还按旧格式传参,服务端解析出来全变成 0,脏数据直接写进了存储,排查了好几个小时。

RPC 框架里的 IDL(接口描述语言)就是来解决这个问题的。先定义一份接口文件,把方法签名、字段名、字段类型、是否必填、默认值全部写清楚,然后客户端和服务端都从这份文件生成代码。接口改了,两端代码重新生成,类型不匹配直接编译期报错,根本不需要人工去“猜”。对于跨团队协作来说,这种“契约先行”的机制能帮你把很多运行期事故提前消灭在编译期,省下来的排查时间比省几毫秒的延迟更有价值。

3. 核心差异拆解:它们到底差在哪

3.1 一个表格看清关键维度

对比维度HTTP/1.1 + JSON 方案RPC 框架方案
协议定位资源协议,操作语义清晰方法调用协议,关注远程函数
接口契约依赖文档、OpenAPI 等外部补充IDL 定义,代码生成,编译期强约束
序列化JSON,可读性强,体积大二进制(如 Protobuf 等),体积小、解析快
传输层文本协议,连接复用差,有队头阻塞TCP 长连接或 HTTP/2,多路复用、头部压缩
服务发现通常依赖 DNS、网关、人工配置内置注册中心,自动发现
负载均衡依赖 Nginx、网关做中心化转发客户端 SDK 内软负载,支持多种策略
超时/重试/熔断业务自己实现框架内置,配置化开启
可调试性curl 一把梭,浏览器直接看需要专用调试工具/CLI 插件
学习/运维成本低,基础设施兼容性好较高,需要维护注册中心、版本、客户端

这个表格不是要证明 RPC 全面优于 HTTP,而是把差异列出来,方便你按场景判断优先级。实际经验里,很多团队选 RPC 并不是因为压测数据多好看,而是看中了“服务发现、负载均衡、熔断”这些治理能力,省的代码量是明摆着的。

3.2 为什么“HTTP + JSON”也属于 RPC 的一种实现

严格从计算机科学的角度讲,任何一次“客户端请求服务端执行某个功能并返回结果”的远程调用,本质都是 RPC。你写client.getUser(1),底层发一个GET /users/1,这就是用 HTTP 实现了一次远程过程调用。RESTful 只是在这个过程外面套了一层“资源语义”的壳,让调用更规范、更符合 Web 架构,而不是改变其“远程调用”的本质。

所以,“有 HTTP 为什么还要 RPC”这个问题,更准确的理解是:RPC 是“一类解决方案”,HTTP 是“一种传输协议”,我们可以用 HTTP 做 RPC,也可以不用 HTTP 做 RPC。工程里常说的“RPC”,通常是指那些把 API 定义、序列化、传输、治理打包在一起的成套框架,而不是某种特定的网络协议。理解了这一点,就不会在面试时被问“gRPC 是不是 RPC”时发懵——所有能完成远程过程调用的框架都是 RPC,只不过它们底层的传输方式各不相同。

3.3 传输层:从 HTTP/1.1 到 HTTP/2 的变化

HTTP/2 把文本协议升级成了二进制分帧,支持多路复用、头部压缩、服务端推送,这让它作为 RPC 传输层的时候一点不逊色于裸 TCP。很多新一代 RPC 框架直接以 HTTP/2 为底座,就是为了同时拿到“二进制传输的高效”和“HTTP 生态的兼容性”。这也是为什么今天讨论“HTTP 和 RPC”时,不能简单地说“RPC 比 HTTP 快”,因为有些 RPC 就是用 HTTP/2。

但传输层只是 RPC 的一个环节,RPC 更重要、更核心的价值在接口定义、代码生成和服务治理上。你可以把传输层理解成公路,协议数据是货车,注册中心和熔断是交通调度。就算路修得再好,没有调度和规则,车多了照样堵。所以,当我们讨论是否引入某类 RPC 框架时,不要只盯着它“底层是不是 HTTP/2”,而是看这一整套体系能不能降低你团队在服务治理上的重复劳动。

4. 动手对比:从一个加法接口看实现差异

4.1 先实现一个极简的自定义 RPC 协议

理论讲再多,不如动手跑一个 Demo。我用 Python 写一个最小可用的“自定义 RPC 协议”示例,展示它的核心思路:客户端把方法名和参数编码成字节流,服务端读出来、路由到本地函数、再把结果编码返回。这里为了演示,序列化用了 JSON,真实框架会用更紧凑的二进制方案。

import socket import struct import json def pack_request(method, payload): body = json.dumps({"method": method, "args": payload}).encode("utf-8") return struct.pack(">I", len(body)) + body def unpack_frame(conn): header = b"" while len(header) < 4: chunk = conn.recv(4 - len(header)) if not chunk: break header += chunk if len(header) < 4: return None (body_len,) = struct.unpack(">I", header) body = b"" while len(body) < body_len: chunk = conn.recv(body_len - len(body)) if not chunk: return None body += chunk return json.loads(body.decode("utf-8"))

这段代码做了两件关键小事:加了 4 字节的长度前缀,解决 TCP 粘包问题;用大端字节序!保证跨语言解析一致。服务端只需要先recv4 字节拿到 body 长度,再按长度读完整消息,然后根据method字段做分发。这个 Demo 虽然简陋,但把 RPC 的骨架完整跑了一遍:请求、解码、路由、执行、返回。

4.2 HTTP + JSON 做同样的事,亏在哪

我们再看看用 HTTP + JSON 完成同样的“加法调用”需要什么。客户端要拼接 URL、设置Content-Type、把参数 JSON 序列化、发 POST、读状态码、解析响应、判断业务码。服务端要用路由框架绑定 URL、处理 request 方法、解析 body、调业务函数、再封装 response。光从代码行数看,HTTP 方案的样板代码明显更多,而且这部分跟业务一点关系都没有。

从报文体积来看,差别就更直观。假设请求就是a=1,b=2,HTTP 请求头加上POST /add HTTP/1.1\r\nHost:...这一串,少说 150 字节,JSON body 还要写{"method":"add","args":[1,2]},又是 30 多字节。而我上面的二进制封装,4 字节长度 + 方法名 + 参数,如果方法名简化为方法 ID,整个请求可能 20 字节以内。性能数据当然随环境和序列化方案变化,但大体趋势是:报文体积小、序列化快,在高并发下的收益很明显。

4.3 实操心得:自定义协议最容易踩的三个坑

第一是粘包和半包。TCP 是流协议,它不会帮你按“一条消息”切好边界。你 send 两次,对端可能一次就收到了,这叫粘包;你 send 一次,对端可能 recv 两次才收到,这叫半包。解决办法就是我在 Demo 里加长度前缀,这是最经典也是最能兜底的做法。

第二是字节序。不同语言、不同 CPU 架构对整数的字节序处理不一样。如果你客户端按小端序打包,服务端按大端序解析,出来的数字全是“天书”。所以协议设计里一定要显式约定字节序,通常统一用网络字节序(大端),也就是struct.pack(">I", ...)里的>。

第三是协议版本兼容。协议上线后一定会改,新增字段、删除字段、修改类型。如果你没有在消息里加版本号,新老客户端交错访问时就会解析错乱。经验是先定义一个版本字段,新老兼容用“新增字段可空”的原则,必要时做双版本共存。很多人一上来就自己造协议,结果半年后改需求改到想骂人。除非你只是想写个 Demo,否则别自己造,直接选成熟的框架比自己从零设计协议靠谱得多。

5. 真实项目里怎么选?我的一套决策清单

5.1 场景导向:四个问题快速定位

每次有团队来问“要不要上 RPC”,我都先让他们回答四个问题:

  1. 这个接口是给内部服务调,还是外部第三方调?外部调用必然要考虑兼容性和生态,HTTP 通用性碾压自研客户端。
  2. 调用频率和延迟要求有多高?一天几千次和一秒几万次,完全不是一码事。
  3. 团队内部能不能统一维护同一套客户端 SDK?跨语言、跨团队时,RPC 框架的接入成本会成倍增加。
  4. 有没有服务注册发现、熔断、链路追踪的硬性诉求?如果只是两三个服务之间的小集群,HTTP 加一个 Nginx 就够用了。

把答案写下来,再对照我之前列的差异表,基本就能拍板。对外 API、BFF、浏览器端接口,老老实实用 HTTP;内部服务之间大流量、强依赖、高频动态上下线,再考虑 RPC 框架。场景没有好坏,只有合不合适。

5.2 哪些情况下别盲目上 RPC

我见过不少反面案例:服务只有两个,每天调用量几百次,却为了“跟上架构潮流”硬上了一套注册中心加两个环境的内存缓存客户端,结果光运维成本就比业务代码还高。引入注册中心要部署、要监控、要备份;引入框架要升级版本、要解决跨语言兼容;连调试都变麻烦了,以前curl一下就能看返回,现在还得装专用命令行工具。

所以我的经验是:能用 HTTP 解决的,先别急着上 RPC。尤其前期业务模型还没稳定,接口天天改的时候,HTTP 改起来成本低、调试方便。等业务跑通、规模上来再演进到 RPC 框架,是完全合理的技术债务策略。你不是在偷懒,是在用最小成本验证业务逻辑,等真正需要的量级到了再补基础设施,反而更好。

5.3 引入 RPC 的运维成本清单

如果决定上 RPC,一定要提前算清这笔账。必选项至少有:

  • 注册中心:保存可用服务节点,负责健康检查和心跳维护,部署至少两个实例保证可用。
  • 配置中心:管理环境变量、开关、灰度配置,没有它你线上改配置得发版,效率极低。
  • 网关或客户端安全认证:内部服务也不能裸奔,至少要对天级对外的端口做访问控制。
  • 链路追踪系统:RPC 调用链变长后,没有 traceId 排查问题就是大海捞针。
  • 日志和监控:P99、QPS、错误率、熔断事件,都得有面板。

这些成本不会因为选了 RPC 就消失,只会从业务代码里转移到基础设施里。你省下了手写调用工具类的几千行代码,但得多维护几套中间件。选型时要冷静评估团队承受能力,不要只看到好处。

6. 常见问题与排查速查

6.1 三个真实案例复盘

案例一:偶发超时,一压测就现原形。服务只在高峰期出现 5% 左右的超时,CPU 和内存看着都不高。排查半天,最后发现是 HTTP 客户端连接池配置设得太小,慢请求占满了所有连接,新请求全部排队等待。这种问题在 RPC 场景同样会出现,配置好连接池大小、读超时、熔断阈值,压测的时候把连接数和线程数指标一起盯住就行了。

案例二:上线新版本后,部分客户端报错。服务端把接口里的某个非必填字段删了,但调用方客户端还是旧版,仍然发送这个字段。老协议框架下,服务端遇到未知字段可能直接反序列化失败。后来的策略是:IDL 改动必须“先增后删”,新增字段先上线一段时间,等所有调用方升级后再删除;发布周期也尽量做到新旧客户端兼容。

案例三:跨语言调用时整数精度丢了。我们有一次用 Python 调用某个 Java 服务,大整数 ID 传到 Python 后被解析成了浮点,低位数字变成 0,数据直接对不上。原因就是底层序列化对大整数类型处理不一致。最后的约定方案是:大整数在 IDL 里统一用字符串表示,业务侧需要数字再自行转换,彻底避开精度坑。

6.2 RPC 调用排查速查表

现象可能原因排查与解决
连接超时网络分区、注册节点不可用、连接池枯竭检查服务端健康检查状态,看连接池是否打满
报错 “No provider”注册中心里没有可用服务节点确认服务是否成功注册,检查心跳和防火墙
调用偶发失败服务端慢调用拖垮线程池、GC 停顿看 P99 指标,调大超时,加限流和熔断
反序列化异常客户端服务端版本不一致,字段被改对比两边 IDL 版本,走灰度发布,先加后删
响应体变大导致内存高序列化对象嵌套过深、大字段未裁剪精简数据结构,按场景裁剪返回字段
跨语言数据不一致序列化类型映射问题,如 Long/浮点精度大整数用字符串,空值和 null 要统一约定

这些坑不只在 RPC 里有,HTTP 接口一样会遇到,只是 RPC 框架把其中一部分问题隐藏得更深,初学者排查起来更不容易。无论选哪种方案,日志里一定要带 traceId,保证你能从入口一眼追踪到出口。没有链路追踪,所有排查都是盲猜。

最后说点个人体会。我在实际项目中踩过“盲目上 RPC”的坑,也踩过“死守 HTTP 不愿改”的坑,最后发现,技术选型最大的敌人不是协议本身,而是“为了用而用”。RPC 不是 HTTP 的替代品,HTTP 也不是 RPC 的绊脚石,它们在不同场景下有各自的合理位置。我建议你先拿一个边缘服务做对比试验,分别用两种方案实现同一个接口,记录 P99 延迟、失败率、带宽、代码行数、团队上手时间,用数据说话,再决定要不要全面铺开。这样得出的结论,比任何技术文章和别人的经验都靠谱。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询