Bittensor (Finney) RPC 端点实施按 IP 速率限制,公共节点通常为每秒 1-5 个请求。本文解释了 Substrate JSON-RPC 上的速率限制工作原理、429 错误的表现形式,以及如何通过订阅、批处理、缓存和退避策略避免该错误。文中包含一个可运行的 Node.js 示例和供您自行基准测试的结果表格。
直接回答:Bittensor RPC 速率限制是什么?
Bittensor (Finney) RPC 端点与大多数公共 Substrate JSON-RPC 服务一样,实施按 IP 速率限制。在共享社区节点上,限制通常约为每个 IP 每秒 1 个请求(RPS),而一些公共或经过身份验证的提供商可扩展到 5 RPS 或更高。当您超过限制时,服务器会返回 HTTP 429(请求过多)或 JSON-RPC 错误,如 Rate limit exceeded。这不是超时;而是明确表示您在给定时间窗口内发送了太多请求。
具体限制因提供商而异。例如,OnFinality 的 Bittensor Finney 端点 可能与社区节点有不同的限制。独立的性能比较(如 comparenodes 提供的)提供了公共端点的快照,但您应始终针对所选提供商基准测试自己的使用情况。本文区分了 Bittensor 的文档化行为与提供商特定限制,并为您提供了一种可复现的方法来衡量您自己端点的容忍度。
- 公共 Bittensor RPC 端点通常允许每个 IP 1-5 RPS。
- 429 或
Rate limit exceeded表示您已达到限制,而非网络问题。 state_getMetadata和state_call等重调用会消耗更多配额。- 使用订阅、批处理和缓存以保持在限制之内。
Bittensor (Substrate) JSON-RPC 速率限制的工作原理
Bittensor 的 Finney 网络基于 Substrate 构建,因此其 JSON-RPC 接口遵循 Substrate 规范。速率限制通常在 HTTP 或 WebSocket 层实现,通常通过中间件跟踪每个 IP 地址的请求。服务器可能使用令牌桶或滑动窗口算法来强制每秒最大请求数。
当请求超过限制时,服务器返回 HTTP 429 状态码(对于 HTTP)或 JSON-RPC 错误,代码为 -32005 或自定义消息,如 Rate limit exceeded(对于 WebSocket)。这与超时不同,超时发生在服务器响应缓慢但未拒绝请求时。429 是明确的拒绝,您必须使用重试逻辑来处理。
某些调用比其他调用更昂贵。例如,state_getMetadata 返回整个运行时元数据,可能很大,而 state_call 执行运行时函数,可能占用大量 CPU。提供商可能对这些调用赋予更高的权重或应用更严格的限制。同样,使用 chain_getBlock 或 chain_getHeader 轮询每个区块会迅速耗尽您的配额,尤其是当您同时进行其他调用时。
- 速率限制是按 IP 的,而不是按账户或 API 密钥(除非您使用经过身份验证的端点)。
- HTTP 和 WebSocket 端点可能有单独的限制。
state_getMetadata和state_call等昂贵调用可能受到更严格的限制。- 区块轮询是 429 错误的常见原因,因为它每隔几秒就会产生一个请求。
Substrate JSON-RPC 上的 429 错误是什么样子的?
当您触发速率限制时,响应格式取决于传输方式。对于 HTTP,您将收到 HTTP 429 状态码,响应体可能包含 JSON-RPC 错误对象。对于 WebSocket,服务器可能关闭连接或发送 JSON-RPC 错误消息。
以下是来自 Substrate 节点的 HTTP 429 响应示例:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
{"jsonrpc":"2.0","error":{"code":-32005,"message":"Rate limit exceeded: 1 req/s"},"id":1}为什么区块轮询和重状态调用会触发限制?
许多 Bittensor 应用程序使用 chain_getBlock 或 chain_getHeader 循环轮询链以获取新区块。如果您每 6 秒(区块时间)轮询一次,那就是每分钟 10 个请求,这没问题。但如果您还为每个区块进行其他调用,如 state_getMetadata 或 state_call,则很容易超过 1 RPS 的限制。
例如,一个监控机器人获取最新区块,然后调用 state_call 读取特定存储值,再调用 state_getMetadata 解码事件,每个区块可能产生 3 个请求。每 6 秒一个区块,即 0.5 RPS,低于 1 RPS。但如果您还轮询多个端点或进行重试,则可能超过限制。
解决方案是使用 chain_subscribeFinalizedHeads(WebSocket)在区块最终确定时接收新区块头,从而无需轮询。这大大减少了请求数量。对于存储读取,尽可能使用带有特定键的 state_getStorage 而不是 state_call,并缓存结果。
- 区块轮询每个区块都会产生一个请求,累积起来很快。
state_getMetadata响应较大,可能受到更严格的速率限制。state_call执行运行时代码,占用 CPU。- 使用
chain_subscribeFinalizedHeads通过订阅获取区块更新,而不是轮询。
保持在限制之内:订阅、批处理和缓存
为了避免 429 错误,您需要减少请求数量。以下是最有效的策略:
使用 WebSocket 订阅获取新区块和事件。chain_subscribeFinalizedHeads 为您提供区块头流,无需轮询。同样,state_subscribeStorage 可以在特定存储键更改时通知您。
使用 JSON-RPC 批处理。Substrate 支持在单个 HTTP POST 中发送请求数组。这算作一个请求,即使包含多个调用。例如,您可以将多个 state_getStorage 调用批处理到一个请求中。
缓存昂贵的读取,如 state_getMetadata。元数据很少更改,因此获取一次并本地缓存。对于不频繁更改的存储值,使用 TTL 缓存。
使用专用端点或自托管 subtensor 节点,如果您需要更多余量。对于需要高吞吐量的挖矿或监控机器人,公共端点的速率限制可能过于严格。OnFinality 的 API 服务 提供更高限制的专用端点,您也可以运行自己的节点。
- 订阅通过将数据推送给您而不是您轮询来减少请求数量。
- 将多个调用批处理到一个 HTTP 请求中算作一个请求。
- 缓存元数据和频繁读取的存储值。
- 对于高吞吐量需求,考虑专用端点或自托管节点。
可复现示例:带退避的 Node.js 请求循环
以下 Node.js 脚本演示了如何向 Bittensor RPC 端点发出请求、处理 429 响应以及使用指数退避重试。它还展示了如何将多个调用批处理到一个请求中。您可以针对任何公共 Bittensor RPC 端点运行此脚本,以观察其速率限制行为。
先决条件: Node.js 18+ 和 ws 包(用于 WebSocket)。使用 npm install ws 安装。
脚本:
const WebSocket = require('ws');
const RPC_URL = 'wss://your-bittensor-endpoint.example';
function subscribeToHeads() {
const ws = new WebSocket(RPC_URL);
ws.on('open', () => {
ws.send(JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'chain_subscribeFinalizedHeads',
params: []
}));
});
ws.on('message', (data) => {
const msg = JSON.parse(data);
if (msg.method === 'chain_subscribeFinalizedHeads') {
console.log('New finalized head:', msg.params.result.number);
}
});
ws.on('error', (err) => {
console.error('WebSocket error:', err.message);
});
}
// Example of batching multiple state_getStorage calls into one HTTP request
async function batchGetStorage(keys) {
const batch = keys.map((key, i) => ({
jsonrpc: '2.0',
id: i + 1,
method: 'state_getStorage',
params: [key]
}));
const response = await fetch(RPC_URL.replace('wss', 'https'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(batch)
});
if (response.status === 429) {
console.log('Rate limited, retrying...');
await new Promise(resolve => setTimeout(resolve, 1000));
return batchGetStorage(keys);
}
return response.json();
}
// Run the subscription
subscribeToHeads();
// Example usage of batchGetStorage (uncomment to test)
// batchGetStorage(['0x...key1', '0x...key2']).then(console.log);预期输出与验证
运行脚本时,您应该看到随着新的最终确定区块头的到达而打印的区块号流。如果端点强制执行速率限制,当您发送过多请求时,可能会看到 WebSocket 错误或断开连接。批处理函数将返回一个 JSON-RPC 响应数组,每个键一个。
要验证速率限制,您可以故意发送一批请求并观察 429 响应。下表供您填写自己的测量结果。针对不同端点运行脚本并记录结果。
结果表格(填写您自己的测量值):
- 端点 URL: [您的端点]
- 观察到的速率限制 (RPS): [例如,1, 5]
- 达到限制时的响应代码: [429 或 JSON-RPC 错误]
- 重试后头部: [例如,1 秒]
- 备注: [例如,3 次违规后 WebSocket 断开]
常见故障与修复
故障:每个请求都返回 429。 这通常意味着您持续超过限制。检查您的请求速率并降低它。使用订阅而不是轮询,并批处理独立调用。
故障:WebSocket 连接断开。 一些提供商会断开超过限制的 WebSocket 客户端。使用指数退避实现重连逻辑。ws 库通过 reconnect 选项支持重连,或者您可以手动处理。
故障:state_getMetadata 缓慢或受到速率限制。 在本地缓存元数据,仅在运行时升级时刷新。您可以通过订阅 chain_subscribeRuntimeVersion 来检测运行时升级。
故障:state_call 昂贵。 尽可能使用带有特定键的 state_getStorage 而不是 state_call。如果必须使用 state_call,请缓存结果并避免在循环中调用它。
- 如果收到 429,请降低请求频率或使用批处理。
- 为 WebSocket 订阅实现带退避的重连。
- 缓存元数据和运行时版本,以避免重复的昂贵调用。
- 对于简单的存储读取,优先使用
state_getStorage而不是state_call。
权衡与限制
虽然订阅和批处理减少了请求数量,但它们也有权衡。订阅需要持久的 WebSocket 连接,这可能不适合无服务器函数或短生命周期进程。批处理会增加单个调用的延迟,因为您需要等待所有响应才能处理。
速率限制是按 IP 的,因此如果您位于共享 IP 后面(例如公司 NAT),您可能会受到其他用户流量的影响。使用带有 API 密钥的经过身份验证的端点可以为您提供专用配额。OnFinality 的 RPC 定价 页面解释了这些选项。
另请注意,公共端点可能对不同方法有不同的限制。例如,chain_getBlock 可能限制为 1 RPS,而 system_health 可能为 5 RPS。始终检查提供商的文档或进行实证测试。
- 订阅需要持久连接;并非适用于所有用例。
- 批处理会增加延迟;用于非时间敏感的调用。
- 按 IP 限制可能受到同一 IP 上其他用户的影响。
- 经过身份验证的端点可能提供更高的限制。
后续步骤与进一步阅读
既然您了解了 Bittensor RPC 速率限制,就可以优化您的应用程序以保持在限制之内。有关更多指导,请探索以下资源:
- Bittensor Finney 网络概述 – 了解网络及其端点。
- OnFinality Learn 中心 – 更多关于 RPC 和区块链基础设施的教程和深入探讨。
- RPC 定价 – 比较计划和专用端点选项。
- API 服务 – 获取更高限制的专用端点。
- Bittensor RPC 指南 (RPC Assistant) – 快速解答常见的 Bittensor RPC 问题。
- RPC 监控与故障转移 – 通过监控和故障转移策略确保高可用性。
请记住对您自己的端点进行基准测试,并根据您的具体需求调整策略。像 comparenodes 这样的独立比较可以为您提供起点,但您的结果可能会有所不同。