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
- }
参考
- https://segmentfault.com/a/1190000012650596
- https://www.jianshu.com/p/db334404d909
来源: https://www.cnblogs.com/jiliguo/p/12148838.html