以太坊原始碼分析(36)ethdb原始碼分析
go-ethereum所有的資料儲存在levelDB這個Google開源的KeyValue檔案資料庫中,整個區塊鏈的所有資料都儲存在一個levelDB的資料庫中,levelDB支援按照檔案大小切分檔案的功能,所以我們看到的區塊鏈的資料都是一個一個小檔案,其實這些小檔案都是一個同一個levelDB例項。這裡簡單的看下levelDB的go封裝程式碼。
levelDB官方網站介紹的特點
**特點**:
- key和value都是任意長度的位元組陣列;
- entry(即一條K-V記錄)預設是按照key的字典順序儲存的,當然開發者也可以過載這個排序函式;
- 提供的基本操作介面:Put()、Delete()、Get()、Batch();
- 支援批量操作以原子操作進行;
- 可以建立資料全景的snapshot(快照),並允許在快照中查詢資料;
- 可以通過前向(或後向)迭代器遍歷資料(迭代器會隱含的建立一個snapshot);
- 自動使用Snappy壓縮資料;
- 可移植性;
**限制**:
- 非關係型資料模型(NoSQL),不支援sql語句,也不支援索引;
- 一次只允許一個程式訪問一個特定的資料庫;
- 沒有內建的C/S架構,但開發者可以使用LevelDB庫自己封裝一個server;
原始碼所在的目錄在ethereum/ethdb目錄。程式碼比較簡單, 分為下面三個檔案
- database.go levelDB的封裝程式碼
- memory_database.go 供測試用的基於記憶體的資料庫,不會持久化為檔案,僅供測試
- interface.go 定義了資料庫的介面
- database_test.go 測試案例
## interface.go
看下面的程式碼,基本上定義了KeyValue資料庫的基本操作, Put, Get, Has, Delete等基本操作,levelDB是不支援SQL的,基本可以理解為資料結構裡面的Map。
package ethdb
const IdealBatchSize = 100 * 1024
// Putter wraps the database write operation supported by both batches and regular databases.
//Putter介面定義了批量操作和普通操作的寫入介面
type Putter interface {
Put(key []byte, value []byte) error
}
// Database wraps all database operations. All methods are safe for concurrent use.
//資料庫介面定義了所有的資料庫操作, 所有的方法都是多執行緒安全的。
type Database interface {
Putter
Get(key []byte) ([]byte, error)
Has(key []byte) (bool, error)
Delete(key []byte) error
Close()
NewBatch() Batch
}
// Batch is a write-only database that commits changes to its host database
// when Write is called. Batch cannot be used concurrently.
//批量操作介面,不能多執行緒同時使用,當Write方法被呼叫的時候,資料庫會提交寫入的更改。
type Batch interface {
Putter
ValueSize() int // amount of data in the batch
Write() error
}
## memory_database.g
這個基本上就是封裝了一個記憶體的Map結構。然後使用了一把鎖來對多執行緒進行資源的保護。
type MemDatabase struct {
db map[string][]byte
lock sync.RWMutex
}
func NewMemDatabase() (*MemDatabase, error) {
return &MemDatabase{
db: make(map[string][]byte),
}, nil
}
func (db *MemDatabase) Put(key []byte, value []byte) error {
db.lock.Lock()
defer db.lock.Unlock()
db.db[string(key)] = common.CopyBytes(value)
return nil
}
func (db *MemDatabase) Has(key []byte) (bool, error) {
db.lock.RLock()
defer db.lock.RUnlock()
_, ok := db.db[string(key)]
return ok, nil
}
然後是Batch的操作。也比較簡單,一看便明白。
type kv struct{ k, v []byte }
type memBatch struct {
db *MemDatabase
writes []kv
size int
}
func (b *memBatch) Put(key, value []byte) error {
b.writes = append(b.writes, kv{common.CopyBytes(key), common.CopyBytes(value)})
b.size += len(value)
return nil
}
func (b *memBatch) Write() error {
b.db.lock.Lock()
defer b.db.lock.Unlock()
for _, kv := range b.writes {
b.db.db[string(kv.k)] = kv.v
}
return nil
}
##database.go
這個就是實際ethereum客戶端使用的程式碼, 封裝了levelDB的介面。
import (
"strconv"
"strings"
"sync"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/errors"
"github.com/syndtr/goleveldb/leveldb/filter"
"github.com/syndtr/goleveldb/leveldb/iterator"
"github.com/syndtr/goleveldb/leveldb/opt"
gometrics "github.com/rcrowley/go-metrics"
)
使用了github.com/syndtr/goleveldb/leveldb的leveldb的封裝,所以一些使用的文件可以在那裡找到。可以看到,資料結構主要增加了很多的Mertrics用來記錄資料庫的使用情況,增加了quitChan用來處理停止時候的一些情況,這個後面會分析。如果下面程式碼可能有疑問的地方應該再Filter: filter.NewBloomFilter(10)這個可以暫時不用關注,這個是levelDB裡面用來進行效能優化的一個選項,可以不用理會。
type LDBDatabase struct {
fn string // filename for reporting
db *leveldb.DB // LevelDB instance
getTimer gometrics.Timer // Timer for measuring the database get request counts and latencies
putTimer gometrics.Timer // Timer for measuring the database put request counts and latencies
...metrics
quitLock sync.Mutex // Mutex protecting the quit channel access
quitChan chan chan error // Quit channel to stop the metrics collection before closing the database
log log.Logger // Contextual logger tracking the database path
}
// NewLDBDatabase returns a LevelDB wrapped object.
func NewLDBDatabase(file string, cache int, handles int) (*LDBDatabase, error) {
logger := log.New("database", file)
// Ensure we have some minimal caching and file guarantees
if cache < 16 {
cache = 16
}
if handles < 16 {
handles = 16
}
logger.Info("Allocated cache and file handles", "cache", cache, "handles", handles)
// Open the db and recover any potential corruptions
db, err := leveldb.OpenFile(file, &opt.Options{
OpenFilesCacheCapacity: handles,
BlockCacheCapacity: cache / 2 * opt.MiB,
WriteBuffer: cache / 4 * opt.MiB, // Two of these are used internally
Filter: filter.NewBloomFilter(10),
})
if _, corrupted := err.(*errors.ErrCorrupted); corrupted {
db, err = leveldb.RecoverFile(file, nil)
}
// (Re)check for errors and abort if opening of the db failed
if err != nil {
return nil, err
}
return &LDBDatabase{
fn: file,
db: db,
log: logger,
}, nil
}
再看看下面的Put和Has的程式碼,因為github.com/syndtr/goleveldb/leveldb封裝之後的程式碼是支援多執行緒同時訪問的,所以下面這些程式碼是不用使用鎖來保護的,這個可以注意一下。這裡面大部分的程式碼都是直接呼叫leveldb的封裝,所以不詳細介紹了。 有一個比較有意思的地方是Metrics程式碼。
// Put puts the given key / value to the queue
func (db *LDBDatabase) Put(key []byte, value []byte) error {
// Measure the database put latency, if requested
if db.putTimer != nil {
defer db.putTimer.UpdateSince(time.Now())
}
// Generate the data to write to disk, update the meter and write
//value = rle.Compress(value)
if db.writeMeter != nil {
db.writeMeter.Mark(int64(len(value)))
}
return db.db.Put(key, value, nil)
}
func (db *LDBDatabase) Has(key []byte) (bool, error) {
return db.db.Has(key, nil)
}
###Metrics的處理
之前在建立NewLDBDatabase的時候,並沒有初始化內部的很多Mertrics,這個時候Mertrics是為nil的。初始化Mertrics是在Meter方法中。外部傳入了一個prefix引數,然後建立了各種Mertrics(具體如何建立Merter,會後續在Meter專題進行分析),然後建立了quitChan。 最後啟動了一個執行緒呼叫了db.meter方法。
// Meter configures the database metrics collectors and
func (db *LDBDatabase) Meter(prefix string) {
// Short circuit metering if the metrics system is disabled
if !metrics.Enabled {
return
}
// Initialize all the metrics collector at the requested prefix
db.getTimer = metrics.NewTimer(prefix + "user/gets")
db.putTimer = metrics.NewTimer(prefix + "user/puts")
db.delTimer = metrics.NewTimer(prefix + "user/dels")
db.missMeter = metrics.NewMeter(prefix + "user/misses")
db.readMeter = metrics.NewMeter(prefix + "user/reads")
db.writeMeter = metrics.NewMeter(prefix + "user/writes")
db.compTimeMeter = metrics.NewMeter(prefix + "compact/time")
db.compReadMeter = metrics.NewMeter(prefix + "compact/input")
db.compWriteMeter = metrics.NewMeter(prefix + "compact/output")
// Create a quit channel for the periodic collector and run it
db.quitLock.Lock()
db.quitChan = make(chan chan error)
db.quitLock.Unlock()
go db.meter(3 * time.Second)
}
這個方法每3秒鐘獲取一次leveldb內部的計數器,然後把他們公佈到metrics子系統。 這是一個無限迴圈的方法, 直到quitChan收到了一個退出訊號。
// meter periodically retrieves internal leveldb counters and reports them to
// the metrics subsystem.
// This is how a stats table look like (currently):
//下面的註釋就是我們呼叫 db.db.GetProperty("leveldb.stats")返回的字串,後續的程式碼需要解析這個字串並把資訊寫入到Meter中。
// Compactions
// Level | Tables | Size(MB) | Time(sec) | Read(MB) | Write(MB)
// -------+------------+---------------+---------------+---------------+---------------
// 0 | 0 | 0.00000 | 1.27969 | 0.00000 | 12.31098
// 1 | 85 | 109.27913 | 28.09293 | 213.92493 | 214.26294
// 2 | 523 | 1000.37159 | 7.26059 | 66.86342 | 66.77884
// 3 | 570 | 1113.18458 | 0.00000 | 0.00000 | 0.00000
func (db *LDBDatabase) meter(refresh time.Duration) {
// Create the counters to store current and previous values
counters := make([][]float64, 2)
for i := 0; i < 2; i++ {
counters[i] = make([]float64, 3)
}
// Iterate ad infinitum and collect the stats
for i := 1; ; i++ {
// Retrieve the database stats
stats, err := db.db.GetProperty("leveldb.stats")
if err != nil {
db.log.Error("Failed to read database stats", "err", err)
return
}
// Find the compaction table, skip the header
lines := strings.Split(stats, "\n")
for len(lines) > 0 && strings.TrimSpace(lines[0]) != "Compactions" {
lines = lines[1:]
}
if len(lines) <= 3 {
db.log.Error("Compaction table not found")
return
}
lines = lines[3:]
// Iterate over all the table rows, and accumulate the entries
for j := 0; j < len(counters[i%2]); j++ {
counters[i%2][j] = 0
}
for _, line := range lines {
parts := strings.Split(line, "|")
if len(parts) != 6 {
break
}
for idx, counter := range parts[3:] {
value, err := strconv.ParseFloat(strings.TrimSpace(counter), 64)
if err != nil {
db.log.Error("Compaction entry parsing failed", "err", err)
return
}
counters[i%2][idx] += value
}
}
// Update all the requested meters
if db.compTimeMeter != nil {
db.compTimeMeter.Mark(int64((counters[i%2][0] - counters[(i-1)%2][0]) * 1000 * 1000 * 1000))
}
if db.compReadMeter != nil {
db.compReadMeter.Mark(int64((counters[i%2][1] - counters[(i-1)%2][1]) * 1024 * 1024))
}
if db.compWriteMeter != nil {
db.compWriteMeter.Mark(int64((counters[i%2][2] - counters[(i-1)%2][2]) * 1024 * 1024))
}
// Sleep a bit, then repeat the stats collection
select {
case errc := <-db.quitChan:
// Quit requesting, stop hammering the database
errc <- nil
return
case <-time.After(refresh):
// Timeout, gather a new set of stats
}
}
}
網址:http://www.qukuailianxueyuan.io/
欲領取造幣技術與全套虛擬機器資料
區塊鏈技術交流QQ群:756146052 備註:CSDN
尹成學院微信:備註:CSDN
相關文章
- 以太坊原始碼分析(38)event原始碼分析原始碼
- 以太坊原始碼分析(41)hashimoto原始碼分析原始碼
- 以太坊原始碼分析(43)node原始碼分析原始碼
- 以太坊原始碼分析(51)rpc原始碼分析原始碼RPC
- 以太坊原始碼分析(52)trie原始碼分析原始碼
- 以太坊原始碼分析(13)RPC分析原始碼RPC
- 以太坊原始碼分析(35)eth-fetcher原始碼分析原始碼
- 以太坊原始碼分析(20)core-bloombits原始碼分析原始碼OOM
- 以太坊原始碼分析(24)core-state原始碼分析原始碼
- 以太坊原始碼分析(29)core-vm原始碼分析原始碼
- 以太坊原始碼分析(34)eth-downloader原始碼分析原始碼
- 以太坊原始碼分析(37)eth以太坊協議分析原始碼協議
- 以太坊原始碼分析(18)以太坊交易執行分析原始碼
- 以太坊原始碼分析(5)accounts程式碼分析原始碼
- 以太坊交易池原始碼分析原始碼
- 以太坊原始碼分析(23)core-state-process原始碼分析原始碼
- 以太坊原始碼分析(31)eth-downloader-peer原始碼分析原始碼
- 以太坊原始碼分析(32)eth-downloader-peer原始碼分析原始碼
- 以太坊原始碼分析(33)eth-downloader-statesync原始碼分析原始碼
- 以太坊原始碼分析(8)區塊分析原始碼
- 以太坊原始碼分析(9)cmd包分析原始碼
- 以太坊原始碼分析(16)挖礦分析原始碼
- 以太坊原始碼分析(26)core-txpool交易池原始碼分析原始碼
- 以太坊原始碼分析(27)core-vm-jumptable-instruction原始碼分析原始碼Struct
- 以太坊原始碼分析(28)core-vm-stack-memory原始碼分析原始碼
- 以太坊原始碼分析(30)eth-bloombits和filter原始碼分析原始碼OOMFilter
- 以太坊原始碼分析(10)CMD深入分析原始碼
- 以太坊原始碼分析(12)交易資料分析原始碼
- 以太坊原始碼分析(19)core-blockchain分析原始碼Blockchain
- 以太坊原始碼分析(44)p2p-database.go原始碼分析原始碼DatabaseGo
- 以太坊原始碼分析(45)p2p-dial.go原始碼分析原始碼Go
- 以太坊原始碼分析(46)p2p-peer.go原始碼分析原始碼Go
- 以太坊原始碼分析(48)p2p-server.go原始碼分析原始碼ServerGo
- 以太坊原始碼分析(49)p2p-table.go原始碼分析原始碼Go
- 以太坊原始碼分析(50)p2p-udp.go原始碼分析原始碼UDPGo
- 以太坊原始碼分析(52)以太坊fast sync演算法原始碼AST演算法
- 以太坊原始碼分析(39)geth啟動流程分析原始碼
- 以太坊原始碼分析(6)accounts賬戶管理分析原始碼