go-ethereum 中的所有区块数据都存储在 leveldb 中,并且 go-ethereum 又基于 leveldb 进行了一层简单的封装。
leveldb 是一个由 Google 开源(BSD)的 KV(Key/Value Pair)非关系型数据库,是基于 LSM(Log-Structured-Merge tree) 的典型实现。
主要有如下几个特性:
- Keys 和 Values 均为任意长度的字节数组;
- Data(KV) 默认以 Key 字典序排序存储,也可以提供自定义的排序算法来重载排序;
- 基本操作包括:Put(k,v),Get(k),Delete(k);
- 支持原子级的批量(Batch)操作;
- 可以创建数据全景(transient)的 snapshot,并支持在 snapshot 中查找数据;
- 支持前向和后向迭代遍历数据;
- 数据自动采用 Snappy 压缩算法进行压缩;
- 可移植性;
源码目录如下,主要就下面 4 个源码文件:
1 | ➜ ethdb : |
我们主要分析下 database.go 和 interface.go 两个源码文件。
database.go
这里主要封装了 leveldb 的接口,从如下 import 可以看出主要使用了 goleveldb 的封装。
1 | import ( |
下面看下在 goleveldb 基础上进一步封装的 LDBDatabase 结构,如下,主要增加了很多维度的 Metrics 用于统计使用情况(几个 metrics 的注释很清晰):
1 | type LDBDatabase struct { |
通过 NewLDBDatabase 方法来打开(或创建)并返回封装后的 LDBDatabase 实例,如下:
1 | // NewLDBDatabase returns a LevelDB wrapped object. |
此外,提供了一些基本操作方法,如 Put,Path,Get,Delete 等等用于操作 leveldb,这些基本操作都是直接调用 leveldb 的封装,如下为 Put 操作:
1 | // Put puts the given key / value to the queue |
创建本地 blockchain 数据库
下面看何时以及如何创建本地区块链DB:
1 | // source: eth/backend.go |
可以看到在创建和初始化 Ethereum 对象的时候会同时通过调用 CreateDB 方法来创建本地区块链数据库,并将 ethdb.LDBDatabase 实例 chainDb 作为 eth 实例的成员,在后续区块数据写入本地库时,都是通过该 chainDb 实例来操作写入的,如下代码:
1 | // source: eth/backend.go |
这里打开 “chaindata” 数据库,并且启动 Metric(见下面),在 ctx.OpenDatabase 中创建 LDBDatabase,如下:
1 | // source: node/node.go |
将 block 写入到 leveldb
在 core/database_util.go 中,封装了 WriteHeader, WriteBody, WriteBlock 等方法用于将区块 header, body, block 写入 leveldb 中。
1 | // source: core/database_util.go |
其中都通过 db.Put 方法将区块信息写入 leveldb 中。
Metrics
在 Meter 方法中创建了各种 Metrics 采集器,然后创建了 quitChan,最后启动一个协程调用了 db.meter 方法每隔 3 秒收集一次。
1 | // Meter configures the database metrics collectors and |
metrics.Enabled 开关默认为关闭(false),通过命令行选项参数 metrics 来设置。
1 | // go-ethereum/metrics.go |
在启动 metrics 开关后,每隔 3 秒会从 leveldb 内部获取计数器,然后公布到 metrics 子系统,这里是一个无限循环,直到 quitChan 收到一个退出信号。
1 | // meter periodically retrieves internal leveldb counters and reports them to |