分布式系统原理-副本与一致性

 分布式系统原理系列目录

因为可靠性、吞吐量、低延时方面的需求,我们为数据设置了一些副本,即将一份数据的多个拷贝存放到不同地方。如果多个副本同时提供读服务,就引发了一致性问题。本文首先介绍了副本一致性的背景,随后对"一致性"的含义加以说明(ACID里的C、CAP里的C、以及本文的一致性他们分别是何含义),最后引出一致性级别,并对各个一致性级别作出了详细解释。相信大家读完后会对一致性有更深的认识

副本

副本简单来说就是一份数据的多份拷贝,用副本最直接的原因就是高可用(即使部分副本出现故障,数据可以不丢失、系统也能继续工作)。使用副本还有些其他的原因

高吞吐:扩展可以接受读请求的机器数量

低延迟:把数据放在离用户更近的地方,比如你的用户遍布全国,北京上海各一个数据中心,都接受读写请求,然后双向同步,用户的请求会被发送到离他更近的数据中心,这样子延迟更低

副本一致性问题

如果多个副本同时提供读服务,就引发了一致性问题。比如下图这样一个主从架构,初始时几个节点上的数据都是k=1,某一刻客户端写入k=2,集群给它返回成功,随后它读取k,它会拿到什么值?

这取决于系统提供的一致性模型。就是说同一份数据在多副本上可能有差异,作为存储系统,要如何协调这个差异,要让客户端看到怎样的数据,这个是副本一致性关注的问题

如果说只有master提供读写服务,答案好像是k=2?其实不一定,问题是matser挂了怎么办?会不会选一个k=1的节点为新的master?如果它提供的是强一致性,那k一定等于2,如果不是,那就说不准了

一致性的含义

在详细介绍副本一致性级别前,我们有必要说明下一致性。大家可能在很多地方都看到过一致性,比如ACID的C是一致性,CAP的C也是一致性,还有这里说的副本一致性,这些一致性是一个东西吗?很明显不是!一致性有很多细分类型,根据上下文的不同,定义有非常大的区别

ACID里的C

不知道大家在看ACID定义时,有没有感觉疑惑或者别扭。一个疑惑是:AID的定义是很清晰的,看了就能明白是什么意思,但是C的定义似乎不太清晰;另一个疑惑是:难道原子性隔离性持久性不是为了满足一致性吗?他们好像并不是正交的,放在一起合适吗?

那C的概念是什么呢,是对数据的一组特定约束必须始终成立。至于这个约束是什么,是由应用程序定义的,而不是数据库定义的。比如说在会计系统中,所有账户整体上必须借贷相抵,如果事务开始前借贷相抵,那事务后借贷也要相抵,这样才满足一致性。再比如你的系统记录了订单明细金额和订单总金额,那对你这个系统来说,一致性就是订单总金额要和所有订单明细金额之和保持一致

所以首先要明确的一点是,一致性是应用程序的属性而不是数据库的属性,那ACID是不是不应该放在一起? 大家都是技术人,比较严谨,我这样说大家可能不太愿意相信,毕竟ACID的叫法已经形成习惯。所以我引用了几个名人对ACID的评论:

加州大学伯克利分校的Joe Hellerstein(乔·海勒斯坦)教授,在他的论文中提到,ACID中的"C"是被扔进去凑缩写单词的

《数据密集型应用系统设计》这本书里提到: 原子性,隔离性和持久性是数据库的属性,而一致性(在ACID意义上)是应用程序的属性。应用可以依赖数据库的原子性和隔离属性来实现一致性,但这并不仅取决于数据库

周志明在他的《凤凰架构》里提到: 我自己对这种已经形成习惯的“ACID”的提法是不太认同的,因为这四种特性并不正交,A、I、D 是手段,C 是目的,完全是为了拼凑个单词缩写才弄到一块去,误导的弊端已经超过了易于传播的好处

所以我们可以得出两个结论,C并不是数据库的属性,是应用程序的属性 应用程序可以依赖于AID来保证C,但是只有AID并不一定保证C,如果你刻意地向数据库写入不一致的数据,数据库并没有什么办法阻止你制造出不一致的数据

CAP里的C

CAP里的C又是啥意思呢,副本一致性按照级别强弱分了很多级,CAP里的C指的就是副本一致性中的最强的线性一致性,这个后面还会细说

副本一致性级别

还是看下面这张图,因为网络有延迟,多个副本同步同一个数据必定存在延迟,写入k=2后,各副本同步到k=2的时间是不一样的。在这个期间,用户来读取,让他读到什么值,这取决于一致性级别:一致性按照强弱级别可以分为这样几类(有点多哦,不用全记住,但是线性一致性和最终一致性还是要重点关注下,因为他们在分布式系统中的出现频率很高)

线性一致性(也叫强一致性、原子一致性):只要⼀个用户成功完成写操作,所有用户必须能读取到刚刚写⼊的值。并且一旦某个用户读到某个值后,任何用户不会再读到比这个值更旧的值

因为线性一致性在分布式系统中出现的频率比较高,然后很多资料上都说的不太明白,所以我们详细说明下,理解了线性一致性其他一致性也就好理解了。线性一致性背后的思想很简单,就是让系统看起来就像只有一个副本,这个说起来简单实际还是很复杂的

咋一看线性一致性的定义,可能会认为只要同步流程把数据写入到所有节点才返回客户端写入成功,就能满足,先不说这个搞法可用性和性能低的问题哦,就算你这样搞了,其实也没法保证。线性一致性有两个约束

约束一:一旦用户写入成功,后续任何用户都应读到最新写入成功的值

如上图,开始时x=0,然后clientC开始写入x=1,写入x=1是有一个过程的,在此期间,其他客户可能会读到0或者1,这无所谓,但是一旦C的写入完成了,这之后随便哪个用户去读,就一定要读到最新的值。如果说同步写入所有节点才返回写入成功,那是可以满足这个约束的,因为在系统返回写入成功时,所有节点都有最新值了,后续读不管到哪台机器都可以读到最新值

但是第二个约束就满足不了,第二个约束是:任何一个节点读到新的值后,所有后续读取也必须返回新值

因为多个节点的写入一定是有时差的,在这中间一旦一个节点写入了,其他节点还没写入,这时候有客户端读到了新的值,后面就不允许再读到旧的值,这个要求是很高的,因为读请求可能会路由到任一个副本。如上图,在clientC的写入过程中,第一个客户端读到啥是无所谓的,但是一旦有客户端读到新的值(1)了,后面就不能读到更旧的

有一点要特别说明的是,这里我所说的线性一致性是严格的线性一致性,很多资料上在定义线性一致性的时候其实只说了第一点约束(一旦用户写入成功,后续任何用户都应读到最新写入成功的值),有些分布式算法或者系统呢做到了第一点他也声称它满足线性一致性,如果你看到了,也不要感觉奇怪,这些其实就是认为严格的线性一致性在工程中太苛刻了,所以就认为只要满足了约束1,就算是线性一致性,但是我们也要知道严格意义上的线性一致性是什么样的

顺序一致性(也叫单调一致性):用户一旦读到某个值后,这个用户不会再读到比这个值更旧的值。弱于强一致性、但非常实用。 因为通常来说,用户只关心从己方视角观察到的一致性,而不会关注其他用户的一致性情况。比如说你发了个朋友圈,你立马就能看到,但是你的好友可能要等会才能看到,就是其他人可能会晚点看到,但是你能立马看到

会话一致性:顺序一致性关注单个用户所有会话级别的一致性,会话一致性只关注单个用户单个会话级别的一致性,或者我们简单理解为一个事务中的一致性,单个事务中看到的数据不会倒退,但其他事务没法保证

因果一致性:是一个比较弱的一致性,就是说发生了两个有因果关系的读或者写操作,之后用户读取时看到的数据也一定满足这个因果关系

上面张图想表达的意思是,如果两个操作没有因果关系,读取到的值没有任何约束,这里XY初始值是0P1写入x=5P2写入y=10,这两个操作虽然在时间上是P1先发生P2后发生,但是没有因果关系。P4在读取时,读到了y=10之后,又读到了x=0而没有读到x=5是没有问题的,因为写入x=5和写入y=10没有因果关系

 

再看下这张图,XY初始值还是0P2这边发生了两个因果关系的操作,就是先读到了x=5,然后写入了y=10,这两个操作有因果关系,写入y=10 happen after 读取x=5然后P4读到y=10后,又读到x=0就不符合前面的因果关系了。因为能读到y=10,说明写入y=10这个操作已经发生过了,由于她两有因果关系,所以读到x=5也已经发生过了,那就不应该读到比5旧的值

再举个生活中的例子:你的微信好友把你删了,然后发了个朋友圈:刚清理一波好友。微信如果要保证因果一致性,你就不可能先看到那个朋友圈,然后再给他发个消息发现你已经不是他好友了,对不对?因为删除你和发朋友圈是有因果关系的,你看到朋友圈就违反了这个因果关系

补充说明:不要把本文的一致性和事务隔离级别搞混了,因为他其实都在说读写要读到什么样的值,隔离级别关注的是并发事务的情况下,事务间共享的状态要怎么处理的问题(A事务会不会读到B事务还没提交的数据,A事务会不会两次读到不一样的数据等等),我们这里说的一致性级别关注的是存储系统在多个副本间数据同步有时差、或者有些节点有故障的情况下,给客户端展示什么样的视图的问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值