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

菜鸟系列Fabric源码学习 — MVCC验证的更多相关文章

  1. 菜鸟系列Fabric源码学习 — committer记账节点

    Fabric 1.4 源码分析 committer记账节点 本文档主要介绍committer记账节点如何初始化的以及committer记账节点的功能及其实现. 1. 简介 记账节点负责验证交易和提交账 ...

  2. 菜鸟系列Fabric源码学习 — 区块同步

    Fabric 1.4 源码分析 区块同步 本文主要从源码层面介绍fabric peer同步区块过程,peer同步区块主要有2个过程: 1)peer组织的leader与orderer同步区块 2)pee ...

  3. 菜鸟系列Fabric源码学习 — peer节点启动

    Fabric 1.4 源码分析peer节点启动 peer模块采用cobra库来实现cli命令. Cobra提供简单的接口来创建强大的现代化CLI接口,比如git与go工具.Cobra同时也是一个程序, ...

  4. 菜鸟系列Fabric源码学习—创建通道

    通道创建源码解析 1. 与通道创建相关配置及操作命令 主要是configtx.yaml.通过应用通道的profile生成创建通道的配置文件. TwoOrgsChannel: Consortium: S ...

  5. 菜鸟系列Fabric源码学习—orderer服务启动

    Fabric 1.4 orderer 服务启动流程 1.提要 orderer提供broadcast和deliver两个服务接口.orderer节点与各个peer节点通过grpc连接,orderer将所 ...

  6. MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)

    前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...

  7. MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)

    前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎.这两天算是理解了一小部分,这里先记录下来,也给需要的园友一个参考,奈何博主技术有限,如有理解不妥之 ...

  8. [转]MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)

    本文转自:http://www.cnblogs.com/landeanfen/p/5989092.html 阅读目录 一.MVC原理解析 1.MVC原理 二.HttpHandler 1.HttpHan ...

  9. MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)

    前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...

随机推荐

  1. mybatis-generator1.3.6的使用

    下载地址: http://blog.mybatis.org/2017/12/mybatis-generator-version-136-released.html 参考了 http://blog.cs ...

  2. 洛谷P2330 [SCOI2005]繁忙的都市

    #include<bits/stdc++.h> using namespace std; ; ; int n,k,Max,tot; struct node{ int cnt,fa; }f[ ...

  3. 开发板ping通虚拟机与主机

    刚因为虚拟机与主机没法互相ping通的事情,奋战到将近凌晨一点.现在把这个过程总结一下,以方便后加入该行业的广大IT精英. VMWare提供了三种工作模式:bridged(桥接模式).NAT(网络地址 ...

  4. 12 Top Open Source Data Analytics Apps

    1. Hadoop It would be impossible to talk about open source data analytics without mentioning Hadoop. ...

  5. 如何使用jmeter调用soap协议

  6. H3C 常用信息查看命令

  7. 在做微信分享到朋友圈时,手机扫码报config:invalid signature,分享后后正常的问题,是url问题

    是按照以下步骤检查的 除了ACCESS_TOKEN没有缓存其他都可以 如果是invalid signature签名错误.建议按如下顺序检查: 1.确认签名算法正确,可用 http://mp.weixi ...

  8. [C#] 用ServiceStack读写redis的性能问题

    ServiceStack.Redis有个方法叫 AddRangeToList,这个方法是有性能问题的.这个方法的实现代码如下: public void AddRangeToList(string li ...

  9. 【b801】笨小猴

    Time Limit: 1 second Memory Limit: 50 MB [问题描述] 笨小猴的词汇量很小,所以每次做英语选择题的时候都很头疼.但是他找到了一种方法,经试验证明,用这种方法去选 ...

  10. 【转载】Windows平台下利用APM来做负载均衡方案 - 负载均衡(下)

    概述 我们在上一篇Windows平台分布式架构实践 - 负载均衡中讨论了Windows平台下通过NLB(Network Load Balancer) 来实现网站的负载均衡,并且通过压力测试演示了它的效 ...