如何批量调用以太坊不同区块的读函数以加快历史链上数据获取速度
标准Multicall合约仅支持同一区块高度下的多合约调用批量执行,确实无法满足跨不同区块高度的读请求批量发送需求,针对你的场景可按以下层级选择优化方案:
即时可落地的优化方案
- 替换串行请求为并行+RPC批量调用
普通JSON-RPC本身支持批量请求,你可以把多个eth_call请求打包在单次HTTP请求中发送,大幅减少TCP握手、网络往返的开销。以ethers.js为例,可以直接使用provider.sendBatch方法实现:
// 先预处理所有需要调用的区块高度和对应时间 const callRequests = [] const blockTimeMap = new Map() const secondsPerDay = 60 * 60 * 24; let day = (await provider.getBlock(startingBlock)).timestamp; for (let currentBlock = startingBlock; currentBlock <= lastBlock; currentBlock += blocksPerDay) { // 构造eth_call参数 const callData = contract.interface.encodeFunctionData("getData") callRequests.push({ jsonrpc: "2.0", id: currentBlock, method: "eth_call", params: [{ to: contract.address, data: callData }, ethers.utils.hexValue(currentBlock)] }) blockTimeMap.set(currentBlock, day) day += secondsPerDay } // 批量发送请求(可根据RPC速率限制拆分批次,避免触发限流) const responses = await provider.sendBatch(callRequests) // 处理返回结果 const result = responses.map(res => { const value = contract.interface.decodeFunctionResult("getData", res.result)[0] return {x: blockTimeMap.get(res.id), y: fromWeiNumber(value)} })
- 修正区块高度计算逻辑
你当前硬编码20s出块的规则误差极大,以太坊合并后出块时间固定为12s,还存在slot跳过的情况,直接按固定步长加区块高度会导致数据和日期对应不上。可以优先选择支持eth_getBlockByTimestamp方法的RPC服务商,直接传入每日0点的时间戳获取对应区块,比自行计算准确高效。
生产级别的优化方案
类似Poocoin这类链上图表产品不会直接面向RPC节点拉取历史数据,都会采用提前索引的方案:
- 部署专属索引服务:可以用子图工具,或者自行开发索引脚本,从链上同步所需的合约数据,按时间维度预存到MySQL、ClickHouse这类数据库中,用户请求图表时直接从数据库读取,响应速度可以做到毫秒级。
- 缓存热点数据:对于已经查询过的历史日期数据,用Redis做一层缓存,后续相同请求不用再去链上拉取。
Archive节点相关说明
如果你需要查询128个区块之前的历史状态,普通的全节点不会保留历史状态快照,必须使用Archive节点:
- 不需要自行搭建,现在主流的第三方RPC服务商都提供付费的Archive节点服务,直接开通后调用即可,成本比自行维护节点低很多。
- 如果数据查询量极大、对延迟要求极高,再考虑自行搭建Archive节点,推荐使用Erigon客户端,存储占用和同步速度都优于Geth的归档模式。
内容的提问来源于stack exchange,提问作者Peter Slaný
相关产品推荐
相关产品推荐

