BlockChain 区块事件处理最佳实践方案的演变流程

首先说 watch dog 日志走 subscribe 是一件非常愚蠢的行为, 要进行强烈的游街批判.

说说我们 watch dog 的历史演变

version 1.0.1 web3 http + private geth 版

该版本底层轮训机制, 拉取最新块的日志信息, 相当于实现了日志推送.

一次性获取最新块的全部日志, 通过 address 和 topics 循环判断日志是否属于我们, 我们的日志又是哪一个 event 事件触发出来的.

解析出内容后, 直接将解析好的内容抛给另一个服务, 用于根据日志内容处理后续逻辑.

在服务器挂掉之前, 将当前处理进度写进一个文件, 用于启动后继续执行, 挽回损失.

那么说说本版本的问题:

原来我们相应代码使用私有链, 准备迁往 infura, 当前 provider 不支持, web3 还是 0.20 左右版本, provider 也很老旧, 很多文档不全, 文不达意的情况.

version 2.0.1 ethjs http + infura 版本

第一个棘手的问题就是需要使用 infura, 原有的 web3 和 provider 并不支持很好, 于是找到了方便易用的 ethjs (eth-abi, eth-filter, eth…)

然后就需要实现块的可信化机制, 要在出块至少超过6个之后才能相信该内容是正确的.

由于觉得 ethjs 的使用人数还不少, 文档也比较丰富, 并且简单易用, 大部分功能都有相应 demo, 于是很快就使用它创建好了新的应用, 测试一段时间上线了.

主要实现方案就是 ethjs 可以监听 block 出块信息(实际底层还是轮训), 当出块的时候获取对应 blockNumber - 6 那个区块的 log, 这样就完美的实现了可信化机制, 获取到 log 后存储到数据库, 顺序的发送给消费者.

此版本出现的问题:

一切未知的东西都是存在变数的. ethjs 在简单易上手方面确实非常有优势, ethjs 在合约和交易方面也相对稳定, 使用人数不少, 但是 ethjs-filter 出现了一个问题, 并且这个问题是在上线后很久才小概率出现一次.

文档里面说通过 ‘fromBlock’, ‘toBlock’ 可以取到对应区块中的 log, 那么我们的实现方案就是 {"fromBlock": blockNumber - 6, "toBlock": blockNumber - 6}, 用来取当前区块前 6 块的数据, 看似很完美, 但是上线之后很久才发现, 其实该两个参数其实并不起作用.

相当于说, 每次努力的传入了两个参数, 但是取的还是最新区块的日志内容, 实际上和原来版本一样, 没有实现可信的目的.

此外还带来了新的问题: 由于获取的最新的区块数据, 那么在网络较慢, 但是区块出块速度很高的时候, 就会出现老块丢失并且新块取2次数据的问题.

version 2.0.2 hot fix 版本

上线之后, 发现一个问题, 就是拿到的日志数据, 当类型为数字的时候, 均是以 BN 对象形式进行展示的, 于是将其循环解析, 使用 BN.isBN() 判断是否是 BN, 使用 new BigNumber().toNumber() 将其解析为可使用的数字类型.

version 2.0.3 ethjs web3 ws + infura 版本

使用 web3 解决问题是区块数据只能拿到最新块的问题, 应用的是 subscribe 订阅的 log 日志, 可以实时监听到日志的变化, 于是将区块写入到数据库后, 仍然根据 ethjs 监听到的区块变化, 进行区块的消费, 实现了可信化机制.

在测试之后, 还有些地方其实并不清楚, 只是暂时写了 TODO 等待以后处理, 上线几天后, 发现监听到的 log 会出现重复情况, 造成某些内容多次处理. 经过排查, 监听日志的部分, 有两块内容, 一块是 on('data', ... 一块是 on('changed', ..., 那么重复日志出现的情况是, 同一个 txHash 的内容会在 on('data', ... 出现两次, 而且在 on('changed', ... 出现一次, 经过文档反复查验, web3js -> solidity -> ethereum, 终于发现, on data 的数据, 是在交易处在 pending 状态下就触发出来的, 当区块出现分叉的时候, gas 较低没有在当前块挖矿… 一系列情况的时候, 会出现通过 on changed 移除原有 pending 状态下发来的数据, 然后重新发送一条 on data 数据.

查明原因之后, 立刻明白应该怎样处理, on changed 的数据要去数据库找到对应记录删除原有数据, on data 数据直接记录即可.

本版本的问题:

跑了一段时间, 发现此方法仍有问题, 存在一定几率出现: 当 on data 数据很多的时候, on changed 又与 on data 数据间隔时间差较短, 或者 mongodb 数据库多线程写入延时问题等等, 会有几率出现 on changed 数据先去数据库删除对应前面的记录, 但是前一条 on data 数据还没有成功写入, 仍会造成数据重复.

version 2.0.4 hot fix 版本

由于使用了长连接, 增加了断线重连接机制, 增加了崩溃邮件报警机制.

但是此次修复并没有达到预期的断线重连效果, 因为即使重新连接, 仍然没办法再让原有 web3 使用新的连接获取数据. 此处猜测在断线重连之后, web3 要进行重新监听.

  1. 调用第三方服务存在失败的可能, 没有安排队列或者阻塞机制, 造成日志没有完全串行化被消费.

  2. 及时获取的日志, 没有进行多块后延时处理, 可能会出现由于分叉影响造成可信程度较低, 抑或链上内容与实际执行内容不一致.

Donate - Support to make this site better.
捐助 - 支持我让我做得更好.