• Derek解读Bytom源码-持久化存储LevelDB


    作者: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
    ", 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信息并返回。

  • 相关阅读:
    第一个Android应用 扫描宝 欲挑战传统扫描枪
    前端工程师在实现支付功能的时候能做些什么(V客学院技术分享)?
    HBuilder android 打包指南(V客学院技术分享)
    JavaScript 事件处理详解
    关于webpack的path和publicPath。
    svg线条的动画到渐变
    vue目录结构及其对应作用
    数据改变视图未变问题解决(Object.assign)
    ES6语法的简单介绍——拓展运算符
    webpack打包原理
  • 原文地址:https://www.cnblogs.com/bytom/p/9528061.html
Copyright © 2020-2023  润新知