Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
RPC 故障排查12 分钟阅读

Sui RPC 超时:JSON-RPC 与 gRPC、QueryWeight 及可靠的重试模式

了解 Sui RPC 请求为何会超时,QueryWeight 和传输选择如何影响超时,以及如何通过重试模式和端点故障转移来诊断和修复这些问题。

TL;DR

Sui RPC 超时通常由超过 QueryWeight 限制的重型查询、传输层超时或节点过载引起。本文解释了 JSON-RPC、gRPC 和 GraphQL 传输之间的差异,QueryWeight 如何影响请求执行,并提供了一个带有超时和重试逻辑的可运行 Node.js 示例。还包括故障排查清单和端点故障转移指南。

直接回答:为什么 Sui RPC 请求会超时

Sui RPC 超时发生在请求耗时超过客户端或服务器允许的时间时,通常是因为查询过重(超过 Sui 的 QueryWeight 限制)、传输(JSON-RPC 与 gRPC)具有不同的超时特性,或者节点过载。最常见的原因是在单个调用中请求了太多数据——例如,使用完整选项获取交易区块,或没有适当限制地分页遍历数千个动态字段。本文解释了底层机制并提供了具体修复方法,包括在 @mysten/sui.js SDK 中设置显式超时和重试、缩小查询选项以及在端点之间进行故障转移。

与速率限制(返回 429 或类似错误)不同,超时可能表现为传输层错误(例如 'ETIMEDOUT')、带有 'request timed out' 的 500/503 错误,或最终报错的停滞。理解差异是选择正确修复方法的关键。有关速率限制的深入探讨,请参阅我们的 Sui RPC 速率限制和(可能的)计算单元 文章。

  • 传输超时:客户端或服务器端对连接或响应时间的限制。
  • QueryWeight:Sui 的每请求计算计费,可能导致重型查询被拒绝或耗时过长。
  • 节点过载:共享或配置不足的节点可能无法在您的超时窗口内响应。

Sui RPC 传输:JSON-RPC、gRPC 和 GraphQL

Sui 提供多种 RPC 传输,每种传输具有不同的超时和帧特性。主要传输是基于 HTTP 的 JSON-RPC,它是同步的请求-响应模式。超时通常在 HTTP 客户端(例如 30 秒)和服务器端(例如 60 秒)设置。如果查询耗时超过服务器超时,您可能会收到带有 'request timed out' 消息的 500 或 503 错误。

gRPC 是一种二进制协议,支持流式传输并内置截止时间。Sui 的 gRPC 接口(由 Sui 全节点使用)允许更高效地流式传输大型数据集,例如检查点数据。gRPC 超时通过上下文截止时间设置,协议能更优雅地处理取消。对于低延迟流式传输,gRPC 通常是首选,但它需要 gRPC 客户端,并且不像 JSON-RPC 那样广泛支持。

GraphQL(Sui 的 RPC 2.0)是一个新兴选项,允许客户端精确请求所需字段,减少负载大小和潜在超时。截至 2026 年,它仍在开发中(参见 GitHub 上的 RPC 2.0 问题),但它有望通过避免过度获取来缓解超时问题。

  • JSON-RPC:简单、广泛支持,但同步且容易在重型查询上超时。
  • gRPC:二进制、流式、带截止时间;更适合大数据传输。
  • GraphQL:字段选择减少负载,但尚不稳定。

QueryWeight:Sui 如何按请求计费计算

Sui 节点使用 QueryWeight 机制来限制每个 RPC 请求的计算成本。每种查询类型都有一个权重,节点对每个请求和每秒有最大权重限制。如果请求超过每请求权重,可能会被拒绝并出现类似 'Query is too heavy' 的错误,或者耗时过长导致超时。具体限制在 Sui 文档 中有记录,并因节点配置而异。

常见的导致超时的重型查询包括:

getTransactionBlock 使用 showInput: trueshowEffects: trueshowEvents: true——这可能返回巨大的负载。

getCoinsmultiGetCoins 使用较大的 limit(例如 1000)且没有分页。

getDynamicFields 使用较大的 limit 和深度递归。

getCheckpoint 使用 showContents: true 获取包含许多交易的检查点。

为避免超时,您应该只请求所需字段。例如,如果只需要摘要,使用 showEffects: false;或使用较小的 limit 并通过 nextCursor 分页。

  • QueryWeight 是每请求和每秒的;超过它可能导致超时或错误。
  • 始终只请求您需要的字段。
  • 使用分页将大型查询分解为较小的块。

诊断 Sui RPC 超时

要诊断超时,首先测量简单查询与重型查询的响应时间。使用带计时标志的 curl 查看延迟发生的位置。例如:

curl -w "time_total: %{time_total}s\n" https://fullnode.mainnet.sui.io/ -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"sui_getChainIdentifier","params":[],"id":1}'

如果简单查询响应迅速但重型查询超时,问题可能出在 QueryWeight 或负载大小上。如果即使简单查询也超时,节点可能过载或您的网络连接缓慢。

还要检查节点的同步状态。如果节点落后于网络,它可能没有您请求的数据,导致挂起。将最新检查点或纪元与独立浏览器(如 Sui Explorer)进行比较,以查看您的节点是否落后。

  • 使用 curl -w 测量总时间和首字节时间。
  • 比较简单查询与重型查询以隔离原因。
  • 检查节点同步状态与独立浏览器对比。

修复超时:SDK 配置和重试模式

@mysten/sui.js TypeScript SDK 允许您在 JSON-RPC 客户端上设置超时。例如,您可以创建一个自定义的 JsonRpcProvider,其 fetch 函数包含带有超时的 AbortController。以下是一个可运行的 Node.js 示例,设置 10 秒超时并实现指数退避重试:

import { SuiClient, getFullnodeUrl } from '@mysten/sui.js/client';

const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

async function rpcWithRetry(client, method, params, { timeoutMs = 10000, retries = 3 } = {}) {
  for (let attempt = 0; attempt < retries; attempt++) {
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), timeoutMs);
    try {
      const result = await client.call(method, params, { signal: controller.signal });
      clearTimeout(timeout);
      return result;
    } catch (error) {
      clearTimeout(timeout);
      if (attempt === retries - 1) throw error;
      const delay = Math.pow(2, attempt) * 1000;
      console.log(`Attempt ${attempt + 1} failed: ${error.message}. Retrying in ${delay}ms`);
      await sleep(delay);
    }
  }
}

const client = new SuiClient({ url: getFullnodeUrl('mainnet') });

// Example: get a transaction block with minimal options
const txDigest = 'your_tx_digest_here';
const result = await rpcWithRetry(client, 'sui_getTransactionBlock', [txDigest, { showEffects: false }]);
console.log(JSON.stringify(result, null, 2));
// Expected output: a JSON object with the transaction block data, or an error after retries.

除了超时,您还应该缩小查询选项。对于 getTransactionBlock,除非需要效果,否则使用 showEffects: false。对于 getCoins,使用 50 或 100 的 limit 并通过 nextCursor 分页。对于 getDynamicFields,使用 limit 并避免深度递归。

如果您使用 gRPC,请在上下文中设置截止时间。例如,在 Node.js 中使用 @grpc/grpc-js 库,您可以设置 10 秒的截止时间。gRPC 还支持取消,这对于长时间运行的流很有用。

  • 在 HTTP 客户端上设置显式超时,避免无限挂起。
  • 为瞬时故障实现指数退避重试。
  • 缩小查询选项以减少负载大小和计算权重。
  • 使用 multiGetCoins 而不是循环 getCoins 获取多种币类型。

可运行示例:使用 Sui TypeScript SDK 的超时、退避重试和健康检查

以下 Node.js 脚本演示了一个完整的、自包含的示例,使用 @mysten/sui/client。它创建一个 SuiClient,将 getObject 调用包装在带有指数退避重试的超时中,并通过获取链标识符执行简单的端点健康检查。该示例设计为在安装 SDK 后直接运行。

运行此脚本时,您应该看到类似以下的输出。首先,健康检查打印链标识符(十六进制字符串)。然后,getObject 调用返回一个 JSON 对象,包含所请求对象的详细信息,包括其类型和摘要。如果端点关闭或请求超时,重试逻辑会记录每次失败的尝试,并最终抛出错误,该错误被捕获并打印。

import { SuiClient, getFullnodeUrl } from '@mysten/sui/client';

const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

async function rpcWithRetry(client, method, params, { timeoutMs = 10000, retries = 3 } = {}) {
  for (let attempt = 0; attempt < retries; attempt++) {
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), timeoutMs);
    try {
      const result = await client.call(method, params, { signal: controller.signal });
      clearTimeout(timeout);
      return result;
    } catch (error) {
      clearTimeout(timeout);
      if (attempt === retries - 1) throw error;
      const delay = Math.pow(2, attempt) * 1000;
      console.log(`Attempt ${attempt + 1} failed: ${error.message}. Retrying in ${delay}ms`);
      await sleep(delay);
    }
  }
}

async function healthCheck(client) {
  try {
    const chainId = await rpcWithRetry(client, 'sui_getChainIdentifier', []);
    console.log('Health check passed. Chain ID:', chainId);
    return true;
  } catch (error) {
    console.error('Health check failed:', error.message);
    return false;
  }
}

async function main() {
  const client = new SuiClient({ url: getFullnodeUrl('mainnet') });

  // Health check
  const healthy = await healthCheck(client);
  if (!healthy) {
    console.error('Endpoint is not healthy. Exiting.');
    process.exit(1);
  }

  // Example: get an object with retry and timeout
  const objectId = '0x0000000000000000000000000000000000000000000000000000000000000001';
  try {
    const result = await rpcWithRetry(client, 'sui_getObject', [objectId, { showType: true }]);
    console.log('Object data:', JSON.stringify(result, null, 2));
  } catch (error) {
    console.error('Failed to fetch object after retries:', error.message);
  }
}

main();

常见故障和修复

以下是常见的超时场景及其修复方法:

场景 1:使用完整选项的 getTransactionBlock 超时。 修复:除非需要,否则设置 showInput: falseshowEffects: falseshowEvents: false。如果需要效果,请考虑单独获取。

场景 2:使用较大 limitgetCoins 超时。 修复:使用 50-100 的 limit 并通过 nextCursor 分页。或者使用 multiGetCoins 并传入币对象 ID 列表。

场景 3:使用较大 limitgetDynamicFields 超时。 修复:减少 limit 并分页。避免递归调用获取所有嵌套动态字段。

场景 4:使用 showContents: true 的检查点查询超时。 修复:如果只需要检查点摘要,设置 showContents: false

场景 5:所有查询都超时,即使是简单的查询。 修复:检查您的网络连接、节点健康状况以及节点是否同步。考虑切换到不同的端点或使用性能更好的提供商(参见我们的 Sui RPC 延迟和性能 文章)。

  • 始终使用满足需求的最小选项对象。
  • 分页是您的朋友——永远不要一次请求超过 100 个项目。
  • 如果节点持续缓慢,故障转移到另一个端点。

权衡和限制

虽然 gRPC 提供更好的流式传输,但它需要更复杂的客户端设置,并且可能不被所有提供商支持。JSON-RPC 更简单,但在重型查询上更容易超时。GraphQL 很有前景,但尚不稳定。

QueryWeight 限制并不总是精确记录;它们可能因节点配置和提供商而异。对于提供商特定的限制,请参阅其文档。OnFinality 的 RPC 定价 页面提供了我们服务的详细信息,但我们不发布具体的延迟或吞吐量数字。

重试模式可能掩盖潜在问题。如果查询在重试后仍然超时,最好优化查询而不是增加重试次数。此外,注意速率限制——过于激进的重试可能触发速率限制,这是另一个问题(参见我们的 Sui RPC 速率限制 文章)。

  • gRPC 不是银弹;它需要客户端支持。
  • QueryWeight 限制并不总是公开的;请与您的提供商测试。
  • 重试应用于瞬时错误,而不是重型查询。

后续步骤和进一步阅读

要充分利用 Sui RPC,首先实现重试模式并缩小查询范围。如果您正在构建生产应用程序,请考虑使用可靠的 RPC 提供商,如 OnFinality 的 API 服务,它提供高可用性和故障转移。您还可以查阅我们的 Sui RPC 指南(RPC 助手) 获取快速提示。

要更广泛地了解 Sui,请参阅我们的 Sui 网络页面。别忘了探索 OnFinality Learn 中心 上的其他文章,获取更多故障排查指南。

  • 在客户端代码中实现超时和重试。
  • 优化查询以保持在 QueryWeight 限制内。
  • 使用具有多个端点的提供商进行故障转移。
  • 监控节点的同步状态和性能。

永远不用担心基础设施

OnFinality 消除了 DevOps 的繁重工作,让您能够更聪明、更快地构建。

开始