区块链应用的体验好坏,很大程度上取决于RPC接口的响应速度。一笔交易从钱包签名到节点确认接收,中间要经过RPC调用这个环节;一次DApp页面的余额刷新、Gas价格查询、区块高度轮询,底层全部依赖RPC请求。如果RPC接口延迟高、连接不稳定,用户感知到的就是页面卡顿、交易提交失败、数据迟迟不更新。而区块链节点天然部署在固定机房,用户却分布在世界各地,这个矛盾就是节点加速要解决的核心问题。

RPC延迟到底慢在哪里
先拆解一次RPC调用的完整链路。客户端发起请求,经过本地网络、运营商骨干网、跨洋海底光缆(如果是国际访问),到达节点所在机房,节点处理请求后原路返回。这个链路里,最容易成为瓶颈的是网络传输段,而不是节点本身的处理能力。
以太坊全节点执行一次eth_getBalance这类简单查询,本地处理耗时通常在几毫秒以内,即使是eth_call模拟合约执行,大多数情况也在几十毫秒内完成。但如果用户在中国访问部署在美国弗吉尼亚的节点,单纯网络往返延迟(RTT)就有180到250毫秒,一次串行请求叠加上TCP握手、TLS握手,首次请求总耗时轻松超过500毫秒。更糟的是移动网络下丢包率偏高,TCP重传会让延迟进一步放大。
除了物理距离,公共RPC服务(如Infura、Alchemy的免费额度)还有两个隐性问题:一是限流,高频轮询容易触发429错误;二是共享出口,高峰期排队导致尾延迟(P99)飙升。很多团队抱怨接口偶尔卡几秒,多数就出在这些地方。
全球就近接入:Anycast与边缘网关
解决物理距离问题的标准做法是让用户就近接入。Anycast(任播)技术允许多个机房宣告同一个IP,BGP路由会自动把用户请求引导到拓扑上最近的节点。Cloudflare、AWS Global Accelerator都提供这类能力。用户在东京访问和用户在法兰克福访问,虽然目标IP相同,但实际落地的网关完全不同。
在边缘网关背后,需要一层转发服务把请求送回真正的区块链节点集群。这里有几个关键点:网关与节点之间走优质专线或骨干网,而不是公网直连;网关之间可以做请求合并与缓存,减少回源量;对WebSocket订阅类连接,需要在网关维持长连接池,避免每个用户都直连节点。
一个简化的Nginx就近网关配置如下,核心是负载均衡加上健康检查,配合keepalive复用连接降低握手开销:
# nginx.conf 关键配置片段
upstream eth_nodes {
least_conn; # 最少连接数调度
server 10.0.1.11:8545 max_fails=3 fail_timeout=10s;
server 10.0.1.12:8545 max_fails=3 fail_timeout=10s;
keepalive 64; # 与后端保持长连接池
}
server {
listen 443 ssl http2;
server_name rpc.example-ipipp.com;
location / {
proxy_pass http://eth_nodes;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_connect_timeout 2s; # 连接超时收紧
proxy_read_timeout 30s;
proxy_buffering off; # 流式响应,降低首字节延迟
}
location /ws { # WebSocket 订阅通道
proxy_pass http://eth_nodes;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s; # 长连接保活
}
}注意配置里两处细节。与后端节点之间的keepalive池能省掉每次请求的TCP握手,这在高频轮询场景下能节省几十毫秒;WebSocket路径的超时必须拉长,否则订阅连接会被网关频繁掐断,客户端会陷入反复重连的循环,反而加重延迟。
缓存与请求合并:减少无效回源
区块链数据有一个很好的特性:历史数据不可变。已经确认的区块、历史交易回执、过去的日志事件,查询一百次结果都一样。这意味着大量查询可以在网关层做缓存。比如eth_getBlockByNumber查询已确认区块、eth_getTransactionReceipt查询已打包交易,都可以根据区块高度设置安全缓存时间。实测中,区块浏览器类应用开启网关缓存后,回源请求量能下降60%以上。
另一类高频请求是只读的eth_call。合约的状态在同一个区块内不变,因此eth_call的结果可以在当前区块高度周期内缓存,长度大约等于出块间隔。以太坊是12秒出一个块,缓存10秒左右是安全的;如果接的是Layer2网络,出块更快,缓存时间要相应缩短,具体应跟随最新区块号动态调整。
对于无法缓存但请求频繁的场景,可以用批量接口。JSON-RPC原生支持batch请求,把客户端轮询的多个余额查询、多个代币信息查询合并成一次HTTP请求。假设页面需要查询20个地址的余额,串行发起20次请求,按每次150毫秒延迟算要3秒;合并为一个batch后只需一次往返,总耗时降到200毫秒以内。配合WebSocket多路复用,效果更明显:
// 将多个 eth_getBalance 合并为一次批量请求
const payload = addresses.map((addr, i) => ({
jsonrpc: "2.0",
id: i,
method: "eth_getBalance",
params: [addr, "latest"]
}));
const res = await fetch(RPC_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
const balances = await res.json();
// 按需解析,注意批量返回顺序不一定与请求一致,要靠id对应
balances.forEach(r => {
if (r.id !== null && addresses[r.id]) {
console.log(addresses[r.id], BigInt(r.result));
}
});自建节点与混合调度策略
当业务量上来之后,纯依赖公共RPC既贵又不可控,自建节点成为必选项。自建不等于只部署一台机器,合理的架构是分层的:写入类请求(sendRawTransaction)走自建全节点,保证交易广播的低延迟和隐私性;读类请求按区域分布多个节点,配合归档节点分担历史查询。节点本身的客户端选择也有讲究,Geth、Erigon、Reth在磁盘占用和查询性能上差异明显,Erigon和Reth对批量历史查询更友好。
区域调度上,可以用DNS智能解析或者全局负载均衡把用户导向最近的接入点,同时设置故障切换:某个区域的节点同步落后超过设定的区块数(比如落后5个区块),调度层就自动把该区域流量切走,避免把过期数据返回给用户。监控指标建议至少采集P50、P95、P99延迟,节点同步高度差,以及错误率,用Prometheus加Grafana搭建面板即可。
最后给一个务实的组合建议:中小项目可以用公共RPC加CDN边缘缓存起步,成本几乎为零;交易量上来了再在两个到三个核心区域自建节点,前置Anycast网关;所有客户端统一通过自有域名访问,方便后端随时切换和扩容。RPC访问链路一旦设计好,后续无论换节点服务商还是增加区域节点,客户端都无需改动,这本身就是一种架构上的低延迟保障。