Derek解讀Bytom源碼-持久化存儲LevelDB

做者:Derekgit

簡介

Github地址:https://github.com/Bytom/bytomgithub

Gitee地址:https://gitee.com/BytomBlockc...數據庫

本章介紹Derek解讀-Bytom源碼分析-持久化存儲LevelDB緩存

做者使用MacOS操做系統,其餘平臺也大同小異

Golang Version: 1.8網絡

LevelDB介紹

比原鏈默認使用leveldb數據庫。Leveldb是一個google實現的很是高效的kv數據庫。LevelDB是單進程的服務,性能很是之高,在一臺4核Q6600的CPU機器上,每秒鐘寫數據超過40w,而隨機讀的性能每秒鐘超過10w。併發

因爲Leveldb是單進程服務,不能同時有多個進程進行對一個數據庫進行讀寫。同一時間只能有一個進程,或一個進程多併發的方式進行讀寫。

比原鏈在數據存儲層上存儲全部鏈上地址、資產交易等信息。分佈式

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。

  • accesstoken.db token信息(錢包訪問控制權限)
    core.db 核心數據庫,存儲主鏈相關數據。包括塊信息、交易信息、資產信息等
    discover.db 分佈式網絡中端到端的節點信息
  • trusthistory.db
    txdb.db 存儲交易相關信息
    txfeeds.db 目前比原鏈代碼版本未使用該功能,暫不介紹
    wallet.db 本地錢包數據庫。存儲用戶、資產、交易、utox等信息

以上全部數據庫都由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
}
  • BlockExist 根據hash判斷區塊是否存在
  • GetBlock 根據hash獲取該區塊
  • GetStoreStatus 獲取store的存儲狀態
  • GetTransactionStatus 根據hash獲取該塊中全部交易的狀態
  • GetTransactionsUtxo 緩存與輸入txs相關的全部utxo
  • GetUtxo(*bc.Hash) 根據hash獲取該塊內的全部utxo
  • LoadBlockIndex 加載塊索引,從db中讀取全部block header信息並緩存在內存中
  • SaveBlock 存儲塊和交易狀態
  • SaveChainStatus 設置主鏈的狀態,當節點第一次啓動時,節點會根據key爲blockStore的內容判斷是否初始化主鏈。

比原鏈數據庫key前綴

database/leveldb/store.go

var (
    blockStoreKey     = []byte("blockStore")
    blockPrefix       = []byte("B:")
    blockHeaderPrefix = []byte("BH:")
    txStatusPrefix    = []byte("BTS:")
)
  • blockStoreKey 主鏈狀態前綴
  • blockPrefix 塊信息前綴
  • blockHeaderPrefix 塊頭信息前綴
  • txStatusPrefix 交易狀態前綴

GetBlock查詢塊過程分析

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函數總共操做有兩步:

  • 從緩存中查詢hash值,若是查到則返回
  • 若是爲從緩存中查詢到則回調fillFn回調函數。fillFn回調函數會將從磁盤上得到到塊信息存儲到緩存中並返回該塊的信息。

fillFn回調函數實際上調取的是database/leveldb/store.go下的GetBlock,它會從磁盤中獲取block信息並返回。

相關文章
相關標籤/搜索