前两篇文章简单分析了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目前只用于版本数据库所使用的两个方案中的一个。
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条原则:
继续查看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()函数即是用于生成键值账本服务提供者的函数。
账本数据库存储服务对象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。至此,整个账本服务对象初始化完毕。以下列出账本服务对象的整体结构和所形成的目录结构,具象化一下。
目录结构: