时间 :入职第 11 天,上午 10:30 天气 :晴,适合看报表 事件:产品经理 (PM) 的灵魂拷问
刚过完试用期,我心情不错。 产品总监 (Head of Product) 拿着一杯咖啡走过来:"Alen,听说节点很稳。那你能不能帮我拉一个数据:过去 7 天,Bybit 用户充值最多的 Top 10 币种是什么?以及这些用户的平均持仓时间是多少?"
我差点把刚喝的水喷出来。 "老大,Geth 节点不是 MySQL。它没有 SELECT * FROM transactions WHERE... 这种命令。你要查这个,我得写脚本把过去 7 天的 50,000 个区块全部遍历一遍,大概要跑 3 个小时。"
产品总监皱眉:"别的交易所都能实时看,我们为什么不行?数据的价值在于实时。 我不管你用什么办法,我要一个 Dashboard。"
任务 :搭建一套 链上数据索引系统 (Indexer) ,把区块链数据实时清洗进 SQL 数据库。
🧩 1. 上午 11:30:架构选型 ------ 为什么 Geth 不行?
我召集了数据组的同事,画了一个架构图。
痛点分析:
-
Geth (LevelDB):是 KV 存储。
-
查
Get(Key)极快。 -
查
Count(*)不可能。 -
数据全是
0x...的十六进制,人类不可读。
-
-
目标 :我们需要一个 OLAP (在线分析处理) 数据库。
技术栈选型:
-
ETL (抽取层) :用 Python (Web3.py) 写一个"爬虫",实时从 Geth 节点
eth_getBlock。 -
Queue (缓冲层) :用 AWS MSK (Managed Kafka)。区块链有时候会爆发(比如 NFT 抢购),直接写数据库会崩,需要 Kafka 削峰。
-
DB (存储层) :ClickHouse。
-
为什么不用 MySQL?因为以太坊历史交易有十几亿笔,MySQL 查不动。
-
为什么用 ClickHouse?它是列式存储,聚合查询(Sum, Count, Avg)快得令人发指,单机每秒处理几亿行。
-
🛠️ 2. 下午 02:00:编写 ETL 脚本 ------ 与"大整数"的斗争
我开始写 Python 脚本。 逻辑很简单:Loop Block -> Get Txs -> Decode -> Push Kafka。
但很快我遇到了 Web3 数据分析的第一个巨坑 :精度问题。
代码片段:
# etl_worker.py
from web3 import Web3
w3 = Web3(Web3.HTTPProvider("http://localhost:8545"))
def process_block(block_num):
block = w3.eth.get_block(block_num, full_transactions=True)
for tx in block.transactions:
# 坑点:tx.value 是一个 uint256 (比如 100 ETH = 100000000000000000000)
# 普通的数据库 Int64 根本存不下!
# 如果转成 Float,精度会丢失,财务会杀了我。
record = {
"hash": tx.hash.hex(),
"from": tx['from'],
"to": tx['to'],
# 解决方案:存成 String 或者 Decimal(38, 0)
"value_raw": str(tx.value),
"gas_price": tx.gasPrice,
"block_number": block_num,
"timestamp": block.timestamp
}
kafka_producer.send("ethereum_txs", record)
Alen 的经验 : 在金融数据仓库里,永远不要用 Float 存钱。 要么存 String,要么用数据库的高精度 Decimal 类型。
🚀 3. 下午 04:00:ClickHouse 的魔法
数据通过 Kafka 流入了 ClickHouse。 我在 ClickHouse 里建了一张表:
CREATE TABLE ethereum.transactions (
hash String,
from String,
to String,
value_raw Decimal(38, 0), -- 完美支持 uint256
block_number UInt64,
timestamp DateTime
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(timestamp) -- 按月分区
ORDER BY (block_number, from); -- 排序索引
测试查询: 产品总监要的那个需求(过去 7 天 Top 10 充值):
SELECT
to,
sum(value_raw) / 1e18 AS total_eth
FROM ethereum.transactions
WHERE
to IN (SELECT address FROM exchange_wallets) -- 我们的充值钱包列表
AND timestamp > now() - INTERVAL 7 DAY
GROUP BY to
ORDER BY total_eth DESC
LIMIT 10;
结果 : ClickHouse 在 0.05 秒 内返回了结果。 而在 Geth 上,这个查询即使写脚本跑,也需要 3 个小时。
🔄 4. 下午 05:30:处理 Reorg (回滚) 的麻烦
就在我得意洋洋的时候,Day 9 的那个幽灵又回来了------Reorg。
如果我的 Python 脚本刚把 Block 100 写入 ClickHouse,结果链上发生了回滚,Block 100 变了。 那我的数据库里就有了脏数据。
解决方案:延迟入库 + 版本控制
-
实时流 (Hot):直接展示,但在前端标记为"未确认 (Unconfirmed)"。
-
确认流 (Cold):只有当区块经过 12 个确认(Safe Block)后,才写入最终的分析表。
-
ClickHouse 的
ReplacingMergeTree: 利用 ClickHouse 的去重引擎。如果有同一个 Block Number 的新数据进来,自动覆盖旧数据。
SQL
-- 引擎改为支持去重的
ENGINE = ReplacingMergeTree(block_timestamp)
ORDER BY (block_number, hash)
📊 5. 下午 06:30:交付 Superset 看板
最后,我部署了一个开源的 BI 工具 Apache Superset,连上 ClickHouse。 拖拖拽拽,画出了几个图表:
-
ETH 每日充值趋势图
-
Gas 费消耗热力图
-
大户监控列表 (Whale Alert)
我把链接发给产品总监。 他点开后,看着实时跳动的数字,沉默了三秒,然后说:"Alen,这比 Etherscan 也就是强了大概 100 倍吧。这周给你加鸡腿。"
1. 什么是 ClickHouse?是 AWS 上面的服务吗?如果不是,为什么要自建?
Alen 的回答:
-
什么是 ClickHouse?
-
它是一个开源的 列式数据库 (Columnar DBMS) ,专门用于 OLAP (在线分析处理)。
-
特点 :快。非常快。对于海量数据(比如几十亿行交易记录)的聚合查询(求和、计数、平均值),它比传统的 MySQL/PostgreSQL 快 100-1000 倍。
-
Web3 标配:在区块链行业,几乎所有的顶尖数据分析平台(如 Dune Analytics, Nansen, Etherscan)底层都在用 ClickHouse 或类似技术。
-
-
是 AWS 的服务吗?
-
不是。 AWS 没有"Amazon ClickHouse"这个原生服务(不像 Amazon RDS 或 Amazon Redshift)。
-
虽然市面上有 ClickHouse Cloud 这种托管服务,但在 AWS 上使用通常意味着:在 EC2 上自建,或者使用第三方托管在 AWS 上的版本。
-
-
为什么要自建?(而不是用 AWS Redshift 或 Athena)
-
成本 (Cost):
-
区块链数据是"写多读少"的日志型数据。AWS Redshift 非常贵,而且按节点收费。
-
ClickHouse 对数据压缩率极高(通常 10:1)。同样的 1TB 数据,存 ClickHouse 可能只需要 100GB 硬盘。在 EC2 上自建 ClickHouse,成本只有 Redshift 的 1/5。
-
-
响应速度 (Latency):
-
AWS Athena (基于 Presto) 是按查询扫描量收费的,而且查询通常有几秒延迟。
-
ClickHouse 是毫秒级响应。产品总监要看实时 Dashboard,他不想点个按钮等 10 秒,他要 0.1 秒出图。
-
-
大宽表支持:
- 区块链交易数据字段很多,ClickHouse 极其擅长处理这种"大宽表"。
-
2. 精度问题是什么问题?
Alen 的回答:
这是区块链开发和传统开发最大的鸿沟------数字太大了。
-
传统世界的数字:
-
数据库里的
Int64(长整型) 最大能存多少?大约 900 亿亿 (9 \\times 10\^{18})。 -
浮点数
Float/Double虽然能存大数,但是会丢精度 (比如存100.0000001变成了100.0)。
-
-
区块链世界的数字:
-
以太坊的最小单位是 Wei。1 ETH = 10\^{18} Wei。
-
Solidity 智能合约里常用的数字类型是
uint256。 -
uint256的最大值是 1.15 \\times 10\^{77}。 -
冲突点 :如果不经处理,直接把一个
uint256的大数塞进 MySQL 的Int64字段,数据库会直接报错 "Numeric Overflow" (数值溢出) 。或者如果塞进Float,财务就会找你拼命,因为钱算不准了。
-
-
Alen 的解决方案:
-
方法 A (字符串) :存成
String类型 ("1000000...")。缺点是不能直接做加减乘除运算。 -
方法 B (高精度小数) :ClickHouse 支持
Decimal256或Decimal(38, 0)。这是一种专门用来存超大整数且不丢精度的格式。这是金融系统的唯一正解。
-
3. 实时流和确认流是什么?是要两种不同的导入到 ClickHouse 吗?
Alen 的回答:
这是为了解决 Day 9 提到的 "回滚 (Reorg)" 问题而设计的数据管道策略。
它们不一定是两条物理网线,而是两条逻辑管道,通常会在代码里处理。
场景:处理"不确定"的未来
-
实时流 (Hot Stream):
-
来源 :Geth 的
Latest区块。 -
动作:爬虫一抓到新块(比如高度 100),立刻塞进 ClickHouse。
-
用途:给用户看"待确认"状态,或者给大屏看实时趋势。
-
风险:这个块可能会被回滚(消失)。
-
-
确认流 (Cold Stream):
-
来源 :Geth 的
Finalized区块(或延迟 12 个块)。 -
动作:爬虫再次检查高度 100。确认它没有被回滚,标记为"已确认"。
-
用途:给财务做账,给风控做审计。
-
怎么导入 ClickHouse?(Alen 的骚操作)
通常不需要导两次(浪费空间)。Alen 使用了 ClickHouse 强大的 ReplacingMergeTree 引擎。
-
10:00:00:实时流写入一行数据:
{Block: 100, Tx: A, Status: "Pending", Version: 1} -
10:15:00(区块已确认):确认流写入一行数据:
{Block: 100, Tx: A, Status: "Confirmed", Version: 2} -
数据库自动合并:
ReplacingMergeTree引擎会自动发现:"咦?Block 100 来了个 Version 2。"它会自动用 Version 2 覆盖 Version 1。