Q18 · Web3 场景优化
前端调用链上接口 / RPC 时,如何减少延迟和资源消耗?
⚡ 速记答案(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;
}面试技巧:回答性能问题时,先说指标和标准,再讲优化手段,最后结合实际项目经验。