通过 RPC 检查 Polkadot 节点健康是一个复合判定,而非单一布尔值。system_health 返回 peers、isSyncing 和 shouldHavePeers;system_syncState 返回 startingBlock、currentBlock 和 highestBlock;system_peers 返回每个对等节点的详细信息。由于停滞的节点仍可能报告 isSyncing=false,可靠的检查方法是将 system_syncState.currentBlock 与参考链头(通过 chain_getHeader 本地读取或从第二个端点获取)进行比较,得出滞后差值,然后读取 system_health.peers 来发现已同步但被分区的节点。本文以 Substrate JSON-RPC 规范为基础阐述方法契约,提供一个可运行的 @polkadot/api 探针,返回 {healthy, reason},并展示如何将判定结果接入间隔探测、健康端点和排空/故障转移。
Substrate 健康接口及其三个权威方法
Polkadot 通过 JSON-RPC 暴露了一个原生健康接口,它早于任何 EVM 兼容层且独立于后者。Substrate JSON-RPC 规范定义了此处至关重要的三个方法:system_health、system_syncState 和 system_peers。每个方法对某一项事实具有权威性,但单独任何一个都不能构成完整的健康判定。
system_health 对等节点数量和同步意图具有权威性。它返回 peers(已连接的对等节点数量)、isSyncing(布尔值)和 shouldHavePeers(布尔值)。system_syncState 对区块进度具有权威性:startingBlock、currentBlock 和 highestBlock。system_peers 对每个对等节点的详细信息具有权威性,包括对等节点 ID、角色、最佳哈希和最佳区块号,这让你能够区分“未连接到任何节点”和“连接到的对等节点本身也落后”。
在信任上述任何字段之前,请先读取健全性检查前导信息:system_name、system_version 和 system_chain。这些信息告诉你实际正在与什么软件、哪条链通信。一个假设是 Polkadot 但实际连接到测试网或配置不同的节点的探针,会得出关于错误系统的判定。这些调用的类型化访问器记录在 Polkadot-JS API 文档中,这也是下面示例客户端所使用的参考。
- system_health:peers、isSyncing、shouldHavePeers——对等节点数量和同步意图。
- system_syncState:startingBlock、currentBlock、highestBlock——区块进度。
- system_peers:每个对等节点的角色、最佳哈希和最佳区块号——对等节点质量。
- system_name / system_version / system_chain——身份健全性检查前导信息。
获取相同健康字段的原始 JSON-RPC 调用
类型化客户端很方便,但底层传输是纯 JSON-RPC 2.0,因此每个方法都可以通过单个 curl 请求读取相同的字段。当你想要确认库实际发送了什么、调试会重写响应的提供商,或者用没有 Substrate 客户端的语言编写探针时,这很有用。
下面的代码片段向本地节点发出三个健康调用以及 chain_getHeader。每个请求都是标准的 JSON-RPC 信封,包含方法名和空参数数组;响应携带 Substrate JSON-RPC 规范中描述的结果对象。请分别运行这些调用,以免某个方法的失败掩盖其他方法,并将返回的 currentBlock 与区块头中的区块号进行比较,以得出与 Node.js 探针计算相同的滞后差值。
# system_health — peers, isSyncing, shouldHavePeers
curl -s -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"system_health","params":[]}' \
http://127.0.0.1:9933
# system_syncState — startingBlock, currentBlock, highestBlock
curl -s -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"system_syncState","params":[]}' \
http://127.0.0.1:9933
# system_peers — per-peer role, best hash and best number
curl -s -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"system_peers","params":[]}' \
http://127.0.0.1:9933
# chain_getHeader — local reference head for the lag delta
curl -s -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"chain_getHeader","params":[]}' \
http://127.0.0.1:9933为什么 isSyncing=false 不代表存活
最常见的生产环境错误是将 system_health.isSyncing=false 视为节点健康的证明。事实并非如此。isSyncing 反映的是节点是否认为自己正在积极追赶;一个已经停滞的节点——例如因为失去对等节点、遇到磁盘问题或卡在坏区块上——可能报告 isSyncing=false,而其 currentBlock 停止推进。该标志描述的是意图,而非进度。
可靠的存活信号是滞后差值:将 system_syncState.currentBlock 与参考链头进行比较。你可以通过 chain_getHeader 在本地获取参考链头(区块头携带区块号),或者从第二个独立运营的端点外部获取。参考链头与 currentBlock 之间的差值就是你的滞后量。滞后量稳定且小的节点正在跟上;滞后量单调增长的节点即使 isSyncing 为 false 也在落后。
对等节点数量填补了剩余的缺口。一个节点可能已完全同步但仍被分区,由于不再接收新区块而提供过时读取。读取 system_health.peers 可以捕获这种情况:一个已同步但零对等节点的节点不是健康的读取目标。这与检测 RPC 节点链头滞后和过时响应中描述的是同一类问题,只是通过 Substrate 字段而非 EVM 区块号来表达。
- isSyncing=false 意味着“当前未在追赶”,而非“正在推进”。
- 滞后差值 = 参考链头 − system_syncState.currentBlock。
- 滞后差值不断增长表明节点正在落后。
- 已同步节点上 peers=0 表明分区,而非健康。
Substrate 特有的注意事项:shouldHavePeers、过时的 highestBlock 和节点模式
shouldHavePeers 的语义很精确,容易被误读。它仅对真正无对等节点的角色为 false,例如轻客户端,或配置为不参与点对点网络的节点。对于验证人和平行链收集人,它为 true,因为这些角色预期会维护对等节点。如果你在预期为全节点的节点上看到 shouldHavePeers=false,请将其视为配置信号,而非暂时性错误。
system_syncState 中的 highestBlock 可能过时。它代表节点对网络最佳区块的视图,该视图源自其对等节点;如果节点被分区或其自身对等节点落后,highestBlock 会低估真实的网络链头。这正是为什么滞后差值应针对独立的参考链头计算,而不是仅针对 highestBlock。将 highestBlock 同时用作目标和当前值会掩盖你正试图检测的滞后。
节点模式会改变你的读取保证。全节点会修剪历史状态,因此对旧状态的读取可能失败或不可用;归档节点保留历史状态,可以服务这些读取。Polkadot 关于节点操作和同步模式的文档是“syncing”、“full”和“archive”含义的权威来源。你的健康探针应记录它期望的模式,因为一个完全健康的全节点仍然会无法完成归档节点可以服务的历史状态查询。
- shouldHavePeers=false:轻客户端或无对等节点配置。
- shouldHavePeers=true:验证人和平行链收集人。
- highestBlock 源自对等节点,可能低估真实链头。
- 全节点修剪状态;归档节点保留状态——读取保证不同。
一个返回 {healthy, reason} 的可运行 Node.js 探针
下面的示例使用 @polkadot/api,其类型化的 system.* 访问器记录在 Polkadot-JS API 文档中。它读取身份前导信息,调用 system.health()、system.syncState() 和 system.peers(),然后通过 chain.getHeader() 读取本地参考链头。它计算滞后差值并返回结构化判定。JSON-RPC 2.0 规范管辖该库底层发出的请求/响应信封。
该探针刻意保守:在字段缺失时失败关闭,因为提供商管理的端点可能隐藏或省略某些 system 字段。将缺失字段视为“未知”,而非“健康”。针对你自己的端点运行它,并将输出记录到下一节的结果表中。
// probe.mjs — run with: node probe.mjs wss://your-endpoint
import { ApiPromise, WsProvider } from '@polkadot/api';
const endpoint = process.argv[2];
if (!endpoint) { console.error('usage: node probe.mjs <ws-endpoint>'); process.exit(2); }
const MAX_LAG = 5; // blocks of tolerance before flagging lag
const MIN_PEERS = 1; // a synced node with 0 peers is partitioned
async function probe(endpoint) {
const api = await ApiPromise.create({ provider: new WsProvider(endpoint) });
try {
// 1. Identity sanity preamble
const [name, version, chain] = await Promise.all([
api.rpc.system.name(),
api.rpc.system.version(),
api.rpc.system.chain(),
]);
// 2. Native health surface
const health = await api.rpc.system.health();
const sync = await api.rpc.system.syncState();
const peers = await api.rpc.system.peers();
// 3. Local reference head via chain_getHeader
const header = await api.rpc.chain.getHeader();
const referenceHead = header.number.toNumber();
const currentBlock = sync.currentBlock.toNumber();
const lag = referenceHead - currentBlock;
const peerCount = health.peers.toNumber();
const isSyncing = health.isSyncing.valueOf();
const shouldHavePeers = health.shouldHavePeers.valueOf();
let healthy = true;
let reason = 'ok';
if (lag > MAX_LAG) { healthy = false; reason = `lag=${lag} exceeds ${MAX_LAG}`; }
else if (peerCount < MIN_PEERS && shouldHavePeers) {
healthy = false; reason = `peers=${peerCount} but shouldHavePeers=true`;
} else if (isSyncing && lag > MAX_LAG) {
healthy = false; reason = 'syncing and behind reference head';
}
return {
healthy, reason,
identity: { name, version, chain: chain.toString() },
health: { peers: peerCount, isSyncing, shouldHavePeers },
sync: {
startingBlock: sync.startingBlock.toNumber(),
currentBlock,
highestBlock: sync.highestBlock.toNumber(),
},
referenceHead,
lag,
peerSample: peers.slice(0, 3).map((p) => ({
peerId: p.peerId.toString(),
role: p.role.toString(),
bestNumber: p.bestNumber.toNumber(),
})),
};
} finally {
await api.disconnect();
}
}
probe(endpoint)
.then((r) => { console.log(JSON.stringify(r, null, 2)); process.exit(r.healthy ? 0 : 1); })
.catch((e) => { console.error('probe failed:', e.message); process.exit(3); });针对你自己的端点填写的结果表
在一天中的不同时间针对你自己的端点运行上述探针,并记录下面的值。目标不是单次读数,而是基线:你需要了解你的端点的正常滞后和对等节点数量是什么样,这样告警阈值才有意义。不要照搬其他提供商的阈值;测量你自己的。
每次运行填写一行。如果某个字段缺失,请写“unknown”而不是猜测,并注明——一个隐藏 system 字段的提供商管理端点会改变你可以断言的内容。
- 时间戳 | 端点 | chain | version | peers | isSyncing | shouldHavePeers | currentBlock | highestBlock | referenceHead | lag | healthy | reason
- 示例行:2026-09-20T00:00Z | wss://… | Polkadot | <version> | <n> | false | true | <n> | <n> | <n> | <n> | true | ok
- 在 24 小时窗口内至少重复三次,以观察滞后变化。
- 在端点旁边记录节点模式(全节点或归档节点)。
组合复合判定
单个方法无法产生可信的判定,因此请按固定顺序组合它们。首先,用 system_name、system_version 和 system_chain 确认身份。其次,读取 system_syncState 并针对参考链头计算滞后。第三,读取 system_health.peers 和 shouldHavePeers 以检测分区。第四,抽样 system_peers 以查看你的对等节点自身是否接近链头。
决策逻辑很小:如果滞后超过你测量的容差,则不健康;如果 shouldHavePeers 为 true 且 peers 为零,则不健康;如果 isSyncing 为 true 且连续探测中滞后不断增长,则不健康。其他情况均为健康。这反映了RPC 节点监控:指标、告警和故障转移中与链无关的方法,但字段是 Substrate 原生的,而非 Prometheus 计数器。
- 身份 → syncState 滞后 → health peers → peers 抽样。
- 滞后超过容差、发生分区或同步时滞后增长时判定失败。
- 将判定保持为 {healthy, reason},以便调用方记录原因。
门控架构:间隔探测、健康端点和故障转移
在生产环境中,按间隔运行探针,并在内部健康端点上暴露判定。你的应用程序应在路由流量之前读取该端点,并在判定翻转为不健康时排空该端点。这与 EVM 端点使用的门控模式相同,但触发字段是 Substrate 的滞后和对等节点数量,而非 EVM 区块高度。
配置故障转移,使排空将流量转移到第二个端点,然后在将排空的端点返回池之前重新探测它。由于节点可以快速恢复对等节点数量但仍落后,因此需要连续两次健康探测才能重新接纳端点。你所选择端点之间的延迟特性在Polkadot RPC 延迟:测量与优化中单独讨论。
- 间隔探测将 {healthy, reason} 写入内部健康端点。
- 应用程序在路由前读取健康状态;不健康时排空。
- 故障转移到第二个端点;需要两次健康探测才能重新接纳。
- 记录 reason 字符串,以便事件可诊断。
故障模式与故障排除清单
当探针报告不健康时,请逐步排查原因,而不是盲目重启。对等节点健康时滞后增长通常意味着节点受 CPU 或磁盘限制。对等节点为零时滞后增长意味着网络分区。长时间报告 isSyncing=true 的节点确实在追赶,这在停机后是预期的,但在稳定状态下则不是。
如果探针本身无法连接,那是传输问题,而非健康判定——该类故障请参阅Polkadot RPC 超时错误。如果对已最终确定状态的读取行为异常,请检查Polkadot 最终性:GRANDPA 证明和已最终确定的链头,因为最终性和链头进度是相关但不同的信号。
- 滞后增长 + 对等节点健康:怀疑节点资源饱和。
- 滞后增长 + 零对等节点:怀疑网络分区。
- 持续 isSyncing=true:节点在停机后正在追赶。
- 探针连接失败:传输问题,而非健康判定。
- system 字段缺失:提供商管理端点隐藏了它们;标记为未知。
局限性与权衡
原生健康接口并非没有权衡。探测每个方法都需要一次往返,因此四调用的探针就是四次请求;如果你要探测许多端点,请批量处理或降低频率。某些节点需要先对外暴露 RPC 服务器(rpc-external 风格的标志),这些方法才能被访问,这是一个具有安全影响的部署决策。
提供商管理的端点可能隐藏或省略某些 system 字段,这意味着你的探针必须将缺失数据视为未知,而非健康。最后,健康接口告诉你的是节点的情况,而不是它返回数据的正确性;一个节点可能健康,但仍处于你不想要的叉上。对于链级保证,请将此探针与最终性检查结合使用。
- 每个方法都是一次往返;批量处理或降低探测频率。
- 这些方法可能需要对外暴露 RPC 才能访问。
- 提供商管理的端点可能隐藏字段——将缺失视为未知。
- 健康不等于正确;请与最终性检查配合使用。
后续步骤和相关阅读
首先针对 Polkadot 端点运行探针,并为你的环境填写结果表。如果你需要一个测试端点,请参阅 Polkadot 网络页面和 Polkadot RPC 指南获取连接详情。有关定价和服务选项,请查看 RPC 定价和 API 服务。
要深入了解,请阅读RPC 节点监控:指标、告警和故障转移中与链无关的监控模式,检测 RPC 节点链头滞后和过时响应中基于 EVM 的滞后讨论,以及Polkadot RPC 延迟:测量与优化中的延迟测量方法。OnFinality Learn 中心汇集了基础设施系列的其余内容。
- 运行探针,填写表格,根据你自己的基线设置阈值。
- 通过 Polkadot 网络页面连接。
- 与 RPC 节点监控进行比较。
- 查看 RPC 定价和 API 服务。