近期项目中遇到了一个分布式系统的并发控制问题。该问题能够抽象为:某分布式系统由一个数据中心D和若干业务处理中心L1,L2 … Ln组成;D本质上是一个key-value存储,它对外提供基于HTTP协议的CRUD操作接口。L的业务逻辑能够抽象为以下3个步骤:

  1. read: 依据keySet {k1, … kn}从D获取keyValueSet {k1:v1, … kn:vn}
  2. do: 依据keyValueSet进行业务处理,得到须要更新的数据集keyValueSet’ {k1′:v1′, … km’:vm’} (:读取的keySet和更新的keySet’可能不同)
  3. update: 把keyValueSet’更新到D (:D保证在一次调用更新多个key的原子性)

在没有事务支持的情况下。多个L进行并发处理可能会导致数据一致性问题。比方。考虑L1和L2的例如以下运行顺序:

  1. L1从D读取key:123相应的值100
  2. L2从D读取key:123相应的100
  3. L1将key:123更新为100 + 1
  4. L2将key:123更新为100 + 2

假设L1和L2串行运行,key:123相应的值将为103,但上面并发运行中L1的运行效果全然被L2所覆盖,实际key:123所相应的值变成了102。

解决方式1:基于锁的事务

为了让L的处理具有可串行化特性(Serializability),一种最直接的解决方式就是考虑为D加上基于锁的简单事务。让L在进行业务处理前先锁定D,完毕以后释放锁。另外,为了防止持有锁的L因为某种原因长时间未提交事务。D还须要具有超时机制,当L尝试提交一个已超时的事务时会得到一个错误响应。

本方案的长处是实现简单,缺点是锁定了整个数据集。粒度太大;时间上包括了L的整个处理时间,跨度太长。尽管我们能够考虑把锁定粒度减少到数据项级别,按key进行锁定,但这又会带来其它的问题。因为更新的keySet’可能是事先不确定的,所以可能无法在開始事务时锁定全部的key。假设分阶段来锁定须要的key。又可能出现死锁(Deadlock)问题。另外,按key锁定在有锁争用的情况下并不能解决锁定时间太长的问题。

所以,按key锁定仍然存在重要的不足之处。

解决方式2:多版本号并发控制

为了实现可串行化。同一时候避免锁机制存在的各种问题。我们能够採用基于多版本号并发控制(Multiversion concurrency control。MVCC)思想的无锁事务机制。人们一般把基于锁的并发控制机制称成为悲观机制,而把MVCC机制称为乐观机制。这是因为锁机制是一种预防性的,读会堵塞写。写也会堵塞读,当锁定粒度较大。时间较长时并发性能就不会太好;而MVCC是一种后验性的,读不堵塞写。写也不堵塞读。等到提交的时候才检验是否有冲突。因为没有锁,所以读写不会相互堵塞,从而大大提升了并发性能。我们能够借用源码版本号控制来理解MVCC。每一个人都能够自由地阅读和改动本地的代码,相互之间不会堵塞,仅仅在提交的时候版本号控制器会检查冲突,并提示merge。

眼下,Oracle、PostgreSQL和MySQL都已支持基于MVCC的并发机制,但详细实现各有不同。

MVCC的一种简单实现是基于CAS(Compare-and-swap)思想的有条件更新(Conditional Update)。普通的update參数仅仅包括了一个keyValueSet’,Conditional Update在此基础上加上了一组更新条件conditionSet { … data[keyx]=valuex, … },即仅仅有在D满足更新条件的情况下才将数据更新为keyValueSet’。否则。返回错误信息。这样,L就形成了例如以下图所看到的的Try/Conditional Update/(Try again)的处理模式:

尽管对单个L来讲不能保证每次都成功更新,但从整个系统来看,总是有任务能够顺利进行。这样的方案利用Conditional Update避免了大粒度和长时间的锁定。当各个业务之间资源争用不大的情况下,并发性能非常好。只是,因为Conditional Update须要很多其它的參数。假设condition中value的长度非常长,那么每次网络传送的数据量就会比較大,从而导致性能下降。

特别是当须要更新的keyValueSet’非常小。而condition非常大时。就显得非常不经济。

为了避免condition太大所带来的性能问题。能够为每条数据项添加一个int型的版本号号字段,由D维护该版本号号,每次数据有更新就添加版本号号;L在进行Conditional Update时,通过版本号号代替详细的值。

还有一个问题是上面的解决方式假设了D是能够支持Conditional Update的。那么。假设D是一个不支持Conditional Update的第三方的key-value存储怎么办呢?这时,我们能够在L和D之间添加一个P作为代理,全部的CRUD操作都必须经过P。让P来进行条件检查。而实际的数据操作放在D。这样的方式实现了条件检查和数据操作的分离。但同一时候减少了性能,须要在P中添加cache,提升性能。因为P是D的唯一client;所以,P的cache管理是非常easy的,不必像多client情形操心缓存的失效。只是,实际上,据我所知redis和Amazon SimpleDB都已经有了Conditional Update的支持。

悲观锁和MVCC对照

上面介绍了悲观锁和MVCC的基本原理,可是对于它们分别适用于什么场合。不同的场合下两种机制优劣详细表如今什么地方还不是非常清楚。

这里我就对一些典型的应用场景进行简单的分析。须要注意的是以下的分析不针对分布式。悲观锁和MVCC两种机制在分布式系统、单数据库系统、甚至到内存变量各个层次都存在。

### 场景1:对读的响应速度要求高

有一类系统更新特别频繁。而且对读的响应速度要求非常高,如股票交易系统。

在悲观锁机制下。写会堵塞读。那么当有写操作时,读操作的响应速度就会受到影响;而MVCC不存在读写锁。读操作是不受不论什么堵塞的,所以读的响应速度会更快更稳定。

### 场景2:读远多于写

对于很多系统来讲,读操作的比例往往远大于写操作,特别是某些海量并发读的系统。在悲观锁机制下,当有写操作占用锁,就会有大量的读操作被堵塞,影响并发性能;而MVCC能够保持比較高且稳定的读并发能力。

### 场景3:写操作冲突频繁

假设系统中写操作的比例非常高,且冲突频繁,这时就须要细致评估。假设两个有冲突的业务L1和L2,它们在单独运行是分别耗时t1,t2。在悲观锁机制下,它们的总时间大约等于串行运行的时间:

T = t1 + t2

而在MVCC下,假设L1在L2之前更新。L2须要retry一次。它们的总时间大约等于L2运行两次的时间(这里假设L2的两次运行耗时相等。更好的情况是,假设第1次能缓存下部分有效结果,第二次运行L2耗时是可能减小的):

T’ = 2 * t2

这时关键是要评估retry的代价,假设retry的代价非常低,比方,对某个计数器递增。又或者第二次运行能够比第一次快非常多,这时採用MVCC机制就比較适合。反之。假设retry的代价非常大,比方,报表统计运算须要算几小时甚至一天那就应该採用锁机制避免retry。

从上面的分析。我们能够简单的得出这样的结论:对读的响应速度和并发性要求比較高的场景适合MVCC;而retry代价越大的场景越适合悲观锁机制。

总结

本文介绍了一种基于多版本号并发控制(MVCC)思想的Conditional Update解决分布式系统并发控制问题的方法。和基于悲观锁的方法相比,该方法避免了大粒度和长时间的锁定,能更好地适应对读的响应速度和并发性要求高的场景。

參考

多版本号并发控制(MVCC)在实际项目中的应用相关推荐

  1. 多版本号并发控制(MVCC)在分布式系统中的应用

    QQ群:289150599 问题 近期项目中遇到了一个分布式系统的并发控制问题.该问题能够抽象为:某分布式系统由一个数据中心D和若干业务处理中心L1,L2 ... Ln组成:D本质上是一个key-va ...

  2. java版本号管理_微服务项目中如何管理依赖版本号?

    本文是微服务项目代码组织形式三部曲中的第三篇,也是最后一篇,通过这三篇文章,相信大家对于如果组织微服务中的代码已经有了一个基本认知,前面两篇分别是: 第三篇相对来说要简单一些,本来没打算写,但是上周有 ...

  3. maven项目中父项目dependencyManagement和子项目dependencies的关系

    1.DepencyManagement应用场景 当我们的项目模块很多的时候,我们使用Maven管理项目非常方便,帮助我们管理构建.文档.报告.依赖.scms.发布.分发的方法.可以方便的编译代码.进行 ...

  4. vue项目中的小知识--快捷键-vue插件版本号--vscode插件等

    vue项目中的小知识--快捷键等 0 版本号 1 代码片段的获取: 2 vscode中一些常用扩展 3 进入另一个文件夹,返回上一级 4查看Vue的版本和Vue/CLI的版本 5 --save-dev ...

  5. 微服务项目中如何管理依赖版本号?

    本文是微服务项目代码组织形式三部曲中的第三篇,也是最后一篇,通过这三篇文章,相信大家对于如果组织微服务中的代码已经有了一个基本认知,前面两篇分别是: 微服务项目搭建,到底要不要聚合工程? 在微服务项目 ...

  6. 管理java版本号_微服务项目中如何管理依赖版本号?

    本文是微服务项目代码组织形式三部曲中的第三篇,也是最后一篇,通过这三篇文章,相信大家对于如果组织微服务中的代码已经有了一个基本认知,前面两篇分别是: 微服务项目搭建,到底要不要聚合工程? 在微服务项目 ...

  7. 实现mvcc_MySQL 的多版本并发控制(MVCC) 是干啥的?

    点击蓝色"架构文摘"关注我哟 加个"星标",每天上午 09:25,干货推送! 来源:https://segmentfault.com/a/11900000375 ...

  8. JAVA Web项目中所出现错误及解决方式合集(不断更新中)

    JAVA Web项目中所出现错误及解决方式合集 前言 一.几个或许会用到的软件下载官网 二.Eclipse的[preferences]下没有[sever]选项 三.Tomcat的安装路径找不到 四.T ...

  9. 在内网中使用maven_搭建私有maven仓库并在项目中使用

    这是一篇写给女朋友看的教程...前方高能,注意避让~ 1.私有maven仓库的搭建 搭建环境为阿里云ESC服务器,CentOS.确保服务器已经安装Jdk.然后我要手把手教你安装和启动nexus. St ...

最新文章

  1. Android App用MulticastSocket监听组播,为什么连接到不同路由、在不同手机上跑,有的能收到有的收不到...
  2. DCMTK:“内容映射资源”Content Mapping Resource中的各种CIDxxx和TIDxxx类的测试程序
  3. 爬了菊姐的两万条评论,竟发现菊粉都是这样的人!
  4. 修改oracle SGA,以提高oracle性能
  5. spring mvc 入门DispatcherServlet转发
  6. 数据库的操作 增删改查 mysql
  7. linux线程计算,有关Linux进程与线程数目计算的问题
  8. iOS开发之为什么更新UI都要放在主线程中
  9. 计算机主机清洁维护,电脑主机日常维护保养
  10. “九个字、一只手、专有云”,有孚网络的云上之路
  11. 1644年,紫禁城换了三任主人
  12. 10 个免费的高清图库网站,强烈推荐
  13. 博士毕业论文英文参考文献换行_写毕业论文时,需要掌握这10个最实用的Word技巧...
  14. OpenCV基础篇——图形图像旋转
  15. c语言指针p=*q,C语言中指针*p=*q与p=q有什么区别
  16. < C++11新特性(部分学习)>——《C++高阶》
  17. 企业直播的适用场景有哪些呢?
  18. 魔众相册系统 v1.2.0 系统内核升级,界面显示优化
  19. ADB 学习(3):adb uninstall 命令
  20. c语言3sum,3sum 4sum

热门文章

  1. DataSnap服务器从xe2升级到xe5报错的处理
  2. Mysql 简介和创建新的数据库
  3. System.Windows.Forms.ListView
  4. Visual Stdio 注册表相关路径
  5. Elasticsearch--进阶-进阶两种查询方式_request uri查询和query DSL查询---全文检索引擎ElasticSearch工作笔记009
  6. STM32工作笔记0039---认识电路图中的DS203,MS,L等
  7. 数据库工作笔记016---Redis、Memcache和MongoDB的区别
  8. Rpc远程调用框架的设计与实现(2)
  9. Rpc远程调用框架的设计与实现(1)
  10. JFrame小练习1