以太坊源码解读之数据库 ethdb

以太坊数据库 ethdb

源码位置:ethereum/go-ethereum/ethdb

.
├── database.go // leveldb封装
├── database_js.go // 暂不支持
├── database_js_test.go
├── database_test.go // 测试用例
├── interface.go // 数据库接口定义
├── memory_database.go // 内存数据库,用于测试
├── table.go // 表操作封装
└── table_batch.go // 表操作封装

以太坊的数据存储使用在eveldb,nosql key-value数据库:

leveldb特点

  • key和value都是任意长度的字节数组;
  • entry(即一条K-V记录)默认是按照key的字典顺序存储的,当然开发者也可以重载这个排序函数;
  • 提供的基本操作接口:Put()、Delete()、Get()、Batch();
  • 支持批量操作以原子操作进行;
  • 可以创建数据全景的snapshot(快照),并允许在快照中查找数据;
  • 可以通过前向(或后向)迭代器遍历数据(迭代器会隐含的创建一个snapshot);
  • 自动使用Snappy压缩数据;
  • 可移植性;

memory_database.go

以太坊测试使用内存数据库,源码实现memory_database.go中。封装了一个map,然后使用锁来进行安全访问。

type MemDatabase struct {
   db   map[string][]byte
    // 使用lock来进行安全访问
   lock sync.RWMutex
}
// 新建一个内存数据库
func NewMemDatabaseWithCap(size int) *MemDatabase {
	return &MemDatabase{
		db: make(map[string][]byte, size),
	}
}
// 使用锁来控制安全访问,其他操作同理
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) {
	...
}

func (db *MemDatabase) Get(key []byte) ([]byte, error) {
	...
}

func (db *MemDatabase) Len() int { return len(db.db) }

bath批量操作同理

type memBatch struct {
   db     *MemDatabase
   writes []kv
   size   int
}

interface.go

定义了数据库的操作 增删改查 关闭等

package ethdb

// 批处理最大数据.
const IdealBatchSize = 100 * 1024

//批处理和常规数据库都支持的数据库写操作
type Putter interface {
   Put(key []byte, value []byte) error
}

//批处理和常规数据库都支持的数据库删除操作.
type Deleter interface {
   Delete(key []byte) error
}
// Database wraps all database operations. All methods are safe for concurrent use.
// 数据库接口,增删改查、关闭、批处理。线程安全的
type Database interface {
   Putter
   Deleter
   Get(key []byte) ([]byte, error)
   Has(key []byte) (bool, 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.
// 不能多线程使用
type Batch interface {
   Putter
   Deleter
   ValueSize() int // amount of data in the batch
   Write() error
   // Reset resets the batch for reuse
   Reset()
}

database.go

封装levelDB,leveldb源码在https://github.com/syndtr/goleveldb。以太坊使用meter系统来进行数据库的性能统计

type LDBDatabase struct {
   fn string      // filename for reporting
   db *leveldb.DB // LevelDB instance
	// metter用户测试计算数据
   compTimeMeter    metrics.Meter // Meter for measuring the total time spent in database 		//安全退出
   quitLock sync.Mutex      // Mutex protecting the quit channel access
    // 在关闭数据库之前停止meter
   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.
// 返回一个db实例 file db文件
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", common.StorageSize(cache*1024*1024), "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
    // 如果数据库打开失败,返回nil
   if err != nil {
      return nil, err
   }
   return &LDBDatabase{
      fn:  file,
      db:  db,
      log: logger,
   }, nil
}

leveldb内部支持多线程访问,所以这里可以直接调用,并不需要lock控制。

// 增
func (db *LDBDatabase) Put(key []byte, value []byte) error {
   return db.db.Put(key, value, nil)
}
// 查找数据是否存在
func (db *LDBDatabase) Has(key []byte) (bool, error) {
   return db.db.Has(key, nil)
}

//查
func (db *LDBDatabase) Get(key []byte) ([]byte, error) {
   dat, err := db.db.Get(key, nil)
   if err != nil {
      return nil, err
   }
   return dat, nil
}

// 删除
func (db *LDBDatabase) Delete(key []byte) error {
   return db.db.Delete(key, nil)
}
// 迭代
func (db *LDBDatabase) NewIterator() iterator.Iterator {
   return db.db.NewIterator(nil, nil)
}

根据传入的前缀 初始化收集器,创建quitchain

// Meter configures the database metrics collectors and
func (db *LDBDatabase) Meter(prefix string) {
	// Initialize all the metrics collector at the requested prefix
	db.compTimeMeter = metrics.NewRegisteredMeter(prefix+"compact/time", nil)
	db.compReadMeter = metrics.NewRegisteredMeter(prefix+"compact/input", nil)
	db.compWriteMeter = metrics.NewRegisteredMeter(prefix+"compact/output", nil)
	db.diskReadMeter = metrics.NewRegisteredMeter(prefix+"disk/read", nil)
	db.diskWriteMeter = metrics.NewRegisteredMeter(prefix+"disk/write", nil)
	db.writeDelayMeter = metrics.NewRegisteredMeter(prefix+"compact/writedelay/duration", nil)
	db.writeDelayNMeter = metrics.NewRegisteredMeter(prefix+"compact/writedelay/counter", nil)

	// Create a quit channel for the periodic collector and run it
	db.quitLock.Lock()
	db.quitChan = make(chan chan error)
	db.quitLock.Unlock()

    // 刷新时间3秒
	go db.meter(3 * time.Second)
}
// 定期统计信息公布到metrics子系统
// 表统计样例
// This is how a stats table look like (currently):
//   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
//
// This is how the write delay look like (currently):
// 写入统计
// DelayN:5 Delay:406.604657ms Paused: false
//
// This is how the iostats look like (currently):
// io统计
// Read(MB):3895.04860 Write(MB):3654.64712

你可能感兴趣的:(区块链)