江沐涵 2019-06-28
作者:Derek
Github地址:https://github.com/Bytom/bytom
Gitee地址:https://gitee.com/BytomBlockc...
本章介绍Derek解读-Bytom源码分析-持久化存储LevelDB
作者使用MacOS操作系统,其他平台也大同小异Golang Version: 1.8
比原链默认使用leveldb数据库。Leveldb是一个google实现的非常高效的kv数据库。LevelDB是单进程的服务,性能非常之高,在一台4核Q6600的CPU机器上,每秒钟写数据超过40w,而随机读的性能每秒钟超过10w。
由于Leveldb是单进程服务,不能同时有多个进程进行对一个数据库进行读写。同一时间只能有一个进程,或一个进程多并发的方式进行读写。
比原链在数据存储层上存储所有链上地址、资产交易等信息。
LevelDB是google开发的一个高性能K/V存储,本节我们介绍下LevelDB如何对LevelDB增删改查。
package main
import (
    "fmt"
    dbm "github.com/tendermint/tmlibs/db"
)
var (
    Key        = "TESTKEY"
    LevelDBDir = "/tmp/data"
)
func main() {
    db := dbm.NewDB("test", "leveldb", LevelDBDir)
    defer db.Close()
    db.Set([]byte(Key), []byte("This is a test."))
    value := db.Get([]byte(Key))
    if value == nil {
        return
    }
    fmt.Printf("key:%v, value:%v\n", Key, string(value))
    db.Delete([]byte(Key))
}
// Output
// key:TESTKEY, value:This is a test.以上Output是执行该程序得到的输出结果。
该程序对leveld进行了增删改查操作。dbm.NewDB得到db对象,在/tmp/data目录下会生成一个叫test.db的目录。该目录存放该数据库的所有数据。
db.Set        设置key的value值,key不存在则新建,key存在则修改。
db.Get        得到key中value数据。
db.Delete    删除key及value的数据。
默认情况下,数据存储目录在--home参数下的data目录。以Darwin平台为例,默认数据库存储在 $HOME/Library/Bytom/data。
以上所有数据库都由database模块管理
在比原链中数据持久化存储由database模块管理,但是持久化相关接口在protocol/store.go中
type Store interface {
    BlockExist(*bc.Hash) bool
    GetBlock(*bc.Hash) (*types.Block, error)
    GetStoreStatus() *BlockStoreState
    GetTransactionStatus(*bc.Hash) (*bc.TransactionStatus, error)
    GetTransactionsUtxo(*state.UtxoViewpoint, []*bc.Tx) error
    GetUtxo(*bc.Hash) (*storage.UtxoEntry, error)
    LoadBlockIndex() (*state.BlockIndex, error)
    SaveBlock(*types.Block, *bc.TransactionStatus) error
    SaveChainStatus(*state.BlockNode, *state.UtxoViewpoint) error
}database/leveldb/store.go
var (
    blockStoreKey     = []byte("blockStore")
    blockPrefix       = []byte("B:")
    blockHeaderPrefix = []byte("BH:")
    txStatusPrefix    = []byte("BTS:")
)database/leveldb/store.go
func (s *Store) GetBlock(hash *bc.Hash) (*types.Block, error) {
    return s.cache.lookup(hash)
}database/leveldb/cache.go
func (c *blockCache) lookup(hash *bc.Hash) (*types.Block, error) {
    if b, ok := c.get(hash); ok {
        return b, nil
    }
    block, err := c.single.Do(hash.String(), func() (interface{}, error) {
        b := c.fillFn(hash)
        if b == nil {
            return nil, fmt.Errorf("There are no block with given hash %s", hash.String())
        }
        c.add(b)
        return b, nil
    })
    if err != nil {
        return nil, err
    }
    return block.(*types.Block), nil
}GetBlock函数最终会执行lookup函数。lookup函数总共操作有两步:
fillFn回调函数实际上调取的是database/leveldb/store.go下的GetBlock,它会从磁盘中获取block信息并返回。