鲁春利的工作笔记,谁说程序员不能有文艺范?
以下内容转载自:Eric雪菲的Quorum机制漫谈和junhua3344的Quorum机制
Quorum这个词的念作“科瑞姆”,字面意思是选举法定人数,对于自小成长于五道杠的环境,缺乏选举参与经验的各位小伙伴,即使把这个词翻译成中文也不太明白吧?其实是这样,腐朽的西方资本主义社会在举行选举时,通常要求参与人数必须达到额定的数量,才能成为一个法定有效的选举。这个额定的人数就是Quorum,这是原始的涵义。
而在计算机的世界里,除了常见的镜像,Raid,cluster外, Quorum机制也是常见的容错技术,经常用在分布式系统的冗余设计方案中。据说微信后台也用,这样是不是突然觉得亲切很多?
分布式系统的设计中会涉及到许多的协议、机制用来解决可靠性问题、数据一致性问题等,Quorum 机制就是其中的一种。我们通过分布式系统中的读写模型来简单介绍它。
分布式系统中的读写模型
由于网络异常、宕机等将导致节点不能正常工作,特别是在节点(指代一台服务器、存储设备等)数量很大的时候,出现异常状况的节点几乎是肯定存在的。为了保证系统的正常运行,能够提供可靠的服务,分布式系统中对于数据的存储采用多份数据副本(注:这里的副本并非只用来备份,它可参与提供系统服务)来保证可靠性,副本存放在不同节点上,读写时需要对多个副本进行操作,也就是其中一个节点上读取数据失败了那么可以转向另外一个存有相同数据副本的节点读取返回给用户。这个过程对于用户来说是透明的。那么随之而来的就会带来数据的副本数据的不一致性,例如:用户提交一次修改后,那么原先保存的副本显然就与当前数据不一致了。
为了解决这个问题,按照自然而然的想法,一个写操作会需要写多个副本,但是读只要读到一个副本即可。这种思路猛听起来很合理,有一个学名叫“读一写全协议”(read only write all,ROWA),就是在用户提交修改操作后,系统确保存储的数据所有的副本全部完成更新后,再告诉用户操作成功;而读取数据的时候只需要查询其中的一个副本数据返回给用户就行了。但若真依此实现,每一次写都必须刷新所有副本,才能避免产生一致性问题,在很少对存储的数据进行修改的情形下(例如存档历史数据供以后分析),这种解决方案很好。如遇到经常需要修改的情形,写操作时延时现象就很明显,加上并发或者连续的执行,写操作就显得太“重”了,尤其是和读操作相比较,一头轻一头重,负载明显不平衡,更加显得不协调;而且写的respond time变长其实也会间接影响到读,因为不写好没法读,最终拖慢整体性能。
实质,这是由于 Write 和 Read 负载不均衡所致,Read 很轻松,Write 深表压力!
那么有没有一种方案能够不需要更新完全部的数据,但又保证返回给用户的是有效数据的解决方案呢?Quorum机制便是一种选择。
从小学的抽屉原理说起
为什么从抽屉原理说起?一来大家对这个比较熟悉,二来它与Quorum机制有异曲同工的地方。回顾抽屉原理,2个抽屉每个抽屉最多容纳2个苹果,现在有3个苹果无论怎么放,其中的一个抽屉里面会有2个苹果。那么我们把抽屉原理变变型,2个抽屉一个放了2个红苹果,另一个放了2个青苹果,我们取出3个苹果,无论怎么取至少有1个是红苹果,这个理解起来也很简单。我们把红苹果看成更新了的有效数据,青苹果看成未更新的无效数据。便可以看出来,不需要更新全部数据(并非全部是红苹果)我们就可以得到有效数据,当然我们需要读取多个副本完成(取出多个苹果)。这就是Quorum机制的原型,其实质是将Write All 的负载均衡到 Read Only 上。
Quorum机制
回到文章的开头,我们来看看是怎么运用Quorum机制来解决读写模型中读写的负载均衡。其实,关键的是更新多少个数据副本后,使得读取时总能读到有效数据?回想我们的的红苹果,假设总共有 N 个数据副本,其中 k 个已经更新,N-k 个未更新的,那么我们任意读取 N-k+1 个数据的时候就必定至少有1个是属于更新了的k个里面的,也就是 Quorum 的交集,我们只需比较 读取的 N-k+1 中版本最高的那个数据返回给用户就可以得到最新更新的数据了。
那么对于写模型呢?我也只需要完成 k个副本的更新后,就可以告诉用户操作完成而不需要 Write All 了,当然告诉完用户完成操作后,系统内部还是会慢慢的把剩余的副本更新,这对于用户是透明的。可以看到,我们把 Write 身上的部分负载转移到了Read上,Read读取多个副本,使得Write不会过于劳累,不好的是弱化了分布式系统中的数据一致性。至于转移多少负载比较合适,这个需要根据分布式系统的具体需求中对数据一致性的要求。不过,CAP 理论告诉我们没有完美的方案。
基于Quorum机制的强一致性存储方案
在线上生成环境用一台服务器提供数据服务时,我们会担心这台服务器停机,造成服务不可用或数据丢失,通常我们对数据进行冗余存储,保障数据服务的高可用性。
但加入更多的机器,会带来数据一致性的问题,下面我们来了解一个基于Quorum机制的强一致性存储方案。
三机Quorum机制
Quorum即多数派原则,与投票选举的方式类似,被多数同意的一项操作即可获得通过。
本文讨论基于三机的Quorum机制,我们称这三台机为A机、B机、C机。其中A、B机用于数据存储,称为数据机。当写请求落到A机时,是否能真正写入数据,至少需要获得B机或C机一方的认可;当请求同时落到A、B机,这时需要第三方仲裁,这就是C机的作用。
读写操作需要在最新的一份数据上进行,为标识哪一台数据机上的数据为最新,我们引入版本号,每一条数据记录,不单存储数据本身,同时存储本机和另一台数据机的相应版本信息:
当各机上的verA = verB时,认为各机数据一致并且都是最新数据;当verA > verB时,则认为A机上的数据最新。实际应用中通过提议、协商、同步和广播四个过程维护数据与版本信息,以数据写入A机为例,我们来看具体的写入过程。
提议与协商
对key写请求落到A机,将经历以下流程:
首先A检查key对应的版本号,当满足verA >= verB时,数据写入A机的请求获得自己的通过,并向B机、C机发送请求询问是否可以进行写入(提议)
B机(C机)检查本地key对应的版本号,也满足verA >= verB时,告知A机可进行写入(协商)
A机获得B机或C机一方通过,即可进行数据写入,同时提升各机key相应的版本号verA
经过以上步骤,A机上key相应的数据B机上的不一致,并且各机上verA > verB。进一步地,我们通过同步与广播更新B机、C机信息。
同步与广播
A机数据更新后,将对B机同步数据;B机完成数据更新后会提升本机版本号,还会将该变动同步给A机、C机,具体过程如下:
B检查本地key对应的版本号,当满足verA > verB时,允许同步数据写入本地、提升verB;当verA = verB时,交由C机仲裁,C.verA > C.verB时,同样允许数据写入本地、提升verB,否则返回错误(同步)
更新成功后,B广播自己的版本号,A.verB、C.verB都将得到提升,之后三机恢复平衡状态(广播)
同步与广播用图示表示如下:
读流程
三机Quorum下读流程较写流程简单,但也有一个问询过程,具体如下:
读请求落到A机时,如果A.verA < B.verB,则返回错误
如果A.verA >= B.verB,还需要询问B、C机,是否同意verA为最新,为最新则返回数据
小结
三机Quorum提供了两数据机存储冗余的实现方案,一次写操作后,读将获取到最新数据,因而它符合强一致性。Quorum机制也可以进行扩展,例如3数据机+5版本机,以满足更多份数据冗余存储需求。