1. 程式人生 > >05.Fabric原始碼分析–kvledger的初始化

05.Fabric原始碼分析–kvledger的初始化

Fabric原始碼分析5–kvledger的初始化

前兩篇文章藉由/fabric/peer/main.go這個線頭,簡單分析了fabric的配置和日誌系統。雖然還有一部分可說的內容,如common.InitCrypto()呼叫,但現在暫且按下main.go不管,而把目光投到/fabric/peer/node/start.go中的serve()函式。具體原因參見peer命令結構一文的最後。

Error: failed to create deliver client: orderer client failed to connect to Orderer.itcast.com:7050: failed to create new connection: context deadline exceeded

賬本簡單介紹

ledger,中文就是賬本,賬冊的意思。先來討論Fabric賬冊的原因來自於peer node start所執行的函式serve第一句程式碼即為ledgermgmt.Initialize(),字面上看屬於對賬冊進行初始化操作,換句話說**,對賬本相關環境的初始化**,是其他peer命令執行的基礎之一。

賬本原始碼目錄

  • common/ledger
  • core/ledger

也就是說Fabric把關於ledger的原始碼把其他模組用得到的,有共享屬性的部分放到了common目錄下,核心的程式碼放到了core目錄下。

賬本資料庫

Fabric中的ledger說白了就是一系列資料庫儲存操作


Fabric中的ledger說白了就是一系列資料庫儲存操作
Fabric中的ledger說白了就是一系列資料庫儲存操作

對應所選用的資料庫,主要有兩種:goleveldb和couchDB。在core.yaml配置檔案中ledger區域中,stateDatabase選項即為指定所選用的資料庫,預設選用goleveldb。本文章著意於ledger的操作,因此下文也只以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。
  3. leveldb資料庫中對鍵值的操作都是byte格式化的資料
  4. 獲取鍵值對,data,_ := db.Get([]byte(“key1”),nil)。獲取key1對應的值。
  5. 遍歷資料庫,iter := db.NewIterator(nil, nil),for iter.Next(){ fmt.Printf(“key=%s,value=%s\n”,iter.Key(),iter.Value()) },iter.Release()。作用就是建立迭代器iter,然後依次遍歷資料庫中所有的資料並列印鍵和值,最後釋放迭代器iter。
  6. 關閉資料庫,db.Close()。

賬本物件

Fabric到處都是介面,各個層級的程式碼編寫風格和習慣很一致,甚至使用的函式名,物件名都有大量雷同的,因而在有些原始碼處十分的繞,其實閱讀原始碼我們應該遵循以下2條原則:

  1. 無論(概念上或形式上)多麼複雜的物件,其本質也不過是一個結構體和掛載到該結構體一些操作函式而已。
  2. 無論物件的初始化多麼複雜,其本質也不過是聲明後填充該物件中的各個欄位的過程而已。掛載到該物件的函式無論多麼複雜,也不過是對該物件中的成員所承載的資料進行增、刪、改、查操作而已。

因此,追溯並檢視ledgermgmt.Initialize()函式,在core/ledger/ledgermgmt/ledger_mgmt.go中,其直接once.Do了initialize()函式。在initialize()函式中,除了日誌操作和鎖保護(一般在初始化的函式中,都會進行鎖保護,各位可自行參看其他原始碼)外,所做的只是初始化了三個物件:openedLedgers,ledgerProvider和initialized,該三個物件均為檔案中的全域性變數:

initialized = true
openedLedgers = make(map[string]ledger.PeerLedger)
provider, err := kvledger.NewProvider()
ledgerProvider = provider

其中initialized和openedLedgers分別賦了初值和分配了記憶體,openedLedgers應該是存放peer的賬本對映的,initialized則是是否初始化的標識,兩者並未有進一步操作,可暫時擱置一旁。而ledgerProvider則被賦於由kvledger.NewProvider()函式返回的值,從其名字我們就可以知道該物件是一個賬本服務提供者,因此我們的目光停留在此即可。其原型為ledger.PeerLedgerProvider介面,在core/ledger/ledger_interface.go中定義:

type PeerLedgerProvider interface {
    Create(genesisBlock *common.Block) (PeerLedger, error)
    Open(ledgerID string) (PeerLedger, error)
    Exists(ledgerID string) (bool, error)
    List() ([]string, error)
    Close()
}

根據Fabric的慣例,有Provider字樣的物件,或大或小,都是某一主題模組服務的提供者,提供該主題模組的一系列操作服務。而介面型別的Provider物件,則在具體實現上則會分為多種具體的Provider以供使用(同時也留下了擴充套件空間)。ledger的Provider亦然如此,kvledger.NewProvider()函式返回的就是PeerLedgerProvider介面的一個具體實現,kvledgerprovider,即鍵值賬本提供者,在core/ledger/kvledger/kv_ledger_provider.go中定義:

type Provider struct {
    idStore            *idStore                      //ledgerID資料庫
    blockStoreProvider blkstorage.BlockStoreProvider //block資料庫儲存服務物件
    vdbProvider        statedb.VersionedDBProvider   //狀態資料庫儲存服務物件
    historydbProvider  historydb.HistoryDBProvider   //歷史資料庫儲存服務物件
}

還是根據Fabric的慣例,在每個定義物件結構的檔案裡,通常都會有一個專門用於生成該物件的函式,kvledger.NewProvider()既是用於生成鍵值賬本服務提供者的函式。通過追溯,我們會發現,這個物件中的四個成員物件其實就是四個資料庫,分別用於儲存不同的資料,也是賬冊儲存所需要的。而kvledger.NewProvider()所做的,就是分別按照配置生成這四個資料庫物件,這也符合和上文我們所說的兩個原則。四個資料庫除了idStore和blockStoreProvider有自己特殊的配置外,都共同使用的leveldb資料庫儲存服務提供者,後文將以塊資料庫服務物件為例,詳細介紹。

塊資料庫儲存服務物件

以block資料庫儲存服務物件blockStoreProvider的結構為例,其程式碼集中在commom/ledger/blkstorage下(本節中除介紹leveldb資料庫儲存服務物件外,所示路徑皆以此路徑為基準)。blockStoreProvider的原型BlockStoreProvider依然是個介面,在blockstorage.go中定義此介面,具體實現為用檔案系統儲存,即fsblkstorage/fs_blockstore_provider.go中定義的FsBlockstoreProvider。

也就是說,與塊資料儲存服務物件blockStoreProvider最終對接的是三個成員,其中兩個配置項成員conf和indexConfig,是相較於其他資料庫服務物件所獨有的,一個leveldb資料庫儲存服務提供者leveldbProvider,則和其他資料庫服務物件一樣。而專門用於初始化FsBlockstoreProvider的函式即為fsblkstorage.NewProvider()。

在kvledger.NewProvider()中,以下三句程式碼用於初始化blockStoreProvider物件

attrsToIndex := []blkstorage.IndexableAttr{
    blkstorage.IndexableAttrBlockHash,
    blkstorage.IndexableAttrBlockNum,
    blkstorage.IndexableAttrTxID,
    blkstorage.IndexableAttrBlockNumTranNum,
    blkstorage.IndexableAttrBlockTxID,
    blkstorage.IndexableAttrTxValidationCode,
}
indexConfig := &blkstorage.IndexConfig{AttrsToIndex: attrsToIndex}
//傳遞兩個配置項,並在內部使用專用函式生成一個leveldb資料庫物件,最終生成塊資料儲存服務物件
blockStoreProvider := fsblkstorage.NewProvider(
    fsblkstorage.NewConf(ledgerconfig.GetBlockStorePath(),
                         ledgerconfig.GetMaxBlockfileSize()),
    indexConfig)

塊索引配置indexConfig

用於儲存塊索引欄位值,這點blockstorage.go中進行了硬編碼。可以將其想象成資料庫表中準備為哪些欄位建立索引,因而在此記錄一下。

塊儲存配置conf

該配置物件在fsblkstorage/config中定義,兩個欄位blockStorageDir和maxBlockfileSize指定了塊資料庫儲存服務物件所使用的路徑和儲存檔案的大小。在fsblkstorage.NewProvider()中,傳入的config是用NewConf(ledgerconfig.GetBlockStorePath(),ledgerconfig.GetMaxBlockfileSize())進行建立的,而NewConf又使用了ledgerconfig下的函式分別獲取了路徑和大小。而通過追溯ledgerconfig,發現其最終形成的路徑值為/var/hyperledger/production/ledgersData/chains,大小為64M(關於路徑和配置,將在以配置和路徑為主題的文章中詳細介紹)。

leveldb資料庫儲存服務物件blockStoreProvider

實際的,最終操作資料庫資料的物件,賬本所使用的四個資料庫服務物件均使用此資料庫物件對資料進行操作。在common/ledger/util/leveldbhelper/leveldb_provider.go中定義:

type Provider struct {
    db        *DB
    dbHandles map[string]*DBHandle
    mux       sync.Mutex
}

leveldb資料庫儲存服務物件包含了封裝leveldb資料庫物件的db,和一個數據庫對映dbHandles,和一把保護鎖mux。因為資料庫是對資料的讀寫操作,因此有一把保護鎖很正常也很應該。其處於同一個檔案中專用的初始化函式leveldbhelper.NewProvider(),在fsblkstorage.NewProvider()函式中即有體現:p := leveldbhelper.NewProvider(&leveldbhelper.Conf{DBPath: conf.getIndexDir()})

在此仍需關注的是DB結構中自帶一個leveldbhelper.Conf配置選項,定義了leveldb資料庫所在的目錄。在leveldbhelper.NewProvider()初始化的過程中,使用了上層物件-塊資料庫儲存服務物件中的conf所掛載的函式getIndexDir(),在common/ledger/blkstorage/fsblkstorage/config.go中定義,獲取了一個路徑,最終被初始化在chains/index下。

賬本服務物件的目錄結構

回到kvledger.NewProvider()函式中,其他幾個資料庫的初始化過程和塊資料庫儲存服務物件類似,但簡單一些,基本都只是用專用函式初始化了一個leveldb資料庫儲存服務物件。至此,整個賬本服務物件初始化完畢。以下列出賬本服務物件整個物件的結構和所形成的目錄結構,具象化一下。

/var/hyperledger/production(core.yaml定義的flieSystemPath的值) 
ledgersData //賬本目錄 
ledgerProvider //ledgerID資料庫目錄
chains //block塊儲存資料庫目錄 
index //block索引資料庫目錄
chains 
賬本ID1
賬本ID2
…
stateLeveldb //狀態資料庫目錄
historyLeveldb //歷史資料庫目錄

在kvledger.NewProvider()函式中接近結尾的地方,有一句程式碼需要注意:provider.recoverUnderConstructionLedger(),該句呼叫了賬本服務物件的一個函式,主要是用於恢復處理一些之前賬本初始化失敗的操作,從中牽扯出了一大堆函式呼叫。但對這些操作的理解需要建立在理解賬本操作的基礎之上,因此在此埋下伏筆,之後的文章中將回過頭來詳細介紹此句。