Q18 · Web3 场景优化

前端调用链上接口 / RPC 时,如何减少延迟和资源消耗?

RPCmulticall缓存

⚡ 速记答案(30 秒)

  • 合并请求:能用 multicall / batch RPC 就不用一堆单次请求
  • 做缓存:不变或变化慢的链上数据用内存缓存 + 本地缓存(IndexedDB / localStorage)
  • 节流:输入框 / 滚动 / 拖拽等操作触发的查询加防抖 / 节流
  • 失败重试:对超时 / 暂时性错误做指数退避重试,而不是疯狂重新请求

📖 详细讲解

RPC 优化策略


1. Multicall 合并请求


将多个合约调用合并成一次 RPC 请求:


• 减少网络往返

• 获得一致的区块状态

• 降低 RPC 节点压力


2. 缓存策略


数据类型缓存策略
合约常量永久缓存
余额/状态短期缓存 + 事件更新
历史数据持久缓存
价格数据不缓存或极短缓存

3. 请求去重


• 相同参数的请求合并

• 使用 SWR / React Query 自动去重


4. 失败处理


• 指数退避重试

• 备用 RPC 节点

• 超时设置

💻 代码示例

Multicall 示例
import { Contract, Provider } from 'ethers';

// 使用 Multicall 合约批量查询
async function batchGetBalances(
  provider: Provider,
  tokenAddress: string,
  accounts: string[]
) {
  const multicallAddress = '0x...'; // Multicall3 地址
  const multicall = new Contract(multicallAddress, MULTICALL_ABI, provider);
  
  const token = new Contract(tokenAddress, ERC20_ABI);
  
  // 构建调用数据
  const calls = accounts.map(account => ({
    target: tokenAddress,
    callData: token.interface.encodeFunctionData('balanceOf', [account]),
  }));
  
  // 一次 RPC 调用获取所有余额
  const [, results] = await multicall.aggregate.staticCall(calls);
  
  return results.map((data: string) => 
    token.interface.decodeFunctionResult('balanceOf', data)[0]
  );
}
缓存 + 去重
const cache = new Map<string, { data: any; timestamp: number }>();
const pending = new Map<string, Promise<any>>();

async function cachedRpcCall<T>(
  key: string,
  fetcher: () => Promise<T>,
  ttl: number = 5000
): Promise<T> {
  // 检查缓存
  const cached = cache.get(key);
  if (cached && Date.now() - cached.timestamp < ttl) {
    return cached.data;
  }
  
  // 检查是否有正在进行的请求(去重)
  if (pending.has(key)) {
    return pending.get(key)!;
  }
  
  // 发起请求
  const promise = fetcher().then(data => {
    cache.set(key, { data, timestamp: Date.now() });
    pending.delete(key);
    return data;
  });
  
  pending.set(key, promise);
  return promise;
}
💡
面试技巧:回答性能问题时,先说指标和标准,再讲优化手段,最后结合实际项目经验。