本文详细介绍了如何优化区块链RPC调用的最佳实践,包括选择正确的RPC方法、优化请求、使用实时数据解决方案、正确处理API响应、保护端点等,旨在帮助开发者构建更快、更经济的区块链应用。
在区块链之间获取和传输数据需要在你的系统和网络之间传输大量信息。为了优化你的 QuickNode 端点的性能,确保你的 dApps 运行顺畅,同时最小化成本,实施高效的 RPC 调用至关重要。本指南概述了向网络发出有效 RPC 调用的最佳实践,帮助你构建更快、更具成本效益的区块链应用程序:
欢迎收藏此页面以备将来参考。如果你有任何问题,请随时通过 Discord 或 Twitter 联系我们。
进行有效 RPC 调用的第一步是确保你使用了正确的调用——并且正确使用它们。在进行 API 请求时,请记住以下关键点:
为了充分利用你的 RPC 调用,请熟悉 QuickNode 的 API 文档,包括每个网络支持的所有方法的最新列表。由于 API 会随着频繁的升级而演变,请务必定期参考文档以了解变化。
不同的 RPC 方法适用于不同的用例,即使它们看起来相似——它们可能会产生不同的响应并具有不同的 成本乘数。选择正确的方法是提高效率的关键。例如,eth_getTransactionCount
用于获取从某个地址发送的交易数量,而 eth_getTransactionByHash
用于获取特定交易哈希的交易详情。使用错误的方法可能会导致不必要的网络流量和更高的成本,因此在选择之前请仔细研究。
另一个减少 RPC 请求数量的机会是在使用 Ethers.js 时使用正确的类实例。例如,JsonRpcProvider 在每个 RPC 调用之前会发送一个或两个 eth_chainId
请求,而 StaticJsonRpcProvider 则不会。如果你正在处理大量的请求,这种差异可能会显著影响性能。
一旦你选择了正确的 RPC 调用,请记住以下最佳实践以提高性能并降低成本。
在进行检索数据的 API 调用时,重要的是在查询中使用过滤器以限制返回的数据量。
以太坊示例:如果你使用 eth_getLogs
来获取特定合约的所有日志,你可以使用 fromBlock
和 toBlock
参数来限制你搜索的区块范围。这将减少返回的数据量。你还可以使用 address
参数将日志限制为特定合约地址。
Solana 示例:如果你在 Solana 上使用 getProgramAccounts
,考虑利用 GetProgramAccountsFilter 来按特定字节大小过滤账户(使用 {dataSize: SIZE}
),按序列化账户数据中的特定位置过滤账户(使用 {memcmp: {offset: LOCATION}}
),或按特定值过滤账户(使用 {memcmp: {bytes: SEARCH_VALUE}}
)。在查询中添加过滤器,而不是检索所有账户(并在客户端进行过滤),可以减少返回的数据量并提高整体性能。有关使用 GetProgramAccountsFilter 的更多信息,请查看 本指南。
优化交易是确保它们在 Solana 网络上成功处理的关键,尤其是在高需求时期。
要了解更多关于提高交易性能和可靠性的信息,请阅读我们的指南:优化 Solana 交易的策略。
批量请求是同时处理多个 RPC 调用的单个请求。具体来说,它是一个 POST 请求,包含在数据元素中的 RPC 方法调用数组。虽然批量处理在某些情况下很有用,但它并不总是处理多个请求到你的端点的最有效方法。根据用例的不同,批量处理实际上可能会由于以下几个因素而减慢你的 dApp 的性能:
200
响应代码而某些请求失败可能会产生误导。请记住,所有对你的端点的请求都会计入你的 RPS 限制,将它们作为批处理提交并不会减少总请求数。一般来说——尤其是在使用批处理时——我们建议在你的端实施速率限制以监控使用情况并保持在你的 RPS 限制内。我们将在 实施速率限制 部分详细探讨其原因以及如何设置速率限制。
与其直接进行批量请求,不如考虑以下替代方法来优化以太坊和 EVM 兼容网络的效率和响应时间。
对于像 eth_blockreceipts
这样的批量读取操作,Single Flight RPC 插件 是一个强大的工具。它安全地将多个 RPC 调用捆绑到一个请求中,减少开销并节省成本,同时提高效率。这种方法可以让你更快地检索区块和交易信息,并简化复杂的数据检索场景。要了解更多关于使用 Single Flight RPC 的信息,请访问 QuickNode 市场 或参考 如何使用 QuickNode Single Flight RPC 指南。
你还可以探索在智能合约级别减少 RPC 负载。利用 Multicall3 可能有助于将多个调用批处理为单个请求,尽管效果可能因你的具体用例而异。我们的 如何通过 Multicall 优化以太坊 RPC 使用 指南提供了关于如何有效实施此优化的详细说明。
如果你正在使用 Solana,我们建议尽可能捆绑你的交易指令。这使你可以在一个 sendTransaction
请求中完成多个任务。Solana 运行时将以原子方式处理交易中包含的每个指令——如果任何部分失败,则整个交易将失败。
在 Solana 上,交易指令可以通过 Transaction.add()
附加到单个交易中。
例如,如果你想同时向网络发送两个指令,你可以通过两种方式实现:
const bulkTx = new Transaction();
const ix1 = new TransactionInstruction(opts:{/* your instruction */});
const ix2 = new TransactionInstruction(opts:{/* your instruction */});
// 选项 A:将两个指令添加到同一交易中
bulkTx.add(ix1, ix2);
await connection.sendTransaction(bulkTx,[/* your signers */]);
const indivTx1 = new Transaction();
const indivTx2 = new Transaction();
// 选项 B:将每个指令添加到单独的交易中
indivTx1.add(ix1);
indivTx2.add(ix2);
await connection.sendTransaction(indivTx1,[/* your signers */]);
await connection.sendTransaction(indivTx2,[/* your signers */]);
第一种方法使用单个交易将两个指令发送到网络,而第二种方法使用两个单独的交易。第一种方法更高效,因为它只需要向网络发出一个请求,而第二种方法需要两个。有关如何将指令捆绑到单个交易中的更详细示例,请参阅我们的 指南:如何在 Solana 上发送批量交易。
分页是一种将大数据集分解为更小、更易管理的部分并以一系列请求检索这些部分的技术。在 RPC 调用的上下文中,分页可以限制单个 API 响应中返回的数据量,这有助于减少响应时间并提高你的 dApp 的性能。假设你想检索区块链上特定账户的所有交易列表。如果该账户有数千甚至数百万个 NFT,在单个 API 调用中检索所有 NFT 会很慢。相反,你可以使用分页以较小的块检索 NFT。
以下是 QuickNode 的 Token 和 NFT API v2 包利用分页的示例:
const axios = require("axios");
(async () => {
const QUICKNODE_ENDPOINT = "YOUR_QUICKNODE_ENDPOINT"; // 替换为你的 QuickNode 端点
const requestData = {
jsonrpc: "2.0",
id: 1,
method: "qn_fetchNFTs",
params: [\
{\
wallet: "0x91b51c173a4bdaa1a60e234fc3f705a16d228740",\
omitFields: ["traits"],\
page: 1,\
perPage: 10,\
contracts: [\
"0x2106c00ac7da0a3430ae667879139e832307aeaa",\
"0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D",\
],\
},\
],
};
const config = {
headers: {
"Content-Type": "application/json",
},
};
try {
const response = await axios.post(QUICKNODE_ENDPOINT, requestData, config);
console.log(response.data); // 记录 API 的响应
} catch (error) {
console.error(
"Error fetching NFTs:",
error.response ? error.response.data : error.message
);
}
})();
在上面的示例中,我们正在获取特定 NFT 地址的钱包地址持有的 NFT。page
参数用于指定返回结果的页面,perPage
参数用于指定每页返回的结果数量。在本例中,我们每页返回 10 个结果。这可能在你显示用户 NFT 的应用程序中有用。通过分页,你可以增加加载时间并减少单个 RPC 调用中返回的数据量。
缓存是一种将频繁使用的 API 数据存储在内存中的技术,以便可以重复使用而无需进行新的 API 调用。当你缓存 API 数据时,你会在你的系统上创建数据的本地副本,你可以比每次需要时从远程 API 检索数据更快地访问它。这对于减少向网络发出的 API 调用数量非常有用。
想象一下,例如,你有一个 dApp 获取用户的 NFT 并将其显示在页面上。如果你使用缓存策略,你可以将用户的 NFT 存储在内存中,这样就不必在每次页面渲染组件时进行新的 API 调用。这对于减少向网络发出的大量 API 调用非常有用。注意:这并不适用于所有用例,你应该只在不需要从网络刷新数据时使用缓存。
以下是如何在 React 应用中使用 localStorage
缓存 RPC 结果的简单示例:
import { useState, useEffect } from 'react';
function LargestAccounts() {
const [accounts, setAccounts] = useState([]);
useEffect(() => {
async function fetchData() {
const cachedData = localStorage.getItem('largestAccountsCache');
if (cachedData) {
setAccounts(JSON.parse(cachedData));
} else {
const response = await connection.getLargestAccounts({/*YOUR SEARCH CRITERIA*/});
const data = await response.json();
setAccounts(data);
localStorage.setItem('largestAccountsCache', JSON.stringify(data));
}
}
fetchData();
}, []);
return (
<div>
{accounts.map((account) => (
<div key={account.id}>
{account.name} - {account.balance}
</div>
))}
</div>
);
}
export default LargestAccounts;
在这个示例中,我们使用 useEffect
钩子在组件挂载时获取最大的账户数据。我们首先使用 localStorage.getItem()
检查数据是否已经存储在本地存储中。如果是,我们将账户状态设置为缓存的数据。如果不是,我们进行 RPC 调用并将账户状态设置为获取的数据。我们还使用 localStorage.setItem()
将获取的数据存储在本地存储中,以便下次组件挂载时可用。
为了构建高效和响应迅速的应用程序,利用实时数据解决方案至关重要。传统的 RPC 请求通常涉及持续的轮询,这可能会导致不必要的开销和延迟。相反,像 Streams 和 WebSocket 订阅 这样的实时数据解决方案允许你的应用程序在特定事件发生时立即接收更新,从而减少延迟并提高性能。
Solana 解决方案
在 QuickNode 的博客文章中发现 QuickNode 的优化解决方案,以访问实时的 Solana 区块链数据,并找到最适合你需求的解决方案:访问实时 Solana 数据:3 种工具比较。
QuickNode 的 Streams 功能提供了一种强大的方式来访问实时数据,并具有高级过滤功能。通过定义精确的条件,你可以确保你的应用程序仅接收它需要的数据,从而最大限度地减少不必要的数据检索并降低成本。Streams 兼容多个区块链,包括 以太坊 和 Solana,使其成为处理实时数据的多功能选择。要探索如何在你的项目中实施 Streams,请查看我们全面的 Streams 指南 和 Streams 文档。
除了 Streams,你还可以使用 WebSocket 订阅来获取实时数据。WebSocket 可用于许多区块链,包括以太坊和 Solana,从而实现高效通信,而无需持续轮询。
以下是一些有用的资源,帮助你开始使用 WebSocket 订阅:
一旦你进行了 RPC 请求,你必须正确处理响应。在处理 RPC 响应时,请记住以下几点:
当你进行 RPC 请求时,响应将包括一个状态代码,指示请求是否成功。重要的是检查响应并适当地处理响应。例如,如果响应返回错误,你知道请求不成功,你应该适当地处理错误。
有关错误代码及其含义的详细列表,请参阅我们 API 文档 中针对你的网络的 错误代码参考 页面。你可以在此处找到以太坊错误代码 这里 和 Solana 错误代码 这里。
当请求失败时,优雅地处理失败,并避免多次重试同一请求。尽管返回错误,持续发送请求可能会导致你被速率限制,这可能会中断你的 dApp 的用户体验。
以下是一个 React 组件的示例,它在请求失败时重试请求,但将重试次数限制为 3 次:
const MAX_RETRIES = 5;
const fetchData = async (retries = 0) => {
try {
const result = await axios.get('https://api.example.com/data');
setData(result.data);
} catch (error) {
if (retries < MAX_RETRIES) {
// 延迟后重试调用
setTimeout(() => fetchData(retries + 1), 1000);
} else {
// 达到最大重试次数。处理错误。
}
}
};
useEffect(() => {
fetchData();
}, []);
断路器是一种防止你的 dApp 向网络发出过多请求的方法。你可以实施一个断路器,如果满足某个触发条件,它将停止向网络发出请求。
以下是一个断路器的示例,如果在给定时间段内失败的请求数量超过 3
,它将停止发出请求:
class CircuitBreaker {
constructor(threshold, timeout) {
this.threshold = threshold; // 在断路器断开之前允许的最大失败请求数
this.timeout = timeout; // 保持断路器断开的时间(以毫秒为单位)
this.failures = 0; // 失败的请求数
this.isBroken = false; // 断路器当前是否断开
this.lastFailure = null; // 上次失败请求的时间
}
async callApi() {
// 如果断路器断开,检查是否可以关闭它
if (this.isBroken) {
const now = new Date().getTime();
const timeSinceLastFailure = now - this.lastFailure;
if (timeSinceLastFailure > this.timeout) {
// 断路器断开时间足够长。再次尝试
this.isBroken = false;
} else {
// 断路器仍然断开,返回缓存响应或错误消息
return this.getCachedResponse();
}
}
try {
// 调用 API 并返回响应
const response = await fetch('https://api.example.com/data');
const data = await response.json();
// 如果成功,重置失败计数
this.failures = 0;
return data;
} catch (error) {
// 增加失败计数并检查是否应断开断路器
this.failures++;
if (this.failures >= this.threshold) {
this.isBroken = true;
this.lastFailure = new Date().getTime();
}
// 返回缓存响应或错误消息
return this.getCachedResponse();
}
}
getCachedResponse() {
// 返回缓存响应或错误消息
return { message: 'API
>- 原文链接: [quicknode.com/guides/qui...](https://www.quicknode.com/guides/quicknode-products/apis/guide-to-efficient-rpc-requests)
>- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!