问题的提出
在计算机科学领域,分布式一致性问题是一个相当重要,且被广泛探索与论证的问题,通常存在于诸如分布式文件系统、缓存系统和数据库等大型分布式存储系统中。
什么是分布式一致性?分布式一致性分为哪些类型?分布式系统达到一致性后将会是一个什么样的状态?如果失去了一致性约束,分布式系统是否还可以依赖?如果一味地追求一致性,对系统的整体架构和性能又有多大影响?这一系列的问题,似乎都没有iyge严格意义上准确的定义和答案。
终端用户
IT技术的发展,让我们受益无穷,从日常生活的超市收银,到高端精细的火箭发射,现代社会中几乎所有行业,都离不开计算机技术的支持。
尽管计算机工程师们创造出了很多高科技的计算机产品来解决我们日常碰到的问题,但用户只会倾向于选择一些易用、好用的产品,哪些难以使用的计算机产品最终都会被淘汰——这种易用性,其实就是用户体验的一部分。
计算机产品的用户体验,可以分为便捷性、安全性和稳定性等方面。我们来看一下计算机产品的终端用户是谁,他们的需求又是什么。
火车站售票
假如说我们的终端用户是一位经常做火车的旅行家,通常他是去车站的售票处购买车票,然后拿着车票去检票口,再坐着火车,开始一段美好的旅行——一切似乎都是那么和谐。想象一下,如果他选择的目的地是杭州,而某一趟开往杭州的火车只剩下最后一张车票了,可能在同一时刻,不同售票窗口的另一位乘客也购买了同一张车票。假如说售票系统没有进行一致性保障,两人都购票成功了。而在检票口检票的时候,其中一位乘客会被告知他的车票无效——当然,现代的中国铁路售票系统已经很少出现这样的问题了,但在这个例子中,我们可以看出,终端用户对于我们的系统的需求非常简单:
“请售票给我,如果没有余票了,请在售票的时候就告诉我票是无效票的。”
这就对购票系统提出了严格的一致性要求——系统的数据(在本例中指的就是那趟开往杭州的火车的余票数),无论在哪个售票窗口,每时每刻都必须是准确无误的!
银行转账
假如说我们的终端用户是一名刚毕业的大学生,通常在拿到第一个月工资之后,都会选择向家里汇款。当他来到银行柜台,完成转账操作后,银行的柜台服务员会友善的提醒他:“您的转账将在N个工作日后到账!”,此时这名毕业生有一些沮丧,会对那名柜台服务员叮嘱:“好吧,多久没关系,钱不要少就行了!”——这也成为了几乎所有的用户对于现代银行系统最基本的需求。
网上购物
假如说我们的终端用户是一名网上购物狂,当他看到一件库存量为5的心仪商品,会迅速地确认购买,写下收货地址,然后下单——然而,在下单的那个瞬间,系统可能会告知该用户:“库存量不足!”此时,绝大部分的消费者往往都会抱怨自己动作太慢,使得心爱的商品被其他人抢走了!
但其实有过网购系统开发经验的工程师一定明白,在商品详情页面上显示的那个库存量,通常不是该商品的真实库存量,只有在真正下单购买的时候,系统才会检查该商品的真实库存量。但是,谁在意呢?
在上面三个例子中,我们的终端用户在使用不同的计算机产品时对于数据一致性的需求是不一样的:
- 有些系统,既要快速的响应用户,同时还要保证系统的数据对于任意客户端都是真实可靠的,就像火车站的售票系统。
- 还有些系统,需要为用户保证绝对可靠的数据安全,虽然在数据一致性上存在延时,但最终务必保证严格的一致,就像银行的转账系统。
- 另外的一些系统,虽然向用户展示了一些可以说是“错误”的数据,但是在整个系统使用过程中,一定会在某一个流程上对系统数据进行准确无误的检查,从而避免用户发生不必要的损失,就像网购系统。
更新的并发性
在计算机发展的早期阶段,受到底层硬件技术的制约,同时也是由于人们对于计算机系统的实际使用需求比较简单,因此很多上层的应用程序架构都是单线程模型的。以C语言为例,其诞生于上世纪70年代,当时几乎所有使用C语言开发的应用程序都是单线程的。从现在来看,单线程应用程序虽然在运行效率上无法和后来的多线程应用程序相比,但是在编程模型上相对简单,因此能够避免多线程程序中出现的不少并发问题。
随着计算机底层硬件技术和现代操作系统的不断发展,多线程技术开始被越来越多的引入到计算机编程模型之中,并对现代计算机应用程序的整体架构起到了至关重要的作用。
多线程的引入,为因够用程序带来性能上的卓越提升,同时也带来了一个最大的副作用,那就是并发。《深入理解计算机系统》一书对并发进行了如下定义:如果逻辑控制流在时间上重叠,那么他们就是并发的。这里提到的逻辑控制流,通俗地讲,就是一次程序操作,比如读取或更新内存中变量的值。
分布式一致性问题
在分布式系统中另一个需要解决的重要问题就是数据的复制。在我们日常的开发经验中,相信很多开发人员都碰到过这样的问题:假设客户端C1将系统中的一个值K由V1更新为V2,但客户端C2无法立即读取到K的最新值,需要在一段时间之后才能读取到。读者可能也已经猜到了,上面这个例子就是常见的数据库之间复制的延时问题。
分布式系统对于数据的复制需求一般都来自于以下两个原因。
- 为了增加系统的可用性,以防止单点故障引起的系统不可用。
- 提高系统的整体性能,通过负载均衡技术,能够让分布在不同地方的数据副本都能够为用户提供服务。
数据复制在可用性和性能方面给分布式系统带来的巨大好处是不言而喻的,然而数据复制所带来的一致性挑战,也是每一个系统研发人员不得不面对的。
所谓的分布式一致性问题,是指在分布式环境中引入数据复制机制后,不同数据节点间可能出现的,并无法依靠计算机应用程序自身解决的数据不一致情况。简单的讲,数据一致性就是指在对一个副本数据进行更新的同时,必须确保也能够更新其他的副本,否则不同副本之间的数据将不再一致。
那怎么来解决这个问题呢?顺着上面提到的复制延时问题,很快就有人想到了一种解决办法,那就是:
“既然是由于延时引起的问题,那我可以将写入的动作阻塞,直到数据复制完成后,才完成写入工作。”
没错,这似乎能解决问题,而且有一些系统的架构也确实直接使用了这个思路。但这个思路在解决一致性问题的同时,又带来了新的问题:写入的性能。如果你的应用场景有非常多的写请求,那么使用这个思路之后,后续的写请求都将会阻塞在前一个请求的写操作上,导致系统整理性能急剧下降。
总的来讲,我们无法找到一种能够满足分布式系统所有系统属性的分布式一致性解决方案。因此,如何既保证数据的一致性,同时又不影响系统运行的性能,是每个分布式系统都需要重点考虑和权衡的。于是,一致性级别由此诞生。
- 强一致性
这种一致性级别是最符合用户直觉的,他要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响比较大。
- 弱一致性
这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不具体承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态。弱一致性还可以再进行细分:
- 会话一致性:该一致性级别只保证对于写入的值,在同一个客户端会话中可以读到一致的值,但其他会话不能保证。
- 用户一致性:该一致性级别只保证对于写入的值,在同一个用户会话中可以读到一致的值,但其他用户不能保证。
- 最终一致性
最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为他是弱一致性中非常重要的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型。