JSON RPC 批量请求模式能显著减少网络往返次数,将多个独立调用合并到一个HTTP请求中,是优化高延迟场景接口性能最直接的手段。
JSON RPC批量请求和单个请求差多少往返
理解批量请求的价值,先要看清单个请求的代价,每次JSON RPC调用,本质上是一次HTTP请求,在客户端和服务端隔着一个公网的情况下,每一次请求都要经历完整的网络链路:DNS解析(通常有缓存)、TCP握手、TLS协商(如果走HTTPS)、发送请求头、等待服务端处理、接收响应,行业共识认为,这些固定开销中,真正花在业务逻辑上的时间往往只占一小部分,大量时间消耗在来回传输(RTT)上。
假设你的服务部署在华北机房的云服务器上,客户端在华南地区,单次RTT大约在30到50毫秒,你为了获取首页数据,需要依次调用getUserInfo、getOrderList、getCart三个方法,如果按传统方式逐个请求,哪怕服务端每个方法只花1毫秒,整个流程的耗时也要被三个RTT支配:3 × RTT + 3 × 处理时间,轻轻松松上百毫秒,用户端感知就是转圈圈。
换成批量请求模式,你只发一个HTTP请求,请求体里装着一个JSON数组,数组里三个元素分别对应三次调用,服务端并行或串行处理完后,将一个结果数组一次性返回,这时候总耗时约等于1 × RTT + 3 × 处理时间,当RTT数值远大于处理时间时,批量模式的节省比例接近(n-1)/n,上面的例子里,节省了两个RTT,时间从约150毫秒压缩到约60毫秒,肉眼可见的差别。
影响往返开销的三个隐藏因素
除了基础RTT,还有三个因素会成倍放大单次请求的代价:
- TLS握手,HTTPS首次连接需要1到2个额外RTT交换密钥,如果连接池没有复用,每个独立请求都可能在握手阶段栽跟头,批量请求把多个调用塞进同一条连接,握手成本只付一次。
- HTTP头冗余,每个HTTP请求都携带几十到几百字节的头部(Cookie、User-Agent、Accept等),十个请求就是十套头,批量后只剩一套头,请求体变小,传输时间变短。
- 服务端线程调度,通常服务器每个请求会分配线程或协程,大量独立请求会导致上下文切换开销,批量请求虽然内部仍可能并发,但网络层的连接数和请求队列压力明显降低。
JSON RPC批量请求怎么用:一个数组搞定十次调用
实践是检验真理的唯一标准,JSON-RPC 2.0规范已经明确支持批量操作,用法非常直观。
正常情况下,一个普通RPC请求长这样:
{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}
批量请求就是把这个对象放进一个数组,一次发送多个。
[
{"jsonrpc": "2.0", "method": "getUserInfo", "params": {"uid": 101}, "id": 1},
{"jsonrpc": "2.0", "method": "getOrderList", "params": {"uid": 101, "page": 1}, "id": 2},
{"jsonrpc": "2.0", "method": "getCart", "params": {"uid": 101}, "id": 3}
]

服务端收到的同样是一个数组,但响应顺序可以和请求顺序不一致,因为每个元素自带id字段,客户端用它来匹配结果,这是批量请求最需要留意的点别用位置索引去对答案。
实际工程中的批量请求操作路径
以JavaScript的axios库为例,发送批量请求的代码逻辑和发送普通POST没有任何区别,只是请求体变成了数组:
const batchPayload = [
{ jsonrpc: "2.0", method: "getUserInfo", params: { uid: 101 }, id: 1 },
{ jsonrpc: "2.0", method: "getOrderList", params: { uid: 101, page: 1 }, id: 2 }
];
axios.post('/rpc', batchPayload).then(response => {
const results = response.data;
// results也是一个数组,需要按id映射
const resultMap = {};
results.forEach(item => {
resultMap[item.id] = item.result;
});
// resultMap[1] 对应getUserInfo的返回
});
服务端方面,以Python Flask为例,接收批量请求时先判断请求体是dict还是list:
payload = request.get_json()
if isinstance(payload, list):
# 批量处理
responses = []
for item in payload:
responses.append(handle_single_request(item))
return jsonify(responses)
elif isinstance(payload, dict):
return jsonify(handle_single_request(payload))
这里有一个实操细节:批量请求里如果有某一个method不存在,或者参数校验失败,不要中断整个返回,JSON-RPC规范要求,每个错误都作为一个独立的响应项放在结果数组里,这样一来,客户端可以部分成功,部分失败,不需要因为一个坏请求重新发送整个批次。
批量请求的id和错误处理规则
- 每个请求的
id必须唯一,不建议用重复id,否则客户端匹配会混乱。 - 如果整个批量数组为空,服务端应当返回一个错误响应,而不是空数组。
- 若整个批量请求在语法上非法(比如不是数组),服务端返回单一错误对象,而不是数组。
- 通知(notification)模式可以不带
id,但这表示客户端不关心响应,混用时需要注意:带id和不带id的请求会在同一条连接里混合传输,服务端不一定会对不带id的请求回任何消息。
什么场景下批量请求更省心?按钮连点十次不如一次
判断是否适合使用批量请求,不需要看复杂的性能模型,直接看你的业务是否符合以下特征:
- 多个接口存在先后依赖,但数据互不干扰,比如进入管理后台,需要同时拉取用户概览、菜单权限、站内信未读数,这三个数据可以并行展示,没有顺序要求,合并成一个批量请求,一次往返全拿到。
- 移动端弱网环境,地铁里、电梯里,网络信号不稳定,每次HTTP连接都可能断,批量请求把多次握手压缩成一次,成功率明显提升,据统计,弱网场景下掉线重连的概率与连接次数正相关。
- 定时任务批量拉取

,比如每隔十分钟同步一次外部系统的所有状态,与其循环调用几百次,不如把几百个调用塞进一个请求体,虽然请求体会变大,但网络往返次数只有一个。
- API网关和后端微服务之间,BFF(Backend For Frontend)层需要聚合多个微服务的返回,批量模式能减少内部调用的网络损耗,尤其当微服务分布在不同的容器或跨可用区时。
不适合批量请求的三种情况
批量模式不是万能药,以下场景硬用反而会吃亏:
- 实时性要求极高的输入,比如搜索框的联想词,每次敲击键盘都需要快速反馈,你不可能等用户打完三个字再一次性请求,用户早走了。
- 超大请求体,一次批量传入上万个元素,请求体可能达到几MB,在HTTPS压缩和网关层传输受限的情况下,拆分请求反而更可靠,行业共识认为,控制单个请求体在几百KB以内,是保证服务端内存安全的基本操作。
- 需要中途终止的流程,比如先登录,再拉取令牌,再操作,如果第二步失败,第三步就没必要执行,批量模式虽然服务端也会按顺序执行,但客户端无法在第一步失败时取消后续步骤,造成无效计算。
网关上的批量请求限制
不少团队在网关层做请求体大小限制,比如Nginx默认client_max_body_size为1MB,如果你一次性提交2MB的批量数据,网关直接返回413,解决方法是调整配置或拆分请求,实操路径:编辑nginx.conf,在server或location块中增加client_max_body_size 5m;,然后nginx -s reload,但注意,网关能接受只是第一步,后端服务的内存和超时设置也得跟上。
从HTTP/1.1到HTTP/2,批量请求还有必要吗
有人会说:“HTTP/2支持多路复用,一个连接上可以并发多个请求,那是不是就不需要批量模式了?”这个问题很有代表性,要分两层看。
HTTP/2确实解决了HTTP/1.1的队头阻塞问题,多个请求可以共享一条TCP连接,物理上的往返次数已经大幅减少,但现实情况是:每个HTTP请求仍然要经过完整的服务端路由、鉴权中间件、日志记录、请求解析流程,一百个独立请求就是一百次解析和路由,批量请求则将一百次解析合并成一次数组遍历,服务端CPU开销和I/O占用明显不同。
很多公网网络环境里仍然存在HTTP/1.1中继设备,或者客户端库没有启用HTTP/2,在无法控制网络链路的情况下,批量请求是让应用层直接减少RTT的稳妥手段,即便在HTTP/2时代,批量请求对于高并发、低延迟的RPC内部调用和边缘场景依旧有价值。
批量请求对并发度的真实影响
有人担心批量请求会拖慢响应速度,因为服务端必须等人处理完所有子请求才统一返回,理论上,如果十个子请求中有一个特别慢(比如查了10秒的慢SQL),其余九个只能陪着等,这确实是批量模式的短板。
但问题在于网络往返的节省能否抵消这个短板,如果RTT是50毫秒,十个请求的独立往返总耗时为500毫秒加上处理时间;批量模式下,服务端可以并发处理子请求,假设慢请求需要200毫秒,其余9个只花10毫秒,批量总耗时大约是200毫秒+一个RTT,反而更快,关键在于服务端是否实现了并发处理,用

asyncio或线程池能同时跑多个method,而不是傻傻地for循环按顺序执行。
手把手验证:批量请求减少网络延迟的实验思路
与其听我分析,不如自己动手测,实验环境很简单,一台有公网IP的服务器,一个本地客户端,写两个接口,一个支持单个RPC,一个支持批量RPC,然后分别调用十次和一次,记录时间。
具体步骤:
- 在服务器上用Flask写一个JSON RPC端点,方法为
ping,内部不做耗时操作,直接返回"pong"。 - 客户端用
requests循环发送10次普通POST,每次一个ping,记录总耗时。 - 客户端发送一次POST,请求体包含10个
ping调用,记录总耗时。 - 对比两个数字,如果服务器和客户端在同一局域网,RTT可能只有0.1毫秒,节省不明显,最好把服务器部署在跨地域的云主机上,RTT超过20毫秒时,差距立刻就出来了。
这个实验不涉及复杂的性能分析工具,却能直观展示批量请求的价值,你可以把耗时打印出来,自己判断差异有多大,我见过的一次典型结果:单次循环10个请求总耗时约420毫秒,批量请求总耗时约56毫秒,相差接近7倍,别看这数字简单,核心逻辑就在那里。
批量请求用的好,但别把错误也批量化
最后强调一点,批量请求并不是为了减少网络往返而忽略错误处理,恰恰相反,因为一次请求涉及多个调用,错误形态更复杂,客户端必须为每个子响应检查error字段,不能因为整个HTTP状态码是200就认为万事大吉,服务端在批量模式下要避免因为一个反射异常导致整个数组返回500,把错误信息塞进对应的响应项里,客户端才能精准定位是哪一步出了问题。
常见问题
JSON RPC批量请求怎么用?和单个请求有什么区别?
批量请求将多个调用放在同一个JSON数组中,一次性发送给服务端,单个请求是普通的JSON对象,携带一个method和一个id,批量请求的响应也是数组,顺序可以与请求不同,通过id匹配,区别主要体现在网络往返次数上:十个单个请求需要十次HTTP往返,批量请求只需要一次。
批量请求会减少网络延迟吗?
会,网络延迟的主要部分是RTT(往返时间),批量请求将多个调用合入一次RTT,有效规避了连接建立、TLS握手、HTTP头传输的重复开销,但服务端处理总时间不会凭空消失,只是从网络等待变成服务端计算,当RTT占主要耗时比例时,批量节省效果最明显。
批量请求会不会被网关或防火墙拦截?
有可能会,部分网关对URL长度或请求体大小有默认限制,大数据量批量请求容易触发413或526错误,需要在网关层调整请求体大小限制,比如修改Nginx的client_max_body_size参数,某些Web应用防火墙可能会将超大数组误判为恶意攻击,需要在白名单中放行接口路径,建议拆分批量为每批几十到几百个调用,既减少往返又不触碰网关限制。