多版本号并发控制(MVCC)在分布式系统中的应用
QQ群:289150599
问题
近期项目中遇到了一个分布式系统的并发控制问题。该问题能够抽象为:某分布式系统由一个数据中心D和若干业务处理中心L1,L2 ... Ln组成;D本质上是一个key-value存储,它对外提供基于HTTP协议的CRUD操作接口。L的业务逻辑能够抽象为以下3个步骤:
- read: 依据keySet {k1, ... kn}从D获取keyValueSet {k1:v1, ... kn:vn}
- do: 依据keyValueSet进行业务处理,得到须要更新的数据集keyValueSet' {k1':v1', ... km':vm'} (注:读取的keySet和更新的keySet'可能不同)
- update: 把keyValueSet'更新到D (注:D保证在一次调用更新多个key的原子性)
在没有事务支持的情况下,多个L进行并发处理可能会导致数据一致性问题。比方,考虑L1和L2的例如以下运行顺序:
- L1从D读取key:123相应的值100
- L2从D读取key:123相应的100
- L1对值添加�1,将key:123更新为100 + 1
- L2对值添加�2,将key:123更新为100 + 2
假设L1和L2串行运行,key:123相应的值将为103,但上面并发运行中L1的运行效果全然被L2所覆盖,实际key:123所相应的值变成了102。
解决方式1:锁机制
为了让L的处理可串行化(Serializable),一种最直接的解决方式就是考虑为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解决分布式系统并发控制问题的方法。和锁机制相比,该方法避免了大粒度和长时间的锁定,能更好地适应对读的响应速度和并发性要求高的场景。
原文地址:http://www.kuqin.com/system-analysis/20120319/319108.html
多版本号并发控制(MVCC)在分布式系统中的应用的更多相关文章
- 转: 多版本并发控制(MVCC)在分布式系统中的应用 (from coolshell)
from: http://coolshell.cn/articles/6790.html 问题 最近项目中遇到了一个分布式系统的并发控制问题.该问题可以抽象为:某分布式系统由一个数据中心D和若干业务 ...
- 多版本号并发控制(MVCC)在实际项目中的应用
近期项目中遇到了一个分布式系统的并发控制问题.该问题能够抽象为:某分布式系统由一个数据中心D和若干业务处理中心L1,L2 - Ln组成:D本质上是一个key-value存储,它对外提供基于HTTP协议 ...
- 分布式系统中生成全局ID的总结与思考
世间万物,都有自己唯一的标识,比如人,每个人都有自己的指纹(白夜追凶给我科普的,同卵双胞胎DNA一样,但指纹不一样).又如中国人,每个中国人有自己的身份证.对于计算机,很多时候,也需要为每一份数据生成 ...
- 如何在高并发分布式系统中生成全局唯一Id
月整理出来,有兴趣的园友可以关注下我的博客. 分享原由,最近公司用到,并且在找最合适的方案,希望大家多参与讨论和提出新方案.我和我的小伙伴们也讨论了这个主题,我受益匪浅啊…… 博文示例: 1. ...
- 【分布式】Zookeeper在大型分布式系统中的应用
一.前言 上一篇博文讲解了Zookeeper的典型应用场景,在大数据时代,各种分布式系统层出不穷,其中,有很多系统都直接或间接使用了Zookeeper,用来解决诸如配置管理.分布式通知/协调.集群管理 ...
- 分布式系统中一些主要的副本更新策略——Dynamo/Cassandra/Riak同时采取了主从式更新的同步+异步类型,以及任意节点更新的策略。
分布式系统中一些主要的副本更新策略. 1.同时更新 类型A:没有任何协议,可能出现多个节点执行顺序交叉导致数据不一致情况. 类型B:通过一致性协议唯一确定不同更新操作的执行顺序,从而保证数据一致性 2 ...
- 如何在高并发分布式系统中生成全局唯一Id(转)
http://www.cnblogs.com/heyuquan/p/global-guid-identity-maxId.html 又一个多月没冒泡了,其实最近学了些东西,但是没有安排时间整理成博文, ...
- (转)如何在高并发分布式系统中生成全局唯一Id
又一个多月没冒泡了,其实最近学了些东西,但是没有安排时间整理成博文,后续再奉上.最近还写了一个发邮件的组件以及性能测试请看 <NET开发邮件发送功能的全面教程(含邮件组件源码)> ,还弄了 ...
- Twitter的分布式系统中ID生成方法——Snowflake
Twitter-Snowflake算法产生的背景相当简单,为了满足Twitter每秒上万条消息的请求,每条消息都必须分配一条唯一的id,这些id还需要一些大致的顺序(方便客户端排序),并且在分布式系统 ...
随机推荐
- PHP做支付宝即时到账需注意
注意:1按照人家的参数规则,规范填写参数列表:2商家信息填写正确:3然后提交走后注意此时告别了咱们的服务器,将在咱们服务器的订单信息提交到了支付宝服务器,然后支付宝服务器进行支付宝支付流程,当如果支付 ...
- 我用Emacs,后来转向Vim——Vim学习之Vim键盘图(绝对值得珍藏)
Emacs本来就比较臃肿,麻烦.当我发现Vim键盘图时,我就渐渐转向Vim,追随Unix/Linux哲学去了.. 我用了Emacs三个月,因为它的学习曲线没Vim陡,这点吸引了,我使用Linux才7. ...
- DBCONN
package Ulike_servlet; //将该类保存到com.tools包中import java.sql.Connection;import java.sql.DriverManager;i ...
- 实现OC与JS的交互
oc-->js stringByEvaluatingJavaScriptFromString,其参数是一NSString 字符串内容是js代码(这又可以是一个js函数.一句js代码或他们 ...
- block 数组排序
#import <Foundation/Foundation.h> //定义⼀一个block,返回值为BOOL,有两个NSString参数.实现:判 //断字符串是否相等. BOOL (^ ...
- Vim C/C++的一键编译
开始用Vim差不多有两个月的时间, 一开始用Makefile 编译一整个项目无压力, 但是当写到单个文件的时候, 编译就比较麻烦了, 每次都得 :w :!gcc -o 1.exe 1.c :!1 非常 ...
- 关于 Boolean 的转换
前端经常喜欢这样写 if else if(value) { //do something } javascript 能智能的把任何类型的 value 转换成 boolean 来进行 if 判断 转换是 ...
- c# 绘图常用对象和方法
//BitMap 位图,常用的方法, Save:主要方式有:(1)保存在图像文件里,可以指定格式[gif,bmp]:(2) 保存在流中,以指定格式[gif,bmp] //gra ...
- Unity NGUI Tween的用法
unity版本:4.5 NGUI版本:3.6.5 参考链接:http://www.colabug.com/thread-1029974-1-1.html,作者:COLABUG.COM 橘虞 htt ...
- Data 语义学(2)
四."继承"与 Data Member (1). 只有继承没有多态 先来看个例子 #include <iostream> using namespace std; cl ...