如何高效地监听链上几万个合约的 Event Log(日志)

6 阅读4分钟

这是一个非常经典的区块链后端开发面试题,考察的是你对 以太坊数据结构RPC 通信成本 的深度理解。

看不懂这个答案,通常是因为不了解 “为什么 eth_getLogs 慢” 以及 “Bloom Filter(布隆过滤器)是个什么神奇东西”

我用大白话和生活中的例子给你拆解一下。


1. 为什么“笨办法”行不通?

场景

你需要监听 1万个 合约地址的事件(Event)。 区块链每 12 秒产生一个新的区块。

笨办法(直接轮询)

每当新区块出来,你都向节点(Node)发送请求:

“哎,节点兄弟,帮我查查这个新块里,有没有关于这 1 万个地址的日志?”

问题在哪里?

  • RPC 请求爆炸:如果你一个一个查,要发 1 万次请求。如果你合并成一个大请求,参数会巨长。
  • 节点磁盘 IO 爆炸eth_getLogs 是一个很“重”的操作。节点收到请求后,必须去它的数据库(LevelDB/RocksDB)里翻阅日志索引。即使这个块里没有任何你关心的交易,节点也得去硬盘里确认一遍“确实没有”。
  • 结果:当你要监听的合约变多时,节点会被读废,响应变慢,甚至拒绝服务。

2. 什么是 Bloom Filter(布隆过滤器)?

在不谈技术细节的情况下,你只需要记住它的核心特性

它像是一个极其压缩的信息指纹

  • 特点 1:如果布隆过滤器说“不存在”,那就绝对不存在。 (100% 准确)
  • 特点 2:如果布隆过滤器说“存在”,那“可能存在”,也可能不存在。 (存在误判率)
  • 特点 3:体积极小,检查速度极快。

举个生活例子

你在管理一个有 1000 个房间的酒店(区块链),你要找“张三”(你关注的合约)。

  • 笨办法 (eth_getLogs):你挨个敲 1000 个房间的门,问“张三在吗?”。这累死人。
  • 布隆过滤器 (LogsBloom):酒店大堂门口有个公告板
    • 规则:如果你姓“张”,就在第 1 行打个勾;如果你名字是两个字,就在第 2 行打个勾。
    • 现在你要找“张三”。你先看公告板:
      • 情况 A:第 1 行没有勾。结论:张三绝对不在里面。(因为如果在,他肯定会打勾)。-> 直接走人,不用敲门了。
      • 情况 B:第 1 行有勾,第 2 行也有勾。结论:张三“可能”在里面。(但也可能是“张四”、“李三”或者“王麻子”碰巧打的勾)。-> 这时候你再去敲门确认。

3. 以太坊是怎么利用这个特性的?

在以太坊中,每个区块头(Block Header) 都有一个字段叫 logsBloom(大小只有 256 字节)。

这个 logsBloom 就是那个“公告板”。它把这个区块里所有交易产生的所有日志的:

  1. 合约地址 (Address)
  2. 事件主题 (Topic)

都映射(Hash)进去了。


4. 优化后的流程(话术里的逻辑)

现在你手握 1 万个合约地址,新区块来了:

  • Step 1:只下载“区块头”(轻量级)

    • 你只调用 eth_getBlockByNumber(..., false)。这个数据非常小,不用查交易详情,节点返回飞快。
    • 你拿到了这个块的 logsBloom(公告板)。
  • Step 2:在本地算一下(CPU 计算,不耗网络)

    • 你自己写代码,把你关注的 1 万个地址,去跟这个 logsBloom 比对。
    • 判断逻辑:这个块里有没有可能包含我的合约地址?
  • Step 3:决策

    • 结果是“不可能”(绝大多数情况):
      • 这 1 万个合约在这个块里都没动静。
      • 动作:直接忽略这个块。省下了去请求 eth_getLogs 的巨大开销。
    • 结果是“可能”(少数情况):
      • 布隆过滤器显示命中(可能是真的命中了,也可能是误判)。
      • 动作:这时候才发起 eth_getLogs 去查详情。就算查回来发现是空的(误判),也无所谓,因为这种次数很少。

总结(如何回答面试官)

你可以这样重新整理思路回答,会显得更清晰:

  1. 痛点:直接轮询 eth_getLogs 对节点 IO 压力太大,尤其是监听数万个合约时,大部分区块其实都没有相关交易,查询是无效的浪费。
  2. 原理:以太坊区块头包含一个 logsBloom 字段,这是一个 256 字节的布隆过滤器,它聚合了该区块所有日志的地址和 Topic。
  3. 方案
    • 先拉取区块头(轻量请求)。
    • 本地内存中,用我关注的合约地址列表去匹配这个 Bloom Filter。
    • 利用布隆过滤器**“说没有就绝对没有”**的特性,我可以安全地跳过 90% 以上无关的区块,完全不需要发起 eth_getLogs 请求。
    • 只有当 Bloom Filter 提示“可能存在”时,我才去请求完整的日志详情。
  4. 收益:将大量的网络 IO 和数据库读取(Rpc调用),转化为了本地极快的 CPU 位运算,性能提升非常明显。