JSON-RPC 批量请求的核心价值,是把 N 次独立调用压进一个请求体里发出,网络往返次数从 N 次降到 1 次;省下的是等待时间,传输的字节数并没有减少,同城机房感知不明显,到了移动弱网、跨地域链路上,差距会被放大到肉眼可见。
很多人优化接口性能,第一反应是换序列化协议、加缓存、升级机器,可把一次调用的耗时拆开看,真正拖住你的往往是“来回跑”的次数,而非服务器算得慢。
批量请求与单次请求网络开销对比:瓶颈藏在往返次数里
一次单独调用,你的程序到底在等什么
以一次走 HTTPS 的普通 RPC 调用为例,从点下“发请求”到拿到结果,中间排着这些队:
- DNS 解析:有本地缓存可跳过,没有就得多一个来回
- TCP 三次握手:至少 1 个 RTT
- TLS 握手:HTTPS 场景再叠 1 到 2 个 RTT
- 发送请求、等待响应:1 个 RTT,外加服务端处理时间
- 连接关闭或归还连接池
连接池加 HTTP keep-alive 能把第 2、3 步摊销掉,这是基本功,但第 4 步的往返省不掉,一次调用就是一次排队,十次调用排十次队,一百次就排一百次。
合并之后,省掉的是哪一段
批量请求的做法很直接:把十个 method 调用装进一个 JSON 数组,一次 POST 出去,服务端逐个执行,把结果也装进数组一次性返回,整趟链路只剩一次请求-响应往返。
省掉的是 RTT 次数,传输的字节数并没有减少,甚至因为多了一层数组结构,请求体还会略微变大,同机房 RTT 只有零点几毫秒时,这点差异基本感受不到;换成跨地域专线或者手机 4G、5G 弱网,RTT 常年是几十毫秒甚至上百毫秒,账就完全不同了。
| 对比维度 | 逐条调用(以 10 次为例) | 批量调用 |
|---|---|---|
| 网络往返次数 | 10 次 | 1 次 |
| 连接与握手开销 | 连接池可摊销,但仍需 10 次排队 | 1 次 |
| 请求体总大小 | 略小 | 因数组包裹略增 |
| 端到端延迟构成 | 10 × RTT + 10 次处理时间 | 1 × RTT + 合并后的处理时间 |
| 错误定位 | 天然隔离,逐条明确 | 需按 id 逐条判断 |
| 重试粒度 | 单条 | 整批,必要时拆批重试 |
JSON-RPC批量请求适合哪些业务场景
收益最明显的四类场景
- 移动端或弱网环境的首屏聚合:一个首页要拿用户信息、未读消息数、推荐列表、配置开关,四条接口串着发,弱网下首屏白屏时间很难看,合并成一批,首屏基本等于一次往返。
- BFF 或网关层聚合下游微服务:网关本来就在做协议转换,顺手把同一页面要的多个下游调用合并,下游服务不用改。
- 批量查询类接口:按 id 列表查商品详情、查用户资料、查字典项、查配置,这类调用天然同构,最适合打包。
- 跨地域、跨可用区调用:RTT 越大,合并的性价比越高。
不建议生搬硬套的场景
批量并非万能,下面几种情况合并了反而添乱:
- 存在链式依赖:后一次调用要用前一次的结果,天然排不了队,强行合并只能等。
- 单条 payload 就很大:比如上传图片、拉大文件,合并后请求体超标,网关直接拒。
- 单条耗时较长的任务:一批里混进一个慢请求,整批都被拖到超时。
- 写操作要求强一致或逐条确认:批量写一旦部分失败,回滚和补偿逻辑会迅速复杂化。
批量大小怎么定才算合理
多数 API 网关和 RPC 框架会对单批条数和请求体大小设上限,常见做法是几十条一档,业内专家指出,批大小要和超时配置、服务端线程模型一起调,并不是越大越省,实践路径是:从二十到五十条起步,压测观察 P99 延迟和服务端 CPU,再往上试探,找到拐点就停。
请求体与响应格式:JSON-RPC 2.0 的几条规范必须守住
请求体就是一个数组
curl -X POST https://api.example.com/rpc \
-H "Content-Type: application/json" \
-d '[
{"jsonrpc":"2.0","method":"getUser","params":{"id":1},"id":1},
{"jsonrpc":"2.0","method":"getUser","params":{"id":2},"id":2},
{"jsonrpc":"2.0","method":"getOrderCount","params":{"uid":1},"id":3}
]'
响应按 id 对齐,顺序不保证
规范明确写了:服务端可以按任意顺序返回结果,客户端必须靠 id 字段配对,不能依赖数组下标。
[
{"jsonrpc":"2.0","result":{"name":"A"},"id":1},
{"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found"},"id":3},
{"jsonrpc":"2.0","result":{"name":"B"},"id":2}
]
通知(notification)不产生响应
数组里如果混进了不带 id 的调用,它属于通知,服务端执行但不返回结果,整批全是通知时,服务端应返回空响应体,空数组 [] 在规范里属于非法请求,会被当作 Invalid Request 处理,客户端封装时记得挡住。
部分失败才是常态
HTTP 状态码 200 不代表这一批全成功了,数组里可能几条成功、几条报错,客户端要逐条检查 error 字段,别只判断状态码就往下走。
跨地域部署下JSON-RPC批量请求的延迟优化
RTT 越高,合并越值
跨地域走公网或专线,一次往返通常在几十毫秒量级,十次串行调用叠起来,光在路上就要几百毫秒,业务处理时间反而成了小头,合成一批之后,总耗时趋近于一次 RTT 加上服务端的串行处理时间,收益非常直观。
超时和重试会被放大
批量请求有个副作用:一批里只要有一条慢,整批都可能超时,超时之后的重试又是整批重发,对下游是成倍的瞬时压力,稳妥做法是给批量接口单独配超时,比单条接口宽松一些,同时保留“拆批重试”的能力整批失败后按 id 拆成小批或单条再试。
一条可验证的落地路径
- 先在聚合层或 BFF 层接入批量,不动核心服务
- 打点记录每批的实际条数分布和响应耗时
- 按 P99 延迟反推批大小上限,写进客户端 SDK
- 对失败批次做拆分重试,并记录拆分率
- 观察一段时间后,再决定是否把批量下沉到更靠下的服务
落地时最容易踩的四个坑
- 把批量当万能药:低延迟内网、单条数据量大、服务端串行处理慢,这三种情况下收益会被吃掉,甚至更慢。
- 忽略服务端上限:请求体超过网关限制会直接被拒,拼批前先查清楚限制。
- 监控维度没跟上:单条调用的耗时监控到批量场景就失真了,要按“批”和“条”两个维度分别看。
- 错误处理写得太松:只判断 HTTP 200、不解析
error,线上出问题会很难查。
关于JSON-RPC批量请求网络往返节省的Q&A
JSON-RPC批量请求能省多少网络往返?
理论上 N 次调用变 1 次往返,省掉 N-1 次,端到端实际能省多少,取决于 RTT 在总耗时里的占比,同机房内网 RTT 很小,收益有限;跨地域或移动网络下 RTT 占大头,合并后耗时往往能降下相当大的比例,具体数字和链路质量强相关,建议用自己的真实链路压测得出。
批量请求与单次请求网络开销对比,是不是批量一定更快?
不一定,行业内普遍认为,批量请求解决的是往返次数问题,不解决服务端处理能力问题,单条数据大、服务端串行执行慢、批内存在长尾慢请求时,合并反而会拉高整体尾延迟,判断标准很简单:先量出 RTT 在总耗时里的占比,占比高就合并,占比低就先优化服务端。
JSON-RPC批量请求的接口调用成本怎么算?
按调用次数计费的场景下,成本下降非常直接,云厂商的 API 网关、部分第三方开放平台,大多按请求次数而非条数计费,原来十次调用算十次,合并后只算一次,带宽成本基本不变,甚至因请求体略增而微涨,通常可以忽略,真正要算进去的是改造和运维成本:客户端封装、拆批重试逻辑、双维度监控,这些才是批量改造的主要投入。
回到最初那个问题:JSON-RPC 批量请求节省的本质是往返次数,而非带宽,它不是所有接口的默认答案,但在高延迟链路、高频小包、同构查询这几类场景里,是投入产出比很高的一招。先把 RTT 占比量出来,再决定合不合并,比拍脑袋改架构靠谱得多。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/776946.html


