作者:Derek 簡介 Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom 本章介紹Derek解讀 Bytom源碼分析 持久化存儲LevelDB 作者使用MacOS操作系統,其 ...
作者:Derek
簡介
Github地址:https://github.com/Bytom/bytom
Gitee地址:https://gitee.com/BytomBlockchain/bytom
本章介紹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信息並返回。