超级账本源码分析(八) - kvledger初始化

前两篇文章简单分析了fabric的配置和日志系统。这一篇分析下 /fabric/peer/node/start.go 中的serve()函数。超级账本源码分析(五) - peer命令结构 这篇文章讲到过,这个函数做了大量的工作。

账本简单介绍

ledger,即账本的意思。serve()函数里初始化账本的相关操作为:

	//initialize resource management exit
	ledgermgmt.Initialize(peer.ConfigTxProcessors)

账本源码目录

  • common/ledger
  • core/ledger

也就是说Fabric把关于ledger的源码把其他模块用得到的,有共享属性的部分放到了common目录下,核心的代码放到了core目录下。

账本数据库

Fabric中的ledger其实就是一系列数据库存储操作。对应所选用的数据库,主要有两种:goleveldb和couchDB。在core.yaml配置文件中ledger区域中,stateDatabase选项即为指定要选用的数据库,默认选用goleveldb。

goleveldb:主要使用了第三方库leveldb,地址: github.com/syndtr/goleveldb/leveldb 。leveldb是一个典型的key-value数据库,有多种语言版本,这里使用的是goleveldb。这也是Fabric把ledger也称为kvledger的原因,这很好的体现了账本的特性,数据的操作都是基于键-值。关于goleveldb数据库定义,操作的代码,集中在common/ledger/util/leveldbhelper目录下。

couchDB:在Fabric用go语言实现的一个数据库,源码集中在core/ledger/util/couchdb下。couchDB在Fabric目前只用于版本数据库所使用的两个方案中的一个。

leveldb的基本操作

1.打开数据库,db, err:=leveldb.OpenFile("./db", nil)。作用就是在当前目录下创建一个db文件夹作为数据库的目录。
2.存储键值对,db.Put([]byte(“key1”),[]byte(“value1”),nil)。作用就是在数据库中存储键值对 key1-value1。leveldb数据库中对键值的操作都是byte格式化的数据。
3.获取键值对,data,_ := db.Get([]byte(“key1”),nil)。获取key1对应的值。
4.遍历数据库,iter := db.NewIterator(nil, nil),for iter.Next(){ fmt.Printf(“key=%s,value=%s\n”,iter.Key(),iter.Value()) },iter.Release()。
作用就是建立迭代器iter,然后依次遍历数据库中所有的数据并打印键和值,最后释放迭代器iter。
5.关闭数据库,db.Close()。

账本对象

Fabric到处都是接口,各个层级的代码编写风格和习惯很一致,甚至使用的函数名,对象名都有大量雷同的,因而在有些源码处十分的绕,其实阅读源代码我们应该遵循以下2条原则:

  1. 无论(概念上或形式上)多么复杂的对象,其本质也不过是一个结构体和挂载到该结构体一些操作函数而已。
  2. 无论对象的初始化多么复杂,其本质也不过是声明后填充该对象中的各个字段的过程而已。挂载到该对象的函数无论多么复杂,也不过是对该对象中的成员所承载的数据进行增、删、改、查操作而已。

继续查看ledgermgmt.Initialize()函数,定义在core/ledger/ledgermgmt/ledger_mgmt.go中,其直接once.Do了ledger_mgmt.go中的initialize()函数:

func initialize(customTxProcessors customtx.Processors, statelisteners []ledger.StateListener) {
	logger.Info("Initializing ledger mgmt")
	lock.Lock()
	defer lock.Unlock()
	initialized = true
	openedLedgers = make(map[string]ledger.PeerLedger)
	customtx.Initialize(customTxProcessors)
	cceventmgmt.Initialize()
	finalStateListeners := addListenerForCCEventsHandler(statelisteners)
	provider, err := kvledger.NewProvider()
	if err != nil {
		panic(fmt.Errorf("Error in instantiating ledger provider: %s", err))
	}
	provider.Initialize(finalStateListeners)
	ledgerProvider = provider
	logger.Info("ledger mgmt initialized")
}

所做的主要是初始化了三个对象:initialized,openedLedgers 和 ledgerProvider,这三个对象均为文件中的全局变量。
其中initialized的赋了初值,openedLedgers分配了内存,两者并未有进一步操作,暂时先不管。而ledgerProvider则被赋于由kvledger.NewProvider()函数(定义在定义在core/ledger/kvledger/kv_ledger_provider.go中)返回的值,从其名字可以看出该对象是一个账本服务提供者,因此我们着重分析下这个。其原型为ledger.PeerLedgerProvider接口,定义在core/ledger/ledger_interface.go中:

// PeerLedgerProvider provides handle to ledger instances
type PeerLedgerProvider interface {
	Initialize(statelisteners []StateListener)
	// Create creates a new ledger with the given genesis block.
	// This function guarantees that the creation of ledger and committing the genesis block would an atomic action
	// The chain id retrieved from the genesis block is treated as a ledger id
	Create(genesisBlock *common.Block) (PeerLedger, error)
	// Open opens an already created ledger
	Open(ledgerID string) (PeerLedger, error)
	// Exists tells whether the ledger with given id exists
	Exists(ledgerID string) (bool, error)
	// List lists the ids of the existing ledgers
	List() ([]string, error)
	// Close closes the PeerLedgerProvider
	Close()
}

根据Fabric的惯例,有Provider字样的对象,或大或小,都是某一主题模块服务的提供者,提供该主题模块的一系列操作服务。而接口类型的Provider对象,则会有多种具体的Provider实现以供使用。ledger的Provider也是如此,kvledger.NewProvider()函数返回的对象就是PeerLedgerProvider接口的一个具体实现,定义在core/ledger/kvledger/kv_ledger_provider.go(kv_ledger_provider即为键值账本提供者的意思)中的Provider:

// Provider implements interface ledger.PeerLedgerProvider
type Provider struct {
	idStore             *idStore     	 								      //ledgerID数据库
	ledgerStoreProvider *ledgerstorage.Provider           //账本数据库存储服务对象
	vdbProvider         privacyenabledstate.DBProvider  //状态数据库存储服务对象
	historydbProvider   historydb.HistoryDBProvider     //历史数据库存储服务对象
	configHistoryMgr    confighistory.Mgr
	stateListeners      []ledger.StateListener
	bookkeepingProvider bookkeeping.Provider
}

根据Fabric的惯例,在每个定义对象结构体的文件里,通常都会定义一个专门用于生成该对象的函数,这里的kv_ledger_provider.go文件中的NewProvider()函数即是用于生成键值账本服务提供者的函数。

区块数据库存储服务对象blkStoreProvider

账本数据库存储服务对象ledgerstorage.Provider(定义在core/ledger/ledgerstorage/store.go中)封装了block数据库存储服务对象和pvt数据存储服务对象:

// Provider encapusaltes two providers 1) block store provider and 2) and pvt data store provider
type Provider struct {
	blkStoreProvider     blkstorage.BlockStoreProvider
	pvtdataStoreProvider pvtdatastorage.Provider
}

这里以block数据库存储服务对象blkStoreProvider的结构为例,其代码集中在commom/ledger/blkstorage下。blkStoreProvider是个BlockStoreProvider类型的对象,BlockStoreProvider定义在blockstorage.go(commom/ledger/blkstorage/blockstorage.go)中,具体实现为用文件系统存储,即commom/ledger/blkstorage/fsblkstorage/fs_blockstore_provider.go中定义的FsBlockstoreProvider:

// FsBlockstoreProvider provides handle to block storage - this is not thread-safe
type FsBlockstoreProvider struct {
	conf            *Conf
	indexConfig     *blkstorage.IndexConfig
	leveldbProvider *leveldbhelper.Provider
}

块数据存储服务对象blkStoreProvider的三个成员,其中两个配置项成员conf和indexConfig,是相较于其他数据库服务对象所独有的,一个leveldb数据库存储服务提供者leveldbProvider对象,则和其他数据库服务对象一样。而专门用于初始化FsBlockstoreProvider的函数即为fsblkstorage.NewProvider() (定义在fs_blockstore_provider.go中)。

core/ledger/ledgerstorage/store.go中的 NewProvider() 调用fsblkstorage.NewProvider() 方法用于创建blockStoreProvider 对象:

// NewProvider returns the handle to the provider
func NewProvider() *Provider {
	// Initialize the block storage
	attrsToIndex := []blkstorage.IndexableAttr{
		blkstorage.IndexableAttrBlockHash,
		blkstorage.IndexableAttrBlockNum,
		blkstorage.IndexableAttrTxID,
		blkstorage.IndexableAttrBlockNumTranNum,
		blkstorage.IndexableAttrBlockTxID,
		blkstorage.IndexableAttrTxValidationCode,
	}
	indexConfig := &blkstorage.IndexConfig{AttrsToIndex: attrsToIndex}
	blockStoreProvider := fsblkstorage.NewProvider(
		fsblkstorage.NewConf(ledgerconfig.GetBlockStorePath(), ledgerconfig.GetMaxBlockfileSize()),
		indexConfig)

	pvtStoreProvider := pvtdatastorage.NewProvider()
	return &Provider{blockStoreProvider, pvtStoreProvider}
}

块存储配置conf
该配置对象在fsblkstorage/config.go中定义,两个字段blockStorageDir和maxBlockfileSize指定了块数据库存储服务对象所使用的路径和存储文件的大小。

块索引配置indexConfig
用于存储块索引字段值,可以将其想象成数据库表中准备为哪些字段建立索引,因而在此记录一下。

leveldb数据库存储服务对象leveldbProvider
实际上,这是最终操作数据库数据的对象。账本所使用的四个数据库服务对象均使用此数据库对象对数据进行操作。在common/ledger/util/leveldbhelper/leveldb_provider.go中定义:

// Provider enables to use a single leveldb as multiple logical leveldbs
type Provider struct {
	db        *DB
	dbHandles map[string]*DBHandle
	mux       sync.Mutex
}

包含了封装leveldb数据库对象的db,和一个数据库映射dbHandles,和一把保护锁mux。

账本服务对象(kv_ledger_provider.go#Provider)的目录结构
回到kvledger.NewProvider()函数中(kv_ledger_provider.go文件中),其他几个数据库的初始化过程和区块数据库存储服务对象blkStoreProvider类似,但相比较而言更简单一些,基本都只是用专用函数初始化了一个leveldb数据库存储服务对象leveldbProvider。至此,整个账本服务对象初始化完毕。以下列出账本服务对象的整体结构和所形成的目录结构,具象化一下。

对象结构:
超级账本源码分析(八) - kvledger初始化_第1张图片

目录结构:

  • /var/hyperledger/production(core.yaml定义的flieSystemPath的值)
    • ledgersData //账本目录
      • ledgerProvider //ledgerID数据库目录
      • chains //block块存储数据库目录
        • index //block索引数据库目录
        • chains
          • 账本ID1
          • 账本ID2
      • stateLeveldb //状态数据库目录
      • historyLeveldb //历史数据库目录

你可能感兴趣的:(区块链,Hyperledger,Fabric,源码分析)