首页 / 资讯中心 / 文章详情

以太坊区块链浏览器搭建:节点、索引与数据库调优实战

以太坊区块链浏览器搭建:节点、索引与数据库调优实战 ★ FEATURED ARTICLE
搞浏览器这件事我在不同项目里前后搭过四五次以太坊区块链浏览器第一次折腾了整整两周最后一次一个下午就跑通了全链路。差别全在几个关键分岔口选得对不对执行层用什么客户端、历史状态要不要留全、索引服务跟节点是不是放在同一台物理机上。这篇文章就把这些路口一个个摊开包括每一步为什么要这么选、参数怎么算出来、跑起来之后会卡在什么地方。以太坊区块链浏览器听上去像个庞然大物本质上是一条数据管道加一个查询界面底层全节点提供 JSON-RPC 接口中间索引器把区块、交易、地址、代币、合约事件一条条抓下来落库上层 Web 服务把这些结构化数据渲染成页面。这套东西适合正在做链上数据平台、私有链或测试链运维、又或者单纯想搞清楚区块浏览器里那些数字到底从哪来的朋友。有 Docker 基础就能跟着走完全没碰过区块链节点的也能看懂每一步在干什么。1. 架构先想清楚三条路线与它们的成本边界动手开机之前我建议先花半小时把路线定下来。这一步定错后面所有的调优都是白费。我看到过太多人上来就docker-compose up跑了三天发现内部交易全是空的、代币余额查不出来回头重来一遍索引一周时间就没了。1.1 自建、托管与只做查询层的三条路第一条是完全自建自己跑执行层节点和共识层节点自己跑索引器自己维护数据库。优点是数据完全自主可以任意定制字段、加自己的业务表、把链上数据和线下数据关联起来缺点是硬件和运维成本不低主网全量索引动辄几百 GB 到数 TB 的数据库磁盘 IO 是硬门槛。适合对数据有强诉求、需要二次开发的场景比如做链上风控、做地址画像、做审计留痕。第二条是只做查询层节点直接用现成的第三方 RPC 服务索引器指向远端接口本地只有数据库和 Web 服务。这条路的门槛低很多一台 8 核 32G 的机器就能跑起来但会撞上两个硬限制。一是远端 RPC 通常对请求频率、单次返回条数、并发连接数都有配额索引器回填历史数据的时候会把配额瞬间打爆二是很多方法尤其是debug_trace*和trace_*这类追踪类接口在免费额度里根本不开放而恰恰是这些接口决定了浏览器能不能显示出内部转账。所以这条路我只推荐给测试链、私有链或者做演示原型的场景。第三条是混合路线本地跑节点只负责提供 RPC索引器和数据库放另一台机器前端再加一层 CDN。这是我自己项目里最常用的形态节点机器吃 IO 和带宽应用机器吃 CPU 和内存两类资源错峰互不干扰。下面所有配置我都按这条路线写另外两条把对应部分砍掉即可。1.2 组件拆解一条数据的完整旅程先搞清楚一次用户在浏览器里点开某个地址背后发生了什么后面的配置才知道每一项在管什么。执行层节点维护账户状态、区块、交易、收据对外提供eth_*、debug_*、trace_*等 RPC 方法。它是唯一的数据源头。共识层节点合并之后执行层必须挂一个共识层客户端才能确认链头。两者通过 Engine API 通信端口默认 8551用 JWT 做认证。没有共识层的执行层只能同步到合并前的高度这一点新手最容易忽略。索引器Indexer周期性地向节点拉取新区块解析出交易、日志、内部调用写入关系型数据库。它还要做反查表的构建比如某地址的所有交易某代币的所有转账这些不是链上原生就有的索引得靠索引器自己算。关系型数据库存所有结构化结果。它是整个系统里最吃磁盘和内存的组件也是索引速度的真正瓶颈所在。Web 服务读数据库、渲染页面、提供对外 API。它是只读的可以横向扩多个实例。反向代理负责 TLS 终止、静态资源缓存、限流。一条交易的完整生命周期是节点产块 → 索引器轮询到新块 → 解析交易和收据 → 写入transactions、logs、addresses等表 → 更新地址余额与计数器 → Web 服务从库里查出来给用户看。理解了这条链路你就明白为什么节点同步完了但浏览器页面还是空的——那是索引器还没跑或者索引器跑失败了。2. 硬件盘算先把账算明白再动手我见过最典型的翻车是拿一台 4 核 8G 的云主机配了块普通云盘然后问我为什么同步一个月还没好。以太坊主网节点的资源需求和一台普通 Web 服务器完全是两个量级这里必须提前算清楚。2.1 磁盘、内存、带宽的估算方法磁盘是第一优先级而且是唯一不能妥协的一项。执行层全节点快照同步模式的数据目录在 1TB 量级起步并且随着链的增长每年都在往上走如果选归档模式那得按十 TB 量级去算。共识层数据相对小一些几十到一百多 GB。再加上索引数据库主网全量索引通常落在几百 GB 到数 TB 之间具体取决于你启用了多少张表、保留多久的历史。我的经验算法是这样的节点数据目录 当前链数据量 × 1.5给增长和临时文件留余量数据库 节点数据目录 × 1.5 到 2索引表带索引和冗余字段膨胀比很高。两块盘分开挂别放同一个分区否则数据库做 vacuum 的时候会把节点的 IO 全部抢走。盘的类型必须是NVMe SSD。SATA SSD 勉强能跑但同步和索引的时间会翻两三倍机械盘直接放弃随机读写完全跟不上同步阶段会表现为区块高度半天不动。这一点我在早期用机械盘试过一次同步速度大概是 NVMe 的十分之一而且不断因为超时重连。内存方面执行层节点 16GB 起步32GB 会舒服很多共识层 8GB 够用数据库单独一台的话 32GB 起步shared_buffers能吃下大部分热数据之后索引速度会有明显提升。如果全挤在一台机器上我建议至少 64GB否则 OOM Killer 会在半夜随机干掉你的某个进程。带宽主要影响同步阶段。全量同步要下载的数据量在 1TB 以上100Mbps 的下行大概能让你在几天内完成执行层同步如果只有 10Mbps那基本上是以周为单位。同步完成之后的日常运行带宽消耗很小索引器和节点通常在同一个内网走的是本地回环。下面这张表是我自己在不同规模下的配置参考按实际预算上下浮动即可场景CPU内存磁盘带宽预期首次可用时间测试链 / 私有链4 核16GB500GB NVMe10Mbps数小时主网仅节点 轻量索引8 核32GB2TB NVMe ×250Mbps3~7 天主网全量索引 完整内部交易16 核64GB4TB NVMe ×2100Mbps2~4 周2.2 全节点、归档节点、裁剪节点的分岔这个选择直接决定了磁盘开销和浏览器的功能完整度必须提前想清楚。裁剪节点pruned只保留最近一段时间的状态数据历史状态会被逐步删掉。磁盘占用最小同步最快日常出块验证完全够用。但它有个致命短板——对历史区块做状态追踪会失败。也就是说当用户点开一笔三年前的交易想看内部转账节点会告诉你状态不可用。这是很多自建浏览器只能看最近几个月的根本原因。归档节点archive保留每一个区块高度的完整状态。任何历史区块的追踪都能成功磁盘占用按十 TB 量级算同步时间以周计。如果你要做的是面向全历史的公开浏览器这是唯一正确的选择。带历史的完整节点这是中间态。以 Erigon 为代表的客户端默认保留全部区块和收据的历史同时对状态做了一定的裁剪策略磁盘占用比归档小一个数量级但对大多数历史区块的追踪依然可用。这是目前自建浏览器性价比最高的选择也是我在生产环境里用得最多的一种。提示如果你选的是裁剪型客户端一定要在配置里把内部交易抓取关掉否则索引器会不停重试失败的历史区块日志里刷满超时错误还会拖慢新区块的索引速度。3. 节点层落地执行层与共识层一起跑节点是整个系统的地基。地基没打好上面所有配置都是浮沙。这一节我按选型 → 启动 → 验证的顺序写命令可以直接抄。3.1 客户端选型与对应的浏览器兼容性执行层客户端有好几个成熟实现对浏览器的友好程度差别很大这是选型时最容易忽略的一点。Geth 系生态最广、文档最全、社区问题最好搜。缺点是默认裁剪状态历史追踪能力弱而且它不提供trace_block这类批量追踪方法浏览器只能退而求其次用debug_traceTransaction逐笔追踪速度慢且对老区块会失败。适合做私有链、测试链或者只关心近期数据的场景。Erigon 系磁盘占用小、同步快、保留完整历史追踪类接口齐全。它是目前自建公开浏览器的主流选择。代价是对磁盘 IO 更敏感数据库结构的维护动作多一些初次同步的配置项也偏多。Reth 系新一代实现同步速度非常快接口兼容性好但要选对模式——full模式仍然会裁剪历史状态想做全历史追踪得开归档。我的建议很直接要做公开的全历史浏览器选 Erigon 或 Reth 的归档模式只做内部工具或测试链Geth 足够。选型定下来之后浏览器的ETHEREUM_JSONRPC_VARIANT参数要跟它对应填错了会导致内部交易解析逻辑走错分支症状是交易详情页永远显示 0 条内部转账。3.2 JWT 认证与 RPC 暴露的安全边界合并之后执行层和共识层之间靠 Engine API 通信必须共享一个 JWT 密钥文件。生成方式很简单openssl rand -hex 32 /data/jwt.hex chmod 600 /data/jwt.hex这个文件两个客户端都要指向它。执行层的启动参数大致如下以 Geth 为例geth \ --datadir /data/geth \ --syncmodesnapshot \ --http \ --http.addr 127.0.0.1 \ --http.port 8545 \ --http.api eth,net,web3,debug,trace \ --http.vhosts localhost \ --authrpc.addr 127.0.0.1 \ --authrpc.port 8551 \ --authrpc.jwtsecret /data/jwt.hex \ --maxpeers 100 \ --cache 8192几个参数值得单独说。--http.addr一定要绑127.0.0.1绝不能绑0.0.0.0——一旦把带debug命名空间的 RPC 暴露到公网等于把整个节点的执行权限送出去了任何人都能通过追踪接口把你的机器 CPU 打满。--http.api里debug和trace是内部交易解析必需的但同时也是最耗资源的方法所以只能在回环地址上开让索引器从本地访问。--cache 8192是按机器内存给的一般来说给到物理内存的四分之一到三分之一比较合适这里假设是 32GB 的机器。共识层这边以 Lighthouse 为例lighthouse bn \ --network mainnet \ --datadir /data/lighthouse \ --execution-endpoint http://127.0.0.1:8551 \ --execution-jwt /data/jwt.hex \ --checkpoint-sync-url 社区提供的检查点同步地址 \ --http \ --http-address 127.0.0.1 \ --http-port 5052 \ --metrics --metrics-address 127.0.0.1--checkpoint-sync-url是个提速神器。共识层如果从创世开始同步要验证历史上每一个 epoch 的签名慢得让人怀疑人生用检查点同步可以从一个较新的信任点开始只回溯很短一段把共识层的同步时间从几天压到几十分钟。这是我最推荐的一个开关没有之一。3.3 同步观测与卡点处理同步过程中要盯三个地方执行层的区块高度、共识层的同步状态、以及两个客户端之间的连接是否正常。执行层的高度可以这样看curl -s -X POST -H Content-Type: application/json \ --data {jsonrpc:2.0,method:eth_syncing,params:[],id:1} \ http://127.0.0.1:8545返回false表示同步完成返回一个对象就说明还在追里面的currentBlock和highestBlock相减就是还差多少。注意这个差值不是线性的刚开始可能差几百万块但因为同步是按段并行拉的后期会突然收窄不要被一开始的数字吓到。常见的卡点有这么几类。高度长时间不动八成是 P2P 连不上对等节点检查--maxpeers和防火墙的 TCP/UDP 端口是否放通。高度在涨但共识层报 engine API 错误多半是 JWT 文件两边不一致或者端口写错。同步到某个高度突然反复回滚通常是磁盘空间不足或者 IO 超时看节点日志里的write error关键字。还有一个隐蔽的坑节点同步期间索引器如果已经在跑会对着不断变动的链头疯狂重试两边抢 IO。我的做法是节点同步完成度到 99% 以上再启动索引器不要一边同步一边索引那样只会让两边都变慢。4. 索引与数据层把链条数据变成可查的表节点跑通之后重头戏才刚开始。这一层是整个浏览器里最耗时、最容易出问题、也最能体现经验差距的部分。4.1 PostgreSQL 参数调优清单数据库是索引速度的真正瓶颈。默认配置的 PostgreSQL 是给一台小机器准备的直接拿来索引主网会慢到让你怀疑硬件坏了。下面是 64GB 内存、NVMe 磁盘的机器上我自己在用的参数参数建议值说明shared_buffers16GB物理内存的 25%索引期间热数据全在这effective_cache_size48GB告诉优化器操作系统还有多少缓存可用work_mem64MB排序和哈希操作的单次内存太小会落盘maintenance_work_mem2GB建索引、vacuum 时的内存max_wal_size16GB调大可以减少检查点频率批量写入更顺checkpoint_timeout30min配合上一条使用random_page_cost1.1SSD 必改默认 4.0 是按机械盘算的max_connections200索引器加 Web 服务加监控留足余量synchronous_commitoff临时只在首次大批量回填时关回填完必须改回 onrandom_page_cost这一项很多人会漏。它是优化器估算随机读代价的参数默认值假设磁盘有寻道时间所以你写一个带索引的查询优化器可能觉得全表扫描更划算然后真的去扫几千万行。改成 1.1 之后执行计划会重新倾向于走索引页面响应时间能差出好几倍。synchronous_commit关掉确实能显著提升写入吞吐因为它不用等 WAL 落盘就返回。但这意味着机器突然断电可能丢最后几秒的数据对索引来说可以接受重跑就行对生产环境不能接受。我的习惯是首次回填期间关掉回填完成后立刻改回on并重启数据库不要图省事一直留着。4.2 环境变量逐项拆解索引服务本身是配置驱动的环境变量填错一项行为就会偏。下面这些是最关键的一批DATABASE_URLpostgresql://explorer:******127.0.0.1:5432/explorer ETHEREUM_JSONRPC_VARIANTerigon ETHEREUM_JSONRPC_HTTP_URLhttp://127.0.0.1:8545 ETHEREUM_JSONRPC_TRACE_URLhttp://127.0.0.1:8545 ETHEREUM_JSONRPC_WS_URLws://127.0.0.1:8546 CHAIN_ID1 COINETH SECRET_KEY_BASE用随机生成器产出的一长串 PORT4000 INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHERfalseETHEREUM_JSONRPC_VARIANT必须和你的节点实现匹配这个前面说过填错的症状是内部交易和部分合约解析异常。ETHEREUM_JSONRPC_TRACE_URL单独拎出来是有原因的——追踪类请求很重如果和普通查询走同一个入口容易把普通查询也堵住分开配置的话可以把两者指到不同的节点实例上。SECRET_KEY_BASE是 Web 服务的会话签名密钥必须是一段足够长的随机串绝对不要用示例配置里那串默认值否则任何人都能伪造会话。还有个容易被忽略的代币元数据的获取需要外部网络访问。索引器解析到 ERC-20 合约之后会尝试调用合约的name、symbol、decimals方法有些实现还会去拉取链外的图片资源。如果机器完全隔离外网代币列表里会全是未知代币看起来像 bug其实是网络策略挡住的。4.3 索引器与 Web 进程分离部署同一个镜像可以跑两种角色靠开关区分# 索引器进程负责写库 docker run --env-file ./explorer.env \ -e DISABLE_INDEXERfalse \ -e DISABLE_WEBfalse \ blockscout/blockscout:latest # 只读 Web 进程负责查询渲染 docker run --env-file ./explorer.env \ -e DISABLE_INDEXERtrue \ blockscout/blockscout:latest分开部署的好处很实在索引器吃掉大量 CPU 和数据库连接的时候Web 服务不会跟着卡死。我自己遇到过好几次索引回填期间页面完全打不开的情况拆开之后这个问题就消失了。Web 进程可以开多个实例挂到反向代理后面索引器只能有一个多开会造成重复写入和唯一键冲突。数据库初始化这一步不要跳过首次启动前必须先跑迁移docker run --rm --env-file ./explorer.env \ blockscout/blockscout:latest \ ./bin/blockscout eval Elixir.Explorer.ReleaseTasks.create_and_migrate()版本升级之后也要重新跑一次这条命令否则新版本代码访问旧表结构会报一堆字段不存在。4.4 首次索引的时间预期与加速手段主网从创世块开始索引是个以天甚至以周计的过程。我在 16 核 64G NVMe 的机器上配合 Erigon 节点全量索引大概要两到三周如果只索引最近一年的数据能压缩到几天。这个数字跟节点追踪接口的响应速度强相关因为每一笔交易都要额外发一次追踪请求解析内部调用。想把时间压下来有几个实招。第一把索引的起始块设成一个较新的高度如果业务上不需要远古数据直接从最近一年开始能省掉七八成的时间。第二分批回填先把区块和交易这两张核心表灌满让页面能用起来代币、内部交易、合约验证这些次要模块后台慢慢补。第三临时关掉一些非必要模块比如代币持有者统计、合约源码验证这类计算量大的任务等主流程跑完再打开。第四监控数据库的写入延迟如果pg_stat_activity里大量会话卡在IO等待说明磁盘到瓶颈了这时候加 CPU 没用得换盘或者把 WAL 单独放到另一块盘上。注意索引过程中千万不要重启节点。节点重启会短暂不可用索引器拿不到数据会标记失败并进入重试队列有些任务的重试间隔是分钟级的一重启可能造成十几分钟的空转。5. 访问层反向代理、HTTPS 与缓存到这一步浏览器已经在127.0.0.1:4000上跑起来了接下来要把它安全地暴露出去。这一层配置不难但配错了会直接影响可用性。5.1 Nginx 关键配置反向代理这块最常出问题的是超时和请求体大小。区块浏览器的某些页面尤其是带大量日志的交易详情页响应时间可能超过默认的 60 秒请求体也可能比较大默认配置会直接返回 504 或 413。server { listen 443 ssl http2; server_name browser.example.com; ssl_certificate /etc/ssl/fullchain.pem; ssl_certificate_key /etc/ssl/privkey.pem; client_max_body_size 20m; location / { proxy_pass http://127.0.0.1:4000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_connect_timeout 30s; proxy_send_timeout 120s; proxy_read_timeout 120s; } location ~* \.(js|css|png|svg|woff2)$ { proxy_pass http://127.0.0.1:4000; expires 30d; add_header Cache-Control public, immutable; } } server { listen 80; server_name browser.example.com; return 301 https://$host$request_uri; }proxy_read_timeout从默认 60 秒提到 120 秒是我踩坑之后加的。有些复杂地址的详情页要聚合几十张表的数据冷缓存下确实会跑到七八十秒。当然如果经常触及这个上限说明该优化数据库查询了而不是继续加超时。X-Forwarded-Proto这一行不能少。少了它应用会以为自己跑在 HTTP 下生成的静态资源链接会是http://开头浏览器在 HTTPS 页面里加载 HTTP 资源会被拦截页面样式全崩。这个症状我第一次遇到时排查了很久因为日志里一切正常。5.2 静态资源与限流静态资源加长缓存是必做的前端打包出来的文件名带哈希可以放心设immutable。真正需要小心的是 API 路径——如果对外开放了查询接口一定要在代理层做限流否则很容易被人写脚本把数据库拖垮。limit_req_zone $binary_remote_addr zoneapi:10m rate10r/s; location /api { limit_req zoneapi burst20 nodelay; proxy_pass http://127.0.0.1:4000; # 其余代理头同上一段 }10r/s 这个值对正常用户完全够用一个页面最多也就几十个请求分散在几秒内对爬虫脚本则是明显的拦截。burst20允许短时突发避免用户同时打开多个标签页被误伤。6. 长跑阶段监控、备份与升级浏览器搭起来只是开始真正花时间的是后面几个月的维护。这一节讲的东西不性感但能省掉你半夜被叫起来的次数。6.1 必看的几个指标监控不用做得很复杂抓几个关键指标就够用。节点高度与链头高度的差值超过几十个块就说明同步落后了得查网络或者磁盘。索引器的最后处理区块高度它应该紧跟着节点高度如果差得越来越远说明索引速度跟不上出块速度长期下去会越拖越久。数据库连接数接近max_connections的时候新请求就会开始排队。磁盘剩余空间这个最要命节点数据目录写满会导致客户端直接崩溃而且恢复起来很麻烦。追踪接口的失败率如果突然升高通常意味着节点在裁剪历史状态或者某个版本的兼容性出了问题。我用一个很土但有效的办法写个定时脚本每五分钟把这些数字打到日志里再用日志系统做阈值告警。不需要什么高大上的方案能第一时间发现问题就行。6.2 升级与数据库迁移顺序节点客户端的升级要谨慎。执行层和共识层的版本要匹配尤其是 Engine API 有变更的版本先升共识层再升执行层或者反过来得看具体版本的发布说明。我的固定流程是先在测试环境跑一遍确认能正常同步、索引器能正常取数再动生产。生产环境升级前一定先停索引器节点升完并且追平链头之后再启动索引器避免中间过程中的数据不一致。浏览器本身的升级顺序是固定的先停索引器 → 跑数据库迁移 → 启动 Web 服务 → 启动索引器。跳过迁移直接启动症状是一堆字段相关的报错先启索引器再迁移可能写进去半截脏数据。这两条我都经历过代价是重新回填了几天。数据库备份不用每天都做全量增量加定期全量就够。真出问题的时候重跑索引比恢复备份还快——前提是你知道从哪个高度开始重跑。所以我习惯在配置里把索引起始块做成可调项出问题时把起点挪到最近一周几小时就能追平。7. 常见问题速查与避坑实录7.1 问题速查表现象大概率原因处理方向页面能开但数据全是空的索引器未启动或启动即失败看索引器日志确认DISABLE_INDEXERfalse交易详情页内部转账为 0节点不提供追踪方法或变体配置错误确认ETHEREUM_JSONRPC_VARIANT与节点匹配代币列表显示未知代币外部网络不可达元数据拉取失败检查出网策略或手动补录元数据节点高度长时间不动P2P 端口未放通或对等节点不足检查 TCP/UDP 端口加大maxpeers共识层报 engine API 错误JWT 文件不一致或端口错核对两边路径与 8551 端口索引速度越来越慢数据库膨胀、索引碎片检查pg_stat_activity必要时重建索引页面 504查询超时先调proxy_read_timeout再优化慢查询磁盘突然写满节点数据增长或 WAL 堆积检查max_wal_size与归档策略静态资源 404、样式错乱反向代理缺X-Forwarded-Proto补上该请求头重启后索引从头再来起始块配置被覆盖检查环境变量与配置文件的优先级7.2 三个我真正踩过的坑第一个坑以为节点同步完就万事大吉。早期我只看eth_syncing返回false就以为可以了结果索引器取到的区块数据里收据字段是空的原因是节点还没完成收据的回填快照同步是分阶段进行的。正确做法是看节点日志里有没有明确的同步完成标记或者用eth_getBlockReceipts抽查几个近期区块确认收据能正常返回。第二个坑把 Web 服务和索引器塞进同一个容器。一开始图省事只跑一个进程结果索引回填期间页面完全打不开用户体验极差。拆成两个容器之后哪怕索引器在满负荷跑Web 服务依然能正常响应。资源上多花的那点内存换来的是可用性非常值。第三个坑数据库没做定期维护。索引跑了几周之后某些查询突然从几百毫秒涨到十几秒。查下来是表膨胀严重死元组占比很高autovacuum 的默认阈值对这种高频写入的表根本不够用。后来给几张核心表单独设了更激进的 autovacuum 参数并且每周跑一次ANALYZE查询计划才恢复正常。我个人在实际操作中的体会是这套系统里真正难的不是把服务跑起来——现在容器化之后命令抄一抄半小时就能起——而是提前把数据量和时间成本算清楚。磁盘够不够、索引要跑多久、哪些功能可以先用简化版顶上这三个问题想明白了后面就是按部就班地填配置。反过来如果一开始就抱着先跑起来再说的心态大概率会在索引跑到一半的时候发现盘满了然后从头再来一遍。
阅读完成 · 觉得有帮助?
咨询建站