一致性的重要性
分布式领域CAP理论告诉我们,任何一个分布式系统都无法同时满足Consistency(一致性),Availability(可用性), Partition tolerance(分区容错性) 这三个基本需求。最多只能满足其中两项。 但是,一个分布式系统无论在CAP三者之间如何权衡,都无法彻底放弃一致性(Consistency),如果真的放弃一致性,那么就说明这个系统中的数据根本不可信,数据也就没有意义,那么这个系统也就没有任何价值可言。所以,无论如何,分布式系统的一致性问题都需要重点关注。
注:这里先简单提一下,由于一个分布式系统不可能放弃一致性,那么为什么有的架构师还说在某些场景中可以牺牲一致性呢?通常这里说的放弃一致性指的是放弃数据的强一致性(后文介绍什么是强一致性)。
通常情况下,我们所说的分布式一致性问题通常指的是数据一致性问题。那么我们就先来了解一下什么是数据一致性。
数据一致性
数据一致性其实就是数据库系统中的概念。我们可以简单的把一致性理解为正确性或者完整性,那么数据一致性通常指关联数据之间的逻辑关系是否正确和完整。我们知道,在数据库系统中通常用事务(访问并可能更新数据库中各种数据项的一个程序执行单元)来保证数据的一致性和完整性。而在分布式系统中,数据一致性往往指的是由于数据的复制,不同数据节点中的数据内容是否完整并且相同。
比如在集中式系统中,有一些关键的配置信息,可以直接保存在服务器的内存中,但是在分布式系统中,如何保存这些配置信息,又如何保证所有机器上的配置信息都保持一致,又如何保证修改一个配置能够把这次修改同步到所有机器中呢?
再比如,在集中式系统中,进行一个同步操作要写同一个数据的时候,可以直接使用事务+锁来管理保证数据的ACID。但是,在分布式系统中如何保证多台机器不会同时写同一条数据呢?
除了上面提到的同一个数据的一致性,还有一种情况也可以叫做数据的一致性:比如我们在电商网站下单,需要经历扣减库存、扣减红包、扣减折扣券等一系列操作。如果库存库存扣减成功,但是红包和折扣券扣减失败的话,也可以说是数据没有保证一致性。
如何保证数据的一致性,是分布式系统中必须面对的问题。
为什么会有数据一致性问题
在初识分布式系统中我们介绍过,虽然分布式系统有着诸多优点,但是由于采用多机器进行分布式部署的方式提供服务,必然存在着数据的复制(如数据库的异地容灾,多地部署)。分布式系统的数据复制需求主要来源于以下两个原因:
可用性: 将数据复制到分布式部署的多台机器中,可以消除单点故障。防止系统由于某台(些)机器宕机导致的不可用。
性能: 通过负载均衡技术,能够让分布在不同地方的数据副本全都对外提供服务。有效提高系统性能。
分布式系统为了提升可用性和性能,会通过复制技术来进行数据同步。复制机制的目的是为了保证数据的一致性。但是数据复制面临的主要难题也是如何保证多个副本之间的数据一致性。在分布式系统引入复制机制后,不同的数据节点之间由于网络延时等原因很容易产生数据不一致的情况。
如果上面提到的数据复制场景你不是很熟悉的话,下面这个例子你肯定遇到过。就是现在很多网站都是微服务化的,一个网站被垂直拆分成多个功能模块。各模块之间独立部署。模块间通过RPC或者HTTP交互。由于这种RPC或者HTTP的交互可能存在网络延迟导致超时的情况。甚至被调用方也有可能执行出错等情况,这时候就可能导致数据不一致。
比如下单操作要依次扣减红包、扣减折扣券、扣减库存。在下单应用的执行过程中,调用红包系统扣减红包成功了,但是再调用折扣券系统扣减折扣券的时候网络超时了。这时候下单应用根本不知道折扣券系统到底有没有执行成功。这时候他就需要一些机制来决定是要回滚红包的扣减,还是继续执行库存的扣减。这种机制,其实就是数据一致性的解决方案了。
由于应用分布式部署,就无法通过数据库事务保证多个写操作的原子性。一旦某个操作失败,其他操作如果不回滚的话就会发生数据不一致问题。
因此,如何能既保证数据一致性,又保证系统的性能,是每一个分布式系统都需要重点考虑和权衡的。一致性模型可以在做这些权衡的时候给我们很多借鉴和思考。
一致性模型
强一致性
当更新操作完成之后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。这种是对用户最友好的,就是用户上一次写什么,下一次就保证能读到什么。
但是这种实现对性能影响较大,因为这意味着,只要上次的操作没有处理完,就不能让用户读取数据。
弱一致性
系统并不保证进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到。但会尽可能保证在某个时间级别(比如秒级别)之后,可以让数据达到一致性状态。
最终一致性
弱一致性的特定形式。系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。在没有故障发生的前提下,不一致窗口的时间主要受通信延迟,系统负载和复制副本的个数影响。DNS是一个典型的最终一致性系统。
最终一致性模型的变种
因果一致性:如果A进程在更新之后向B进程通知更新的完成,那么B的访问操作将会返回更新的值。如果没有因果关系的C进程将会遵循最终一致性的规则。
读己所写一致性:因果一致性的特定形式。一个进程总可以读到自己更新的数据。
会话一致性:读己所写一致性的特定形式。进程在访问存储系统同一个会话内,系统保证该进程读己之所写。
单调读一致性:如果一个进程已经读取到一个特定值,那么该进程不会读取到该值以前的任何值。
单调写一致性:系统保证对同一个进程的写操作串行化。
上述最终一致性的不同方式可以进行组合,例如单调读一致性和读己之所写一致性就可以组合实现。并且从实践的角度来看,这两者的组合,读取自己更新的数据,和一旦读取到最新的版本不会再读取旧版本,对于此架构上的程序开发来说,会少很多额外的烦恼。
为了解决分布式的一致性问题,在长期的研究探索过程中,涌现出了一大批经典的一致性协议和算法,其中比较著名的有二阶段提交协议,三阶段提交协议和Paxos算法。