Web3开发中的链上事件监听与数据同步,不能只靠一条实时订阅连接。比如应用要展示某个用户的 ERC-20 转账记录,若 WebSocket 短暂断开,期间发生的事件就可能没有推送到应用。更稳妥的做法是:历史区块通过 JSON-RPC 补扫,实时区块通过 WebSocket 接收,再用检查点和去重逻辑衔接两条数据流。
先弄清楚要同步的是什么
以 Ethereum 上的 ERC-20 合约为例,转账会发出 Transfer 事件。监听时应限定目标合约地址,并按事件签名和需要的地址筛选日志,避免把无关合约的同类事件混进来。常用日志字段包括 blockNumber、blockHash、transactionHash 和 logIndex;它们分别帮助定位区块、交易及交易内的日志顺序。
还要区分“收到事件”和“业务状态已更新”。事件可以用来生成转账记录或触发后续处理,但应用余额最好根据合约状态查询或可靠地重建,不能把一条推送直接当作永久、最终的状态依据。
用补扫加实时订阅,建立连续数据流
- 明确范围。记录合约地址、事件类型、起始区块和目标业务字段。过滤条件尽量具体,减少无关日志和处理成本。
- 先做历史补扫。调用 JSON-RPC 的 eth_getLogs,按区块范围查询事件。服务商对单次查询的范围和结果大小可能有限制,应按其规则拆分区间;失败时重试较小区间,并记录已完成的最后区块。
- 启动实时订阅。使用 WebSocket 的 eth_subscribe 订阅目标日志。订阅适合低延迟接收,但连接中断时通常不会替应用自动补齐缺失区间,因此不能把它当作唯一数据源。
- 处理两路数据的交界。保存补扫进度,并让实时日志也经过同一套去重逻辑。应用重启或连接恢复后,从已确认的检查点继续查询,必要时重叠扫描一小段区块,再去重,避免边界处漏记。
- 入库并推进检查点。只有日志处理和数据库写入成功后才推进进度。若采用队列,可先可靠地保存待处理消息,再由消费者更新业务表;不要在写库失败时仍标记该区块已完成。
把重复、断线和区块重组纳入设计
用幂等键阻止重复入账
同一日志可能因重试、补扫与实时推送同时到达。可用“链标识、交易哈希、日志序号”组成唯一键,并让数据库在重复写入时安全忽略或更新。这样重试不会生成两条转账记录,也不会重复触发不可逆的业务操作。若业务需要撤销或冲正,应单独设计状态变更流程。
重组时撤回旧分支数据
区块重组可能使已观察到的日志不再属于当前主链。比较同一高度的 blockHash,发现分支变化时,回退受影响区块之后的派生记录并重新补扫。WebSocket 日志中的 removed 标记也值得处理,但不能只依赖它:断线期间可能收不到相关通知。对不要求即时展示的结果,可以等待若干确认后再对外标记为稳定;具体等待深度取决于链、服务要求和风险承受度。
上线前验证同步是否真的连续
监控至少应覆盖最后处理区块、最新链高度、补扫延迟、订阅连接状态、重试次数和重复日志数量。若处理进度长时间落后,先检查 RPC 服务是否限流、区块区间是否过大、数据库是否积压,而不是盲目增加订阅数量。
测试时可主动重启监听进程、断开 WebSocket,并检查恢复后是否从检查点补齐日志;还应验证重复消息不会重复写入,以及重组后旧分支记录能否被修正。Web3开发中的链上事件监听与数据同步,关键不是追求永不断线,而是让每次中断都能被发现、重放并核对。
常见问题
只用 WebSocket 监听可以吗?
不建议作为唯一方案。它适合实时通知,但连接断开后需要通过历史查询补齐空档。
区块范围应该一次查多大?
没有适用于所有节点服务的固定值。先按服务商限制和返回体大小设置较小区间,再根据超时、限流和响应耗时调整。
如何避免同一事件处理两次?
为日志建立稳定的唯一键,并让写入和业务更新支持幂等;队列重试也应使用同一规则。
检查点记录什么信息?
至少保存已成功处理的区块高度;需要识别重组时,还应保存对应区块哈希,以便发现分支变化并回退重扫。
按“历史补扫、实时订阅、幂等入库、重组校验”形成闭环,Web3开发中的链上事件监听与数据同步才更容易恢复和审计,数据遗漏也更容易被及时发现。