菜鳥系列Fabric源碼學習 — MVCC驗證


Fabric 1.4 源碼分析 MVCC驗證

讀本節文檔之前建議先查看[Fabric 1.4 源碼分析 committer記賬節點]章節。

1. MVCC簡介

Multi-Version Concurrency Control 多版本並發控制,MVCC 是一種並發控制的方法,一般在數據庫管理系統中,實現對數據庫的並發訪問。在數據庫系統中,鎖機制可以控制並發操作,但是其系統開銷較大,而MVCC可以在大多數情況下代替行級鎖,使用MVCC,能降低其系統開銷。MVCC是通過保存數據在某個時間點的快照來實現的. 不同存儲引擎的MVCC. 不同存儲引擎的MVCC實現是不同的,典型的有樂觀並發控制和悲觀並發控制.

2. MVCC樣例介紹

InnoDB的MVCC,是通過在每行記錄后面保存兩個隱藏的列來實現的,這兩個列,分別保存了這個行的創建時間,一個保存的是行的刪除時間。這里存儲的並不是實際的時間值,而是系統版本號(可以理解為事務的ID),每開始一個新的事務,系統版本號就會自動遞增,事務開始時刻的系統版本號會作為事務的ID。其中MVCC只在 READ COMMITTED 和 REPEATABLE READ 兩個隔離級別下工作。

  • SELECT
    InnoDB會根據以下兩個條件檢查每行紀錄:
    InnoDB只查找版本早於當前事務版本的數據行,即,行的系統版本號小於或等於事務的系統版本號,這樣可以確保事務讀取的行,要么是在事務開始前已經存在的,要么是事務自身插入或者修改過的。
    行的刪除版本,要么未定義,要么大於當前事務版本號。這樣可以確保事務讀取到的行,在事務開始之前未被刪除。
    只有符合上述兩個條件的紀錄,才能作為查詢結果返回。

  • INSERT
    InnoDB為插入的每一行保存當前系統版本號作為行版本號。

  • DELETE
    InnoDB為刪除的每一行保存當前系統版本號作為行刪除標識。

  • UPDATE
    InnoDB為插入一行新紀錄,保存當前系統版本號作為行版本號,同時,保存當前系統版本號到原來的行作為行刪除標識。

優點
保存這兩個額外系統版本號,使大多數讀操作都可以不用加鎖。這樣設計使得讀數據操作很簡單,性能很好。
缺點
每行紀錄都需要額外的存儲空間,需要做更多的行檢查工作,以及一些額外的維護工作

3. Fabric里面MVCC的實現

這里回顧幾個知識點:

  • 狀態由鍵值對組成。所有鍵值條目都是帶有版本的
  • 鍵的版本只記錄在讀集中;寫集只包含鍵和交易設置的鍵的最新值
  • 使用讀寫集中的讀集來驗證交易,使用寫集來更新受影響的鍵的版本和值
  • 使用交易的高度來作為版本號

3.1 驗證公共數據讀集key

讀集中鍵的版本和世界狀態中鍵的版本一致就認為該交易是 合法的。

if !version.AreSame(committedVersion, rwsetutil.NewVersion(kvRead.Version)) {
	return false, nil
}
版本數據結構
type Version struct {
	BlockNum       uint64   
	TxNum          uint64   
}

當驗證完一筆交易后,如果交易有效,會更新key版本,接着再驗證下一筆交易。

committingTxHeight := version.NewHeight(block.Num, uint64(tx.IndexInBlock))
updates.ApplyWriteSet(tx.RWSet, committingTxHeight, v.db)

在此舉例介紹,mycc鏈碼a轉賬給b。
實例化鏈碼交易在區塊3中,則a、b版本為

{
    "key": "a",
    "version": {
        "block_num": "3",
        "tx_num": "0"
        }
}

發起一筆有效交易后,版本更新為

{
    "key": "a",
    "version": {
        "block_num": "4",
        "tx_num": "0"
        }
}

3.2 驗證range-query

當讀寫集中包含一個或多個查詢信息(query-info)時,需要執行額外的驗證。這種額外的驗證需要確保在根據查詢信息獲得的結果的超集(多個范圍的合並)中沒有插入、刪除或者更新鍵。

"range_queries_info":
[
    {
        "end_key":"marble3",
        "itr_exhausted":true,
        "raw_reads":{
            "kv_reads":[
                {
                    "key":"marble1",
                    "version":{
                        "block_num":"8",
                        "tx_num":"0"
                    }
                },
                {
                    "key":"marble2",
                    "version":{
                        "block_num":"9",
                        "tx_num":"0"
                    }
                }
            ]
        },
        "start_key":"marble1"
    }
]

validate()方法會根據rangeQueryInfo是否包含了合法當梅克爾樹摘要對象返回不同當驗證方法。

if rangeQueryInfo.GetReadsMerkleHashes() != nil {
	logger.Debug(`Hashing results are present in the range query info hence, initiating hashing based validation`)
	// 暫時全局搜索只發現ReadsMerkleHashes讀,沒發現寫
	validator = &rangeQueryHashValidator{}
} else {
	logger.Debug(`Hashing results are not present in the range query info hence, initiating raw KVReads based validation`)
	validator = &rangeQueryResultsValidator{}
}

因此,在此只介紹rangeQueryResultsValidator;該方法會對讀集key以及版本與查詢結果進行一一比較。一致則返回true。

func (v *rangeQueryResultsValidator) validate() (bool, error) {
	rqResults := v.rqInfo.GetRawReads().GetKvReads()
	for i := 0; i < len(rqResults); i++ {
		versionedKV := result.(*statedb.VersionedKV)
		// versionedKV key驗證
		if versionedKV.Key != kvRead.Key {
			logger.Debugf("key name mismatch: Key in rwset = [%s], key in query results = [%s]", kvRead.Key, versionedKV.Key)
			return false, nil
		}
		// versionedKV版本驗證
		if !version.AreSame(versionedKV.Version, convertToVersionHeight(kvRead.Version)) {
			logger.Debugf(`Version mismatch for key [%s]: Version in rwset = [%#v], latest version = [%#v]`,
				versionedKV.Key, versionedKV.Version, kvRead.Version)
			return false, nil
		}
		if result, err = itr.Next(); err != nil {
			return false, err
		}
	}
}

3.3 驗證私密數據kvReadHash

當讀寫集中存在collection_hashed_rwset,需要驗證collHashedRWSet.HashedRwSet.HashedReads里面的KVReadHash.Version

{
    "collection_hashed_rwset":[
        {
            "collection_name":"collectionMarbles",
            "hashed_rwset":"CiYKIF4flG/gcV3gNm0J6EgLrXZyojVRVwKbDd+8lYUPBFcOEgIIDw==",
            "pvt_rwset_hash":null
        }
    ],
    "namespace":"marblesp",
    "rwset":null
}

源碼:

遍歷collHashedRWSets,再遍歷collHashedRWSet.HashedRwSet.HashedReads,最后對每個kvReadHash的版本進行驗證。

for _, collHashedRWSet := range collHashedRWSets {
	if valid, err := v.validateCollHashedReadSet(ns, collHashedRWSet.CollectionName, collHashedRWSet.HashedRwSet.HashedReads, updates); !valid || err != nil {
		return valid, err
	}
}
for _, kvReadHash := range kvReadHashes {
		if valid, err := v.validateKVReadHash(ns, coll, kvReadHash, updates); !valid || err != nil {
			return valid, err
		}
	}

驗證代碼與驗證key類似

committedVersion, err := v.db.GetKeyHashVersion(ns, coll, kvReadHash.KeyHash)
if err != nil {
	return false, err
}

if !version.AreSame(committedVersion, rwsetutil.NewVersion(kvReadHash.Version)) {
	logger.Debugf("Version mismatch for key hash [%s:%s:%#v]. Committed version = [%s], Version in hashedReadSet [%s]",
		ns, coll, kvReadHash.KeyHash, committedVersion, kvReadHash.Version)
	return false, nil
}

參考

  1. https://segmentfault.com/a/1190000012650596
  2. https://www.jianshu.com/p/db334404d909
  3. https://stone-fabric.readthedocs.io/zh/latest/readwrite.html
有興趣的關注IT程序員客棧哦


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM