Monad RPC 端点按 IP 和 API 密钥实施速率限制,公共免费层限制最严格。循环调用 eth_getBalance 或无界 eth_getLogs 等重负载模式可能触发 429 错误。本文介绍如何识别限制范围、通过订阅和批处理减少请求量、缓存状态读取,以及实现带有 Retry-After 的指数退避。包含可运行的 Node.js 示例和故障排查清单。
直接回答:什么触发 Monad RPC 429 以及如何修复
Monad RPC 端点按 IP 地址和 API 密钥实施速率限制,公共免费层限制最严格。当超过这些限制时,服务器会返回 HTTP 429 请求过多或丢弃/挂起请求。要修复,需要识别限制范围(HTTP 与 WebSocket,按 IP 与按密钥),通过批处理和订阅减少请求量,缓存状态读取,并实现尊重 Retry-After 头的指数退避。对于持续负载,请使用专用或认证端点。
Monad 的高吞吐量(测试网高达 10,000 TPS)意味着单个请求可以快速处理,但速率限制仍然基于请求数量,而不是计算成本。这使得简单的循环很容易达到上限,而在较慢的链上则不会。
- 公共端点:严格的按 IP 限制,通常为 10-100 请求/秒。
- 认证端点:限制更高,但因提供商而异。
- WebSocket 连接:通常与 HTTP 有单独的限制。
- 429 响应包含 Retry-After 头;请遵守。
Monad RPC 速率限制的工作原理
Monad 是一个兼容 EVM 的 L1,具有并行执行和 MonadBFT 共识。其 JSON-RPC 接口是标准的以太坊,但底层节点软件可能以不同方式实施速率限制。根据官方 Monad 文档(docs.monad.xyz),公共 RPC 端点按 IP 进行速率限制,且限制未公布。OnFinality、QuickNode 和 Dwellir 等提供商提供自己的端点,限制各不相同。 Monad 的 JSON-RPC 接口与文档化限制请参见官方 Monad JSON-RPC 文档。
速率限制范围可以是按 IP、按 API 密钥或按 WebSocket 连接。HTTP 和 WebSocket 通常分别计量。例如,公共端点可能允许每个 IP 每秒 100 个 HTTP 请求,但每秒仅允许 20 条 WebSocket 消息。当超过限制时,会收到带有 Retry-After 头的 429 响应,或连接被断开。
Monad 的并行执行意味着单个 eth_call 可以在毫秒内处理,但速率限制器仍将其计为一个请求。这与第一代 L1 不同,后者的瓶颈是区块时间;在 Monad 上,瓶颈是您的请求速率。
- 按 IP 限制:适用于来自单个 IP 的所有请求,无论 API 密钥如何。
- 按密钥限制:适用于使用特定 API 密钥的请求,通常高于按 IP 限制。
- WebSocket:单独限制,通常消息速率较低。
- 公共免费层:最严格,专为开发设计,不适合生产。
识别限制范围
当收到 429 时,第一步是确定限制是按 IP 还是按密钥。如果使用没有 API 密钥的公共端点,则是按 IP。如果使用认证端点,则可能是按密钥。检查响应头:某些提供商包含 X-RateLimit-Limit 和 X-RateLimit-Remaining。
另外,检查限制是在 HTTP 还是 WebSocket 上。如果通过 HTTP 轮询 eth_getLogs,可能会达到 HTTP 限制。如果使用 WebSocket 订阅,可能会达到 WebSocket 消息限制。使用单独的 HTTP 和 WebSocket 端点以避免交叉影响。
要测试,发送突发请求并观察响应。如果在一定数量后收到 429,那就是您的限制。记录 Retry-After 头以了解需要等待多长时间。
- 检查响应头以获取速率限制信息。
- 为 HTTP 和 WebSocket 使用单独的端点。
- 通过受控突发测试找到您的限制。
- 记录提供商的限制。
减少请求量:订阅、批处理和缓存
避免 429 最有效的方法是减少请求数量。不要轮询新区块或日志,而是使用 WebSocket 上的 eth_subscribe。这会向您推送数据,因此仅在发生更改时才会收到更新。
对于状态读取(如 eth_getBalance 或 eth_call),在本地缓存结果并定期刷新。例如,如果需要显示余额,请获取一次并每 10 秒更新一次,而不是每秒更新。
JSON-RPC 批处理请求允许您在单个 HTTP 请求中发送多个调用。这对于循环特别有用。不要发送 100 个 eth_getBalance 请求,而是发送一个包含 100 个调用的批处理。这减少了 HTTP 请求的数量,而速率限制器计数的正是 HTTP 请求。
对于 eth_getLogs,避免无界范围。使用足够小的区块范围以快速返回,并在必要时分页。Monad 的高吞吐量意味着大范围可能返回许多日志,但请求本身仍是一个请求。
- 使用 eth_subscribe 订阅 newHeads 和日志,而不是轮询。
- 缓存状态读取并定期刷新。
- 使用 JSON-RPC 批处理请求组合多个调用。
- 限制 eth_getLogs 范围并分页。
- 考虑为高容量生产使用专用端点。
使用 Retry-After 实现指数退避
当确实收到 429 时,应使用指数退避重试。Retry-After 头告诉您需要等待多少秒。如果不存在,请使用基本延迟,并在每次重试时加倍,直到最大值。
这是一个使用 ethers.js 的 Node.js 示例,演示了带退避和批处理的有上限请求循环。它获取地址列表的余额,进行批处理,并在 429 时使用指数退避重试。
该示例使用公共 Monad RPC 端点(替换为您自己的)。它首先尝试批处理所有余额请求。如果失败并返回 429,则使用退避重试。预期输出是余额列表。
- 始终遵守 Retry-After(如果存在)。
- 使用带抖动的指数退避以避免惊群效应。
- 设置最大重试次数以避免无限循环。
- 记录重试以进行调试。
const { ethers } = require('ethers');
const RPC_URL = 'https://rpc.monad.xyz'; // 替换为您的端点
const provider = new ethers.JsonRpcProvider(RPC_URL);
const addresses = [
'0x...', // 替换为实际地址
'0x...',
'0x...',
];
async function getBalancesWithRetry(addresses, maxRetries = 5) {
let retries = 0;
let delay = 1000; // 从 1 秒开始
while (retries <= maxRetries) {
try {
// 将所有余额请求批处理为一个 JSON-RPC 批处理
const batch = addresses.map((addr) => ({
method: 'eth_getBalance',
params: [addr, 'latest'],
id: addresses.indexOf(addr),
jsonrpc: '2.0',
}));
const results = await provider.send('eth_batch', batch); // 注意:eth_batch 不是标准方法;使用 provider.send 进行批处理?实际上 ethers 不直接支持批处理。使用 provider.send 逐个调用?更好的方法是使用自定义批处理。
// 为简单起见,我们使用 Promise.all 进行单独调用,但这不是批处理。
// 要真正批处理,请使用自定义 HTTP 请求。这是一个使用 fetch 的简单批处理:
const response = await fetch(RPC_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(batch),
});
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
const wait = retryAfter ? parseInt(retryAfter) * 1000 : delay;
console.log(`收到 429。等待 ${wait} 毫秒后重试。`);
await new Promise(resolve => setTimeout(resolve, wait));
delay *= 2; // 指数退避
retries++;
continue;
}
const data = await response.json();
if (data.error) {
throw new Error(data.error.message);
}
// data 是结果数组
const balances = data.map((item) => ethers.formatEther(item.result));
console.log('余额:', balances);
return balances;
} catch (error) {
console.error('错误:', error.message);
if (retries >= maxRetries) throw error;
await new Promise(resolve => setTimeout(resolve, delay));
delay *= 2;
retries++;
}
}
}
getBalancesWithRetry(addresses).catch(console.error);
// 预期输出:ETH 余额数组,例如 ['0.123', '0.456', '0.789']常见失败与修复
一个常见失败是使用循环获取许多地址的数据而不进行批处理。这会迅速达到按 IP 限制。修复:批处理请求或使用专用端点。
另一个失败是每秒轮询日志。这效率低下并触发 429。修复:使用 eth_subscribe 订阅日志。
无界的 eth_getLogs 范围可能导致超时或大响应,但仍计为一个请求。然而,它们可能很慢,并可能被服务器丢弃。修复:限制范围并分页。
不遵守 Retry-After 可能导致重复 429 和潜在的 IP 封禁。修复:始终读取头并等待。
对 HTTP 和 WebSocket 使用同一端点可能导致跨限制问题。修复:使用单独的端点。
- 循环而不批处理:使用批处理请求。
- 轮询日志:使用订阅。
- 无界 getLogs:限制范围并分页。
- 忽略 Retry-After:实现退避。
- 混合 HTTP 和 WebSocket:使用单独的端点。
权衡与限制
批处理减少了 HTTP 请求的数量,但增加了有效负载大小。某些提供商对批处理大小有限制(例如 100 项)。如果超过,可能会收到错误。
WebSocket 上的订阅非常适合实时更新,但需要维护持久连接。如果连接断开,需要重新订阅。
缓存状态读取可能导致数据过时。您需要平衡新鲜度与请求量。
专用端点需要付费,但提供更高的限制和可靠性。对于生产环境,值得投资。
Monad 的高吞吐量意味着即使单个请求也能快速处理,但速率限制仍然基于请求数量,而不是计算成本。这是与第一代 L1 的关键区别。
- 批处理大小限制因提供商而异。
- WebSocket 连接需要重连逻辑。
- 缓存引入过时性。
- 专用端点是付费的。
- 速率限制基于请求数量,而非成本。
故障排查清单
使用此清单诊断和修复 Monad RPC 429 错误。
首先,确定端点类型(公共与认证)和限制范围。然后,检查您的请求模式。最后,实施上述修复。
- 检查响应头以获取速率限制信息和 Retry-After。
- 确定限制是按 IP 还是按密钥。
- 检查您使用的是 HTTP 还是 WebSocket,以及它们是否分开。
- 审查代码中可批处理的循环。
- 尽可能用订阅替换轮询。
- 缓存状态读取并定期刷新。
- 实现带抖动的指数退避。
- 考虑为生产环境使用专用端点。
- 监控请求速率并相应调整。
后续步骤与进一步阅读
现在您了解了 Monad RPC 速率限制,可以优化您的应用程序以避免 429。有关更多详细信息,请查看 Monad RPC 端点和指南 以了解端点设置和延迟注意事项。如果您正在寻找可靠的提供商,请参阅我们的 Monad RPC 端点(RPC 助手) 以获取选项列表。
有关一般 RPC 429 处理,请参阅我们的 通用 RPC 429 处理 指南。如果您是 Monad 新手,请从 Monad 主网 页面开始。有关定价详情,请参阅 RPC 定价。如果您需要托管解决方案,请探索我们的 API 服务。
请记住始终根据提供商的限制测试您的实现并相应调整。祝您在 Monad 上构建愉快!