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

读集中键的版本和世界状态中键的版本一致就认为该交易是 合法的。

  1. if !version.AreSame(committedVersion, rwsetutil.NewVersion(kvRead.Version)) {
  2. return false, nil
  3. }
  4. 版本数据结构
  5. type Version struct {
  6. BlockNum uint64
  7. TxNum uint64
  8. }

当验证完一笔交易后,如果交易有效,会更新key版本,接着再验证下一笔交易。

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

在此举例介绍,mycc链码a转账给b。
实例化链码交易在区块3中,则a、b版本为

  1. {
  2. "key": "a",
  3. "version": {
  4. "block_num": "3",
  5. "tx_num": "0"
  6. }
  7. }

发起一笔有效交易后,版本更新为

  1. {
  2. "key": "a",
  3. "version": {
  4. "block_num": "4",
  5. "tx_num": "0"
  6. }
  7. }

3.2 验证range-query

当读写集中包含一个或多个查询信息(query-info)时,需要执行额外的验证。这种额外的验证需要确保在根据查询信息获得的结果的超集(多个范围的合并)中没有插入、删除或者更新键。

  1. "range_queries_info":
  2. [
  3. {
  4. "end_key":"marble3",
  5. "itr_exhausted":true,
  6. "raw_reads":{
  7. "kv_reads":[
  8. {
  9. "key":"marble1",
  10. "version":{
  11. "block_num":"8",
  12. "tx_num":"0"
  13. }
  14. },
  15. {
  16. "key":"marble2",
  17. "version":{
  18. "block_num":"9",
  19. "tx_num":"0"
  20. }
  21. }
  22. ]
  23. },
  24. "start_key":"marble1"
  25. }
  26. ]

validate()方法会根据rangeQueryInfo是否包含了合法当梅克尔树摘要对象返回不同当验证方法。

  1. if rangeQueryInfo.GetReadsMerkleHashes() != nil {
  2. logger.Debug(`Hashing results are present in the range query info hence, initiating hashing based validation`)
  3. // 暂时全局搜索只发现ReadsMerkleHashes读,没发现写
  4. validator = &rangeQueryHashValidator{}
  5. } else {
  6. logger.Debug(`Hashing results are not present in the range query info hence, initiating raw KVReads based validation`)
  7. validator = &rangeQueryResultsValidator{}
  8. }

因此,在此只介绍rangeQueryResultsValidator;该方法会对读集key以及版本与查询结果进行一一比较。一致则返回true。

  1. func (v *rangeQueryResultsValidator) validate() (bool, error) {
  2. rqResults := v.rqInfo.GetRawReads().GetKvReads()
  3. for i := 0; i < len(rqResults); i++ {
  4. versionedKV := result.(*statedb.VersionedKV)
  5. // versionedKV key验证
  6. if versionedKV.Key != kvRead.Key {
  7. logger.Debugf("key name mismatch: Key in rwset = [%s], key in query results = [%s]", kvRead.Key, versionedKV.Key)
  8. return false, nil
  9. }
  10. // versionedKV版本验证
  11. if !version.AreSame(versionedKV.Version, convertToVersionHeight(kvRead.Version)) {
  12. logger.Debugf(`Version mismatch for key [%s]: Version in rwset = [%#v], latest version = [%#v]`,
  13. versionedKV.Key, versionedKV.Version, kvRead.Version)
  14. return false, nil
  15. }
  16. if result, err = itr.Next(); err != nil {
  17. return false, err
  18. }
  19. }
  20. }

3.3 验证私密数据kvReadHash

当读写集中存在collection_hashed_rwset,需要验证collHashedRWSet.HashedRwSet.HashedReads里面的KVReadHash.Version

  1. {
  2. "collection_hashed_rwset":[
  3. {
  4. "collection_name":"collectionMarbles",
  5. "hashed_rwset":"CiYKIF4flG/gcV3gNm0J6EgLrXZyojVRVwKbDd+8lYUPBFcOEgIIDw==",
  6. "pvt_rwset_hash":null
  7. }
  8. ],
  9. "namespace":"marblesp",
  10. "rwset":null
  11. }

源码:

遍历collHashedRWSets,再遍历collHashedRWSet.HashedRwSet.HashedReads,最后对每个kvReadHash的版本进行验证。

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

验证代码与验证key类似

  1. committedVersion, err := v.db.GetKeyHashVersion(ns, coll, kvReadHash.KeyHash)
  2. if err != nil {
  3. return false, err
  4. }
  5. if !version.AreSame(committedVersion, rwsetutil.NewVersion(kvReadHash.Version)) {
  6. logger.Debugf("Version mismatch for key hash [%s:%s:%#v]. Committed version = [%s], Version in hashedReadSet [%s]",
  7. ns, coll, kvReadHash.KeyHash, committedVersion, kvReadHash.Version)
  8. return false, nil
  9. }

参考

  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. Hadoop应用程序示例

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

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

  3. HZOJ 那一天她离我而去

    一个数据水到不行的题,各路大佬用各种方法A掉了这个题(比如A*,最短路,dfs……). 这里只说一下我的暴力和被碾压的正解. 暴力AC系列: 要找过1点的最小环,那么这个环可以拆成两部分,与1相连的两 ...

  4. 利用scrapy爬取文件后并基于管道化的持久化存储

    我们在pycharm上爬取 首先我们可以在本文件打开命令框或在Terminal下创建 scrapy startproject xiaohuaPro   ------------创建文件 scrapy ...

  5. Project Euler Problem 21-Amicable numbers

    先说最暴力的算法,直接对一万内的每个数字暴力分解因子(对每个数字的时间复杂度是O(sqrt(n)的),然后,用个数组记录下来因子和,然后寻找 亲密数. 好一点:要先打个素数表,然后对每个数字,分解素因 ...

  6. 2016年NOIP普及组复赛题解

    题目涉及算法: 买铅笔:入门题: 回文日期:枚举: 海港:双指针: 魔法阵:数学推理. 买铅笔 题目链接:https://www.luogu.org/problem/P1909 设至少要买 \(num ...

  7. Nuget 通过 dotnet 命令行发布

    在开发完成一个好用的轮子就想将这个轮子发布到 nuget 让其他小伙伴可以来使用,但是 nuget.org 的登陆速度太慢,本文介绍一个命令行发布的方法,通过命令行发布的方法可以配合 Jenkins ...

  8. golang http get请求方式

    client := &http.Client{} //生成要访问的url,token是api鉴权,每个api访问方式不同,根据api调用文档拼接URLurl := fmt.Sprintf(&q ...

  9. 2018-8-10-win10-uwp-win2d-使用-Path-绘制界面

    title author date CreateTime categories win10 uwp win2d 使用 Path 绘制界面 lindexi 2018-08-10 19:17:19 +08 ...

  10. H3C 多路径网络中环路产生过程(1)