JSON-RPC 批量请求是在单个 HTTP POST 中发送的请求对象数组。每个请求必须具有唯一的 id,服务器按相同顺序返回响应数组,并包含逐项错误。批量请求减少了往返次数,适用于多个独立的读取操作,但不会减少服务器负载或保证原子性。对于 eth_getLogs,批量请求与分页结合可高效扫描大范围数据。
什么是 JSON-RPC 批量请求?
JSON-RPC 批量请求是一个单一的 HTTP POST,其请求体是一个包含多个请求对象的 JSON 数组。每个对象遵循标准的 JSON-RPC 2.0 结构:jsonrpc、method、params 和 id。服务器处理所有请求,并返回一个响应对象数组,每个请求对应一个响应,顺序与请求一致。这一定义在 JSON-RPC 2.0 规范 中有所说明。
对于以太坊和其他 EVM 链,OnFinality 等提供商提供的 JSON-RPC API 支持批量请求。无需发送 10 个单独的 eth_blockNumber 调用,只需发送一个包含 10 个项目的请求。这减少了网络开销,并能显著降低需要多个独立数据点的应用程序的延迟。
- 批量请求是数组:
[ {...}, {...} ] - 每个项目必须具有唯一的
id(数字或字符串) - 响应是结果/错误的数组,顺序与请求相同
- 如果整个批次无效(例如空数组),服务器返回单个错误对象
curl -X POST https://eth.api.onfinality.io/public \
-H 'Content-Type: application/json' \
-d '[
{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1},
{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":2},
{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x742d35Cc6634C0532925a3b844Bc454e4438f44e","latest"],"id":3}
]'批量请求与响应的结构
批次中的每个请求对象必须是有效的 JSON-RPC 请求。id 用于关联响应,但由于服务器按相同顺序返回响应,您可以依赖位置匹配。然而,规范建议使用唯一的 id 以便清晰和调试。
响应是响应对象的数组。每个响应具有 result 或 error 字段。如果某个请求失败(例如方法未找到、参数无效),服务器仍会返回带有 error 和 null 结果的响应对象,其他请求不受影响。这称为错误隔离。
以下是上述批次的示例响应。请注意,第三个请求使用了无效地址,因此返回错误,但前两个成功。
[
{"jsonrpc":"2.0","result":"0x134a5c","id":1},
{"jsonrpc":"2.0","result":"0x1","id":2},
{"jsonrpc":"2.0","error":{"code":-32602,"message":"invalid argument 0: hex string has length 42, want 40 for common.Address"},"id":3}
]何时使用批量请求(以及何时不使用)
当您有多个可以并行执行的独立读取调用时,批量请求非常有用。例如,获取多个地址的余额、获取多个区块的详细信息或检查交易收据。通过将它们合并到一个 HTTP 请求中,您可以将往返次数从 N 减少到 1,这在高延迟连接上尤其有益。
然而,批量请求不会减少服务器端的工作量。每个请求都是单独处理的,因此总计算量相同。它也不提供原子性:如果某个请求失败,其他请求仍会执行。如果您需要确保全有或全无的行为,则必须在应用程序层面处理。
避免对依赖调用进行批量处理,因为一个调用的结果可能是另一个调用的输入。例如,您不能批量处理 eth_getTransactionCount 和依赖于该 nonce 的 eth_sendRawTransaction。您必须等待第一个响应。
此外,请注意批次大小。大多数提供商(包括 OnFinality)对每批请求的数量施加了最大限制(通常为 100-200)。发送过大的批次可能会导致 413 Payload Too Large 或 JSON-RPC 错误。请查阅提供商的文档或先使用小批次进行测试。
- 适合:多个
eth_getBalance调用、多个eth_getBlockByNumber调用、不同范围的eth_getLogs - 不适合:依赖调用、需要 nonce 的写入、需要顺序执行的调用
- 批次大小限制:为安全起见,保持在 100 项以下,或检查提供商限制
使用批量 eth_getLogs 进行分页
eth_getLogs 是一个强大但可能昂贵的调用。它返回与过滤器匹配的所有日志,如果范围过大,节点可能会超时或返回类似 query returned more than 10000 results 的错误。为了处理大范围,您需要进行分页:将范围拆分为较小的块,并为每个块获取日志。
批量请求与分页完美配合。无需按顺序发送每个块,您可以在一个批次中发送多个 eth_getLogs 请求,每个请求具有不同的 fromBlock 和 toBlock。这可以并行化扫描并减少总时间。
例如,要扫描特定合约的 1,000,000 到 1,000,999 区块,您可以将其拆分为 10 个块,每块 100 个区块,然后批量发送。响应将包含每个块的日志,您可以按顺序连接它们。
这种方法在社区资源中有所记录,例如 Chainstack 关于 eth_getLogs 限制的指南 和 sqd.dev 关于 eth_getLogs 分页的文章。
curl -X POST https://eth.api.onfinality.io/public \
-H 'Content-Type: application/json' \
-d '[
{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0xf4240","toBlock":"0xf42c0","address":"0x..."}],"id":1},
{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0xf42c0","toBlock":"0xf4340","address":"0x..."}],"id":2},
{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0xf4340","toBlock":"0xf43c0","address":"0x..."}],"id":3}
]'批量请求与 Multicall:该用哪个?
Multicall 是一个智能合约,它将多个 eth_call 请求聚合到单个调用中。由于两者都减少了往返次数,因此经常被比较。然而,它们有根本的不同。
批量请求由节点的 JSON-RPC 服务器处理,每个调用独立执行。Multicall 在单个 EVM 执行中执行多个合约调用,对于只读调用可能更高效,因为它避免了多次 JSON-RPC 调用的开销。然而,Multicall 需要部署或使用已知的 Multicall 合约地址,并且仅适用于 eth_call(不适用于 eth_getBalance 或 eth_getLogs)。
在实践中,对于简单的余额检查或合约读取,批量请求更简单、更灵活。对于许多合约调用的复杂聚合,Multicall 可能更快,因为它减少了 EVM 执行的次数。选择取决于您的用例。如果您需要使用不同参数多次调用同一合约,Multicall 通常更好。如果您需要混合不同的方法(例如 eth_getBalance 和 eth_call),批量请求是更好的选择。
有关优化 RPC 调用的更多信息,请参阅我们的指南 如何降低 RPC 延迟。
诊断批量请求失败
当批量请求失败时,如果批次本身格式错误(例如空数组、无效 JSON),整个响应可能是一个错误对象。如果个别请求失败,您会得到逐项错误。常见错误包括 -32600(无效请求)表示缺少 jsonrpc 或 method,-32601(方法未找到)表示拼写错误,以及 -32602(无效参数)表示参数类型错误。
要进行诊断,首先测试单个请求以确保其正常工作。然后测试两个请求的批次。使用 curl -w 测量时间,看看批量处理是否真的改善了延迟。例如:
curl -w 'Total time: %{time_total}s\n' -X POST ... -d '[single]' 与 -d '[batch]' 对比。
如果您看到 413 Payload Too Large,请减小批次大小。如果您看到来自提供商的 -32005(超出限制),则可能达到了速率限制或批次大小限制。请查看 OnFinality RPC 助手 获取端点特定指导。
# 测量单个请求的延迟
curl -w 'Single: %{time_total}s\n' -X POST https://eth.api.onfinality.io/public \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
# 测量 5 个请求批次的延迟
curl -w 'Batch: %{time_total}s\n' -X POST https://eth.api.onfinality.io/public \
-H 'Content-Type: application/json' \
-d '[
{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1},
{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":2},
{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":3},
{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":4},
{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":5}
]'批量请求的限制与权衡
虽然批量请求减少了网络开销,但不会减少服务器总负载。批次中的每个请求都是单独处理的,因此 100 个 eth_getLogs 调用的批次与 100 个单独调用一样昂贵。提供商可能会限制或拒绝大型批次以保护其基础设施。
另一个权衡是错误处理。使用批量请求,您必须单独解析每个响应并处理部分失败。这增加了代码的复杂性。此外,如果批次中的某个请求格式错误,服务器可能会拒绝整个批次(取决于实现)。JSON-RPC 规范规定,如果批次无效(例如空数组),服务器返回单个错误对象,但如果个别项目无效,则单独处理。
最后,批量请求不保证服务器上的执行顺序。规范规定服务器可以按任何顺序执行请求,但响应按请求顺序返回。对于只读调用,这没问题,但对于写入,您不能依赖顺序。
有关处理超时和错误的更多信息,请参阅 如何修复 RPC 超时错误。
后续步骤:优化您的 RPC 使用
既然您了解了批量请求,就可以将其应用于您的 dApp 或脚本,以减少延迟并提高效率。首先确定可以批处理的独立调用,并使用 curl -w 测量改进。
如果您在以太坊、Polygon 或 Base 上构建,请查看相应网络页面以获取端点详细信息:以太坊、Polygon、Base。有关端点的完整列表,请参阅我们的 多链 RPC 端点指南。
如果您需要更高的吞吐量或专用基础设施,请考虑我们的 RPC 定价计划 或 API 服务 以获取高级功能。对于历史数据访问,请阅读我们的指南 访问历史区块链数据。