Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
网络与协议指南阅读约 14 分钟

JSON-RPC vs gRPC vs REST:如何选择区块链 API 协议

深入剖析协议架构,对比区块链 API 中的 JSON-RPC、gRPC 与 REST,附可运行的 Node.js 示例和可复现的测量方法。

TL;DR

JSON-RPC、gRPC 和 REST 是调用区块链节点或索引器的三种不同协议风格。JSON-RPC 是一种无状态、面向方法的协议,每个请求都是一个包含 jsonrpc、method、params 和 id 的 JSON 对象,每个方法对应一个具体的链上操作,例如 eth_getBalance 或 eth_call。gRPC 是一种基于 HTTP/2、使用 Protobuf 序列化的 RPC 框架,具有类型化服务定义和流式传输,正被越来越多的新链 SDK 采用。REST 是面向资源的,使用 HTTP 动词和状态码,适合索引化的 CRUD 式数据,但不适合实时节点调用或订阅。三层混淆在于传输、消息风格和载荷编码是相互独立的选择,因此“gRPC vs REST”与“HTTP vs WebSocket”并非同一维度。本文区分了有文档记录的协议行为与提供商特定行为,并提供一种可复现的方法,用于测量你自己端点上的请求字节数、响应字节数和实际耗时。

区块链 API 调用的三个独立层次

大多数协议争论把三个独立的决策混为一谈。第一是传输:HTTP/1.1、HTTP/2、WebSocket 或 IPC。第二是消息风格:RPC(面向方法)与面向资源的 REST。第三是载荷编码:JSON 文本与 Protobuf 二进制。这些选择是正交的,这就是为什么“gRPC vs REST”与“HTTP vs WebSocket”并非同一维度。

例如,gRPC 由 HTTP/2 传输、Protobuf 编码以及带类型化服务定义的 RPC 消息风格组合而成。REST 通常是 HTTP/1.1 或 HTTP/2 搭配 JSON 编码和面向资源的风格。JSON-RPC 通常是 HTTP 或 WebSocket 传输搭配 JSON 编码和 RPC 风格。你可以混合使用:基于 WebSocket 的 JSON-RPC 仍然是 JSON-RPC,而 REST API 也可以返回 Protobuf。

保持各层分离可以避免类别错误。当有人说某条链“支持 gRPC”时,通常意味着存在类型化服务定义并可通过 HTTP/2 访问。当有人说某个节点“支持 WebSocket”时,意味着该传输支持推送通知。这是不同的能力,你可能两者都需要。关于 JSON-RPC 的传输层对比,请参阅 JSON-RPC over HTTP, WebSocket, and IPC。

  • 传输:字节如何移动(HTTP/1.1、HTTP/2、WebSocket、IPC)。
  • 消息风格:操作如何命名(RPC 方法 vs REST 资源)。
  • 编码:载荷如何序列化(JSON 文本 vs Protobuf 二进制)。

JSON-RPC 对区块链节点而言究竟是什么

JSON-RPC 是一种无状态、面向方法的协议。JSON-RPC 2.0 规范 定义了一个包含四个成员的请求对象:jsonrpc(版本字符串)、method(操作名称)、params(参数数组或对象)和 id(关联标识符)。服务器返回 result 成员或包含 code、message 和可选 data 的结构化错误对象。

对于区块链节点,每个方法都映射到一个具体的链上操作。以太坊 JSON-RPC 规范 记录了 eth_getBalance、eth_call、eth_getBlockByNumber 和 eth_sendRawTransaction 等方法。方法名就是契约:没有 URI 层级,也没有需要解释的 HTTP 动词语义。向节点的单一端点发送带有 JSON 正文的 POST 请求就是整个交互模型。

由于 JSON-RPC 是无状态的,每个请求都携带执行所需的一切。这使其可以轻松水平扩展并易于代理,但也意味着协议本身没有模式。类型按方法记录,而不是由线格式强制。这既是其通用性的根源,也是其繁琐性的根源:每条链都暴露 JSON-RPC,但客户端必须从文档或客户端库中了解每个方法的形态。

  • 请求信封:{jsonrpc, method, params, id}。
  • 响应信封:{jsonrpc, result, id} 或 {jsonrpc, error, id}。
  • 没有 URI 层级、没有 HTTP 动词语义、没有内置模式。
  • 跨链普遍支持;节点访问事实上的默认选择。

gRPC 是什么,以及为什么较新的链 SDK 采用它

gRPC 是一种基于 HTTP/2、使用 Protobuf 序列化的 RPC 框架。gRPC 简介 将其描述为使用 HTTP/2 进行传输、Protocol Buffers 进行序列化,并使用服务定义语言实现类型化契约。它支持一元调用、服务器流、客户端流和双向流,并根据服务定义生成客户端和服务器代码。

实际优势是类型化契约和更低的每消息开销。Protobuf 将字段编码为带标签的二进制,因此包含几个数字字段的消息通常比等效的 JSON 文本更小。代码生成意味着客户端在编译时就知道确切的请求和响应类型,从而减少集成错误。流式传输意味着单个连接可以承载连续的数据流。

较新的链 SDK 越来越多地暴露 gRPC API,有时与 JSON-RPC 并存。Sui 的 gRPC API 是链提供类型化 gRPC 接口的一个有文档记录的示例。代价是 gRPC 无法在没有 gRPC-Web 或代理的情况下直接从浏览器调用,并且需要将代码生成作为构建步骤。对于后端服务和索引器,这个成本通常是值得的;对于快速脚本或浏览器 dApp,基于 HTTP 的 JSON-RPC 仍然更简单。

  • 传输:HTTP/2 多路复用流。
  • 编码:带类型字段标签的 Protobuf 二进制。
  • 契约:服务定义并生成客户端/服务器代码。
  • 流式传输:一元、服务器、客户端和双向。
  • 浏览器限制:需要 gRPC-Web 或代理才能从页面调用。

REST 是什么,以及它在区块链中的定位

REST 是面向资源的。URI 标识资源,HTTP 动词表达意图(GET、POST、PUT、DELETE),HTTP 状态码传达结果。它非常适合 CRUD 式数据:获取交易列表、检索代币元数据、向索引器查询历史余额。统一接口使缓存、分页和人类可读的 URL 变得简单直接。

它不适合区块链节点暴露的有状态、方法繁多、面向订阅的调用。节点的操作不是资源:eth_call 是针对特定区块状态的计算,而不是对 URI 的 GET。将其建模为 REST 需要发明一个并不存在的资源层级,并且你会失去每个链客户端都已理解的方法名契约。

在实践中,REST 在区块链中以叠加在节点之上的索引器或浏览器 API 形式出现。索引器摄取区块并暴露可查询的资源。这对于分析和仪表板确实有用,但它不能替代实时节点调用。如果你需要链的当前状态,就调用节点;如果你需要历史聚合数据,就调用索引器。

  • 面向资源的 URI,配合 HTTP 动词和状态码。
  • 擅长索引化、CRUD 式、可缓存的数据。
  • 不擅长实时节点状态、方法繁多的调用和订阅。
  • 通常作为索引器层实现,而非节点接口。

跨协议维度的对比表

下表在选择区块链 API 时最重要的维度上比较了三种风格。请将其视为有文档记录的行为和一般特征,而非基准测试。不同提供商的实现各不相同,你应该验证你的提供商所暴露的确切接口。

“典型区块链用途”一行与决策最相关。JSON-RPC 是读取和写入链状态的默认选择。gRPC 常见于需要类型化契约和流式传输的后端服务和 SDK。REST 常见于索引器、浏览器和分析端点。

  • 请求形态 — JSON-RPC:带有 method 和 params 的单个 POST。gRPC:类型化请求消息。REST:URI 加 HTTP 动词。
  • 编码 — JSON-RPC:JSON 文本。gRPC:Protobuf 二进制。REST:通常是 JSON 文本。
  • 流式传输 — JSON-RPC:基础协议中没有;WebSocket 订阅是单独的传输特性。gRPC:原生一元和流式。REST:非原生;轮询或 SSE。
  • 工具 — JSON-RPC:curl、任何 HTTP 客户端、无需代码生成。gRPC:protoc 和生成的存根。REST:curl、任何 HTTP 客户端、OpenAPI 可选。
  • 浏览器友好性 — JSON-RPC:通过 HTTP 很高。gRPC:没有 gRPC-Web 或代理则很低。REST:高。
  • 典型区块链用途 — JSON-RPC:节点读写。gRPC:类型化 SDK 和后端流式传输。REST:索引器、浏览器、分析。

可运行的 Node.js 示例:同一余额的三种获取方式

下面的示例以三种方式执行相同的逻辑操作——获取余额。第一种是对节点端点的 JSON-RPC POST。第二种是对索引器的 REST 风格 GET。第三种是使用类型化客户端的概念性 gRPC 调用;它以存根形式展示,因为服务定义和包名是链特定的,必须来自链的 SDK。

该脚本测量每次调用的请求字节数、响应字节数和实际耗时。HTTP 路径仅使用 Node.js 内置模块,因此你无需安装依赖即可运行。请将占位端点和地址替换为你自己的。gRPC 分支被有意注释掉,以避免捏造服务定义。

使用 node balance-compare.js 运行它。输出是一个小表格,你可以粘贴到自己的结果表中。由于端点不同,该比较仅说明形态和开销,并非受控基准测试。要进行受控比较,请将三者都指向同一提供商和同一区块高度。

// balance-compare.js — Node.js 18+ (built-in fetch)
const NODE_RPC = process.env.NODE_RPC || 'https://your-node-endpoint';
const INDEXER_REST = process.env.INDEXER_REST || 'https://your-indexer-endpoint';
const ADDRESS = process.env.ADDRESS || '0x0000000000000000000000000000000000000000';

function bytesOf(str) { return Buffer.byteLength(str, 'utf8'); }

async function timed(label, fn) {
  const start = process.hrtime.bigint();
  const { reqBytes, resBytes, note } = await fn();
  const end = process.hrtime.bigint();
  const ms = Number(end - start) / 1e6;
  return { label, reqBytes, resBytes, ms: ms.toFixed(2), note };
}

async function jsonRpcBalance() {
  const body = JSON.stringify({
    jsonrpc: '2.0',
    id: 1,
    method: 'eth_getBalance',
    params: [ADDRESS, 'latest']
  });
  const res = await fetch(NODE_RPC, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body
  });
  const text = await res.text();
  return { reqBytes: bytesOf(body), resBytes: bytesOf(text), note: 'JSON-RPC POST' };
}

async function restBalance() {
  const url = `${INDEXER_REST}/v1/addresses/${ADDRESS}/balance`;
  const res = await fetch(url, { method: 'GET' });
  const text = await res.text();
  return { reqBytes: bytesOf(url), resBytes: bytesOf(text), note: 'REST GET' };
}

// Conceptual gRPC branch: requires a chain-specific .proto and generated client.
// async function grpcBalance() {
//   const grpc = require('@grpc/grpc-js');
//   const protoLoader = require('@grpc/proto-loader');
//   const def = protoLoader.loadSync('chain.proto');
//   const pkg = grpc.loadPackageDefinition(def);
//   const client = new pkg.chain.QueryService(GRPC_ENDPOINT, grpc.credentials.createInsecure());
//   const req = { address: ADDRESS };
//   const reqBytes = Buffer.from(JSON.stringify(req)).length; // Protobuf size differs
//   const res = await new Promise((resolve, reject) =>
//     client.getBalance(req, (err, r) => err ? reject(err) : resolve(r))
//   );
//   return { reqBytes, resBytes: Buffer.from(JSON.stringify(res)).length, note: 'gRPC unary' };
// }

(async () => {
  const rows = [];
  rows.push(await timed('JSON-RPC', jsonRpcBalance));
  rows.push(await timed('REST', restBalance));
  console.table(rows);
})();

针对你自己端点的可复现结果表

针对你的工作负载比较协议的唯一诚实方式是测量你自己的端点。下表是一个模板。用上面脚本的输出或你自己的插桩数据填写它。不要将任何已发布的数字视为通用:延迟和载荷大小取决于链、方法、提供商和网络路径。

每个操作至少运行 30 次,并记录中位数和第 95 百分位数,而不仅仅是单个样本。记录测量时的区块高度或时隙,因为状态在运行之间会变化。如果你在比较提供商,请保持方法和区块高度不变。关于更广泛的评估框架,请参阅 评估区块链 JSON-RPC API 解决方案。

“观察到的模式/类型优势”一列是定性的。记录客户端是否在构建时捕获了类型错误、你是否需要查阅文档来构造请求,以及响应是否需要手动解析。这正是 gRPC 代码生成优势体现的地方,而它不会出现在字节数中。

  • 操作 — 逻辑动作,例如获取余额、获取区块、发送交易。
  • 协议 — JSON-RPC、gRPC 或 REST。
  • 请求字节数 — 线上序列化请求大小。
  • 响应字节数 — 线上序列化响应大小。
  • 实际耗时 — 至少 30 次运行的中位数和 p95。
  • 观察到的模式/类型优势 — 构建时类型安全、文档查阅、手动解析。
  • 区块高度或时隙 — 在测量时记录,以保持运行可比。

每种协议风格的局限性与权衡

JSON-RPC 缺乏模式,比二进制替代方案更繁琐,但每条链都普遍支持它。这种通用性是一项真正的工程资产:一种客户端模式可适用于以太坊、Polkadot 和大多数其他网络。代价是你通常需要多次往返才能组装一个视图,并且你必须信任每个方法的类型文档。批处理可以减少往返次数;请参阅 JSON-RPC 批处理最佳实践。

gRPC 高效且类型化,但需要代码生成,并且没有代理就无法从浏览器调用。构建步骤在 CI 中是真实成本,并且当服务定义更改时必须重新生成存根。对于重视类型安全和流式传输的后端服务,这个成本通常是合理的。对于一次性脚本,则是摩擦。

REST 对索引器很方便,但不适合实时节点调用或订阅。当数据已经聚合且可缓存时,它表现出色。当你需要推送通知时,它不能替代 WebSocket 订阅:这三种风格在其基础形式中,都没有提供服务器发起的事件。如果你需要实时响应新区块或日志,你需要订阅传输,这是与消息风格不同的决策。

  • JSON-RPC:无模式、更繁琐,但跨链通用。
  • gRPC:高效且类型化,但需要代码生成和浏览器代理。
  • REST:非常适合索引器,不适合实时节点调用和订阅。
  • 三者都不能替代 WebSocket 订阅来实现推送通知。

排查常见的协议选择失败

一个常见的失败是调用 REST 端点却期望获得实时节点状态。索引器在设计上会落后于链头,因此从索引器获取的余额可能已过时。如果你需要当前状态,请直接调用节点的 JSON-RPC 方法。如果你需要历史聚合数据,请调用索引器。在不了解滞后的情况下混合两者会产生令人困惑的结果。

另一个失败是尝试从浏览器进行 gRPC 调用。标准 gRPC 需要 HTTP/2 尾部,而浏览器不会向 JavaScript 暴露这些尾部,因此你需要 gRPC-Web 或代理。如果你的 dApp 必须直接调用类型化服务,请为此代理预留预算。否则,请使用基于 HTTP 的 JSON-RPC,它可以从任何浏览器 fetch 调用中工作。

第三个失败是假设 JSON-RPC 有模式并发送格式错误的参数。协议验证信封,而不是方法参数。错误的参数类型可能返回结构化错误,也可能返回不是你预期的结果。始终根据方法的文档化签名进行验证,并在存在客户端库时使用它。关于模拟式调用,请参阅 eth_call 与状态覆盖模拟。

最后,延迟问题常常被错误地归咎于协议。如果你的调用很慢,请在切换协议之前进行测量。瓶颈通常是网络路径或提供商的负载,而不是 JSON 与 Protobuf 的区别。关于测量优先的方法,请参阅 降低 RPC 延迟。

  • 将过时的索引器数据误认为实时节点状态。
  • 浏览器 gRPC 调用在没有 gRPC-Web 或代理时失败。
  • 格式错误的 JSON-RPC 参数通过信封验证但在语义上失败。
  • 将延迟归咎于协议,而原因是网络或提供商负载。

为你的区块链集成选择协议

从操作开始,而不是协议。如果你在读取或写入链状态,JSON-RPC 是默认且最安全的选择,因为每条链都支持它。如果你在构建受益于类型化契约和流式传输的后端服务,请评估链的 gRPC 接口。如果你在查询聚合的历史数据,请使用索引器的 REST API。

对于大多数团队,答案是组合使用。使用 JSON-RPC 进行实时节点调用,使用 WebSocket 订阅进行推送通知,在链提供 gRPC 的地方将其用于类型化后端服务,使用 REST 进行分析。这些协议与其说是竞争对手,不如说是层次,而第一节中的三层模型是保持它们清晰有序的工具。

当你评估提供商时,检查他们暴露哪些接口以及如何记录它们。在一个账户下提供 JSON-RPC、WebSocket 和 gRPC 的提供商可以减少集成摩擦。你可以在 API 服务 页面查看可用的接口,并在 RPC 定价 页面比较成本模型。关于特定网络的端点,请参阅 以太坊。

  • 实时节点读写:基于 HTTP 的 JSON-RPC。
  • 推送通知:WebSocket 订阅。
  • 类型化后端服务:在链提供的地方使用 gRPC。
  • 历史聚合数据:REST 索引器 API。

后续步骤与延伸阅读

最有用的下一步是针对你自己的端点运行测量脚本并填写结果表。这为你提供了协议决策的可辩护依据,而不是猜测。记录区块高度,运行足够的样本,并在字节数旁边注明定性的类型优势。

从那里开始,通过 JSON-RPC over HTTP, WebSocket, and IPC 深入了解传输层,并通过 JSON-RPC 批处理最佳实践 减少往返次数。如果你正在选择提供商,评估区块链 JSON-RPC API 解决方案 指南涵盖了协议风格之外的重要标准。

关于相关材料的更广泛索引,请从 OnFinality Learn 中心 开始。目标不是在 JSON-RPC、gRPC 和 REST 中选出赢家,而是将架构的每一层与适合它的协议风格相匹配。

  • 运行测量脚本并填写结果表。
  • 审查 JSON-RPC 的传输选项。
  • 应用批处理以减少往返次数。
  • 根据提供商暴露的接口进行评估,而不仅仅是价格。

永远不用担心基础设施

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

开始