两种数据一致性?
之前一致性这个概念听得多了,有时候指系统内各节点保持一致,有时候又指不同系统的协同合作一致。到底含义如何,困扰了我很长的时间,现在我将其定义为两个概念:
领域内的数据一致性
一般我们读到的文章谈到说「分布式一致性」都包含在这个概念里面,大名鼎鼎的 Paxos 协议、Raft 协议等都是在研究这个,也称为共识算法。更基础一点的模型是 2PC、3PC、TCC模型等等。这是一个很大的命题,也相当复杂,但是可以简化为寄存器模型
:
SET x = 1
GET x
EXPECT x == 1
就这么简单!不管这个寄存器
背后有多少个节点、集群、leader、follower,它对外就是一个「存东西的地方」。想想我们业务中使用的 etcd 是不是就干这个事情的。
领域间的数据一致性
我们现在后端架构基本都是微服务实现。尽管微服务带来了迭代效率、资源隔离等好处,但是也带来了很多新的技术问题。「巨服务」框架下通过关系型数据库的单体事务保证的一致性,现在在不同系统的不同数据库存储,无法通过老办法保证一致。
实际上这是「分布式事务」在研究的事情。也有一种说法是将其定义为「业务一致性」。
下面我们将本文讨论的范围缩小到领域内的数据一致性这个范畴内。
几种容易混淆的一致性定义
好几个理论或者模型中都提到了一致性 Consistency,到底作何区别?
关系型数据库事务 ACID 中的 Consistency
数据库的事务机制是通过 ACID 实现的,复习一下 ACID 的定义:
- Atomic 原子性: 一个事务的所有系列操作步骤被看成是一个动作,所有的步骤要么全部完成要么一个也不会完成,如果事务过程中任何一点失败,将要被改变的数据库记录就不会被真正被改变。
- Consistency 一致性: 在事务开始或结束时,数据库应该在一致状态。也就是说,通过各种途径包括外键约束等任何写入数据库的数据都是有效的,不能发生表与表之间存在外键约束,但是有数据却违背这种约束性。所有改变数据库数据的动作事务必须完成,没有事务会创建一个无效数据状态.
- Isolated 隔离性: 主要用于实现并发控制, 隔离能够确保并发执行的事务能够顺序一个接一个执行,通过隔离,一个未完成事务不会影响另外一个未完成事务。
- Durable 持久性: 一旦一个事务被提交,它应该持久保存,不会因为和其他操作冲突而取消这个事务。很多人认为这意味着事务是持久在磁盘上,但是规范没有特别定义这点。
这里的一致性是为了保证系统数据的保护性
和不变性
,不论在何种并发的情况下。以转账案例为例,假设有五个账户,每个账户余额是100元,那么五个账户总额是500元,如果在这个5个账户之间同时发生多个转账,无论并发多少个,比如在A与B账户之间转账5元,在C与D账户之间转账10元,在B与E之间转账15元,五个账户总额也应该还是500元,这就是保护性
和不变性
,这是一种「一致的状态」。
CAP 理论中的 Consistency
复习下 CAP 理论的定义:
- Consistency 一致性,指数据在多个副本之间能否保持一致的特性。在一致性的需求下,当一个系统在数据一致的状态下执行更新操作后,应该保证系统的数据仍然处于一致的状态。
- Availability 可用性,指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。这里的重点是"有限时间内"和"返回结果"。
- Partition tolerance 分区容错性,约束了一个分布式系统具有如下特性:分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。
CAP 理论认为,一个分布式系统中的 CAP三种属性,只能同时满足其中两种。
CAP 理论中的 Consistency 是分布式领域下的一致性表述,为了保证领域中不同节点的数据是相同的
,或者说服务器之间数据复制整齐一致了。可以看出和上面 ACID 的一致性完全是不同的概念。还是以银行账户举例,我在支付宝存了100万☺️☺️☺️,那么支付宝杭州机房里面的数据库存储的我的账户为100万,同时支付宝在北京的机房也必须是100万!这是 CAP 理论的 Consistency 保证的。
BASE 理论中的 Eventual consistency
BASE 理论是:
- Basic Availability 基本业务可用性,接受响应时间变慢或者非关键模块宕机等意外情况。
- Soft state 柔性状态(软状态),指的是允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性。
- Eventual consistency 最终一致性,如字面含义。
BASE 理论 是对 CAP 的妥协。对于普遍的互联网场景而言,保证可用性的要求要高于一致性(金融支付类的除外),故一致性的约束降级为最终一致性。考虑到用户体验,这个最终一致的时间窗口,要尽可能的对用户透明。最常见的实现最终一致性的系统是DNS(域名系统Domain Name System)。一个域名更新操作根据配置的形式被分发出去,并结合有过期机制的缓存;最终所有的客户端可以观察到这个更新。
另外,和 ACID 代表的刚性事务相比,BASE 代表的柔性事务面向的是大型高可用可扩展的分布式系统,通过牺牲强一致性来获得可用性。这两种模型分别是向一致性和可用性这两个维度的靠拢。有趣的是,ACID 和 BASE 恰巧分别在化学里面是酸和碱的含义。
下面我们将再本文讨论的范围缩小到分布式一致性这个范畴内。
分布式系统的一致性的不同层级
我们来看一下一致性的不同程度的约束。
弱一致性
和强一致性相对,系统并不保证连续进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到。但会尽可能保证在某个时间级别(比如秒级别)之后,可以让数据达到一致性状态。
最终一致性是弱一致性的特定形式
《最终一致性》一文提到最终一致性模型有很多变体是需要重点关注的:
- 原因一致性。如果进程A已经与进程B建立了联系更新了一个数据项,进程B发出的一个后续访问将会返回更新后的值,并且这次写入操作确认已经替代了上一次写入。进程C的访问就与进程A没有因果关系了,而是一个普通的最终一致性的规则的问题。
- 自读写一致性。这是一个非常重要的模型,进程A在完成一次数据项更新后总是能访问到更新后的数据,永远看不到旧数值。它是原因一致性模型的一个特例。
- 会话一致性。这是前一个模型,当一个进程在会话上下文中访问存储系统时,的实践。只要会话还存在,系统就应该保证自我读写一致性。如果会话由于某种原因关闭了,那么需要建立一个新会话,这个会话并不会记忆上一会话。
- 单读一致性。如果一个进程已经检测到对象的特殊值,那么后续的访问都不会返回任何历史值。
- 单写一致性。在这样一个例子中,系统保证同一进程的写入是连续的,要建造不能保证这个层次一致性的系统是有一定难度的。
图自《大数据日知录:架构与算法》
强一致性
强一致性也就是指一旦有写操作写入任何一个服务器,立即在其他服务器之间同步复制新的数据,这样任何服务器上任何读操作总是能看到最近写入的新数据。
来看一下数学模型:
N:存储数据冗余副本的节点数
W:在更新结束前,需要发出更新到达信号的冗余副本数
R:一个数据对象进行读操作需要建立的冗余副本的数量
如果 W+R>N,那么读和写的场景总是有交集,这种情况就是强一致性。相反,如果 W+R<=N,则是弱/最终一致性。以关系型数据库的复制方案举例:
- 在同步复制的方案中,N=2,W=2,R=1。无论客户端读哪个冗余副本,都可以获得一个同步的返回值。
- 在允许读取副本异步复制系统中,N=2,W=1,R=1。在这个例子中 R+W=N,一致性得不到保证。
线性一致性是强一致性的一种表述
CAP 理论中要求的一致性正是线性一致性(linearizable consistency)。线性化的意思是,一旦写完成,其后的所有其他读操作应该返回写入的值或者最近写入的值,一旦读返回一个特定的值,所有其后读取应该返回的也是这个值或最近写入的值。另一种描述是:
如果 B 操作在成功完成 A 操作之后,那么整个系统对 B 操作来说必须表现为 A 操作已经完成了或者更新的状态。
让我们来看一个例子。在这个例子中的系统并不是可线性化的。
图自《这可能是我看过最通俗也是最深刻的CAP理论》
这张图展示了 Alice 还有 Bob, 他们在同一个房间,都在用他们的手机查询 2014 年世界杯的决赛结果。
就在最终结果刚发布之后,Alice 刷新了页面,看到了宣布冠军的消息,而且很兴奋地告诉了 Bob。
Bob 马上也重新加载了他手机上的页面,但是他的请求被送到了一个数据库的拷贝,还没有拿到最新的数据,结果他的手机上显示决赛还正在进行。
如果 Alice 和 Bob 同时刷新,拿到了不一样的结果,并不会太让人意外。因为他们不知道具体服务器到底是先处理了他们中哪一个请求。
但是 Bob 知道他刷新页面是在 Alice 告诉了他最终结果之后的。所以他预期他查询的结果一定比 Alice 的更新。事实是,他却拿到了旧的结果。这就违反了可线性化。