1 概念
1.1 什么是一致性
在分布式系统中,一致性(Consistency)是指多副本(Replications)问题中的数据一致性。
1.2 一致性的种类
- 事务一致性
- 数据一致性
本文主要讨论数据一致性(并非事务的一致性)
1.3 导致一致性出现的原因
数据的分布式存储是导致出现一致性的唯一原因
2 强一致性 与 弱一致性
2.1 数据一致性的种类
- 强一致性(线性一致性):即数据的复制是同步的;
- 弱一致性:即数据的复制是异步的;数据更新后,如果能容忍后续的访问只能访问到部分或者全部访问不到,则是弱一致性。
- 最终一致性、顺序一致性等都属于弱一致性。
2.2 强一致性两个要求
- 任何一次读都能读到某个数据的最近一次写的数据。
- 系统中的所有进程,看到的操作顺序,都和全局时钟下的顺序一致。(能读到的最新值顺序与更新时的顺序一致)
因此,强一致性也叫做线性一致性
简言之,在任意时刻,所有节点中的数据是一样的。
在实践中,我们通常使一个从库是同步的,而其他的则是异步的。如果这个同步的从库出现问题,则使另一个异步从库同步。这可以确保永远有两个节点拥有完整数据:主库和同步从库。 这种配置称为半同步。
3 顺序一致性
3.1 顺序一致性要求
- 任何一次读都能读到某个数据的最近一次写的数据。
- 系统的所有进程的顺序一致,而且是合理的。即不需要和全局时钟下的顺序一致,错的话一起错,对的话一起对。(强一致性的要求比顺序一致性更严格)
3.2 顺序一致性的例子
顺序一致性(Sequential Consistency)提出了两个约束:
- 单个节点的事件历史在全局历史上符合程序的先后顺序
- 全局事件历史在各个节点上一致
历史指某一个读写操作的发生
可以根据这两个约束来判断一个系统是否满足顺序一致性:如果在系统中找不到任何一个符合上述两个约束的全局事件历史,则说明该系统一定不满足顺序一致性;如果能找到一个符合上述两个约束的全局事件历史,这说明系统在这段过程内是满足顺序一致性的。
通过示例来解释说明顺序一致性。假设分布式系统存在A、B、C三个节点,初始值x=0、y=0,各个节点上发生的事件如图所示。
B2读出y=3意味着在全局历史中A2发生在B2之前(记作A2-B2),B3读出x=0意味着全局历史中B3发生在A1之前(记作A2-B2,且B3-A1),而节点A中A1发生在A2之前,节点B的中B2发生在B3之前。所以,显然不存在一个全局历史能够将A1、A2、B2、B3满足上面的执行顺序。
能够找到满足以上约束的全局历史,例如:
B1-B2-A1-B3-A2-C1-C2
B1-A1-B2-B3-A2-C1-C2
B1-A1-C1-B2-A2-C2-B3
……
总结起来就是:在同一从节点中观察,在主节点中先被更新的记录,一定先被读到。
可以描述为,若写入顺序为A、B,在某节点中读到B的最新值,则从该节点中读A获得的一定也是最新值。
4 最终一致性
4.1 最终一致性的含义
不保证在任意时刻任意节点上的同一份数据都是相同的,但是随着时间的迁移,不同节点上的同一份数据总是在向趋同的方向变化。
最终两个字用得很微妙,因为从写入主库到反映至从库之间的延迟,可能仅仅是几分之一秒,也可能是几个小时
简单说,就是在一段时间后,节点间的数据会最终达到一致状态。
最终一致性的种类
最终一致性根据更新数据后各进程访问到数据的时间和方式的不同,又可以区分为:
1)因果一致性(Casual Consistency)
因果一致性。 即如果一系列写入按某个逻辑顺序发生,那么任何人读取这些写入时,会看见它们以正确的逻辑顺序出现。
它往往发生在分区(也称为分片)的分布式数据库中。分区后,每个节点并不包含全部数据。不同的节点独立运行,因此不存在全局写入顺序。如果用户A提交一个问题,用户B提交了回答。问题写入了节点A,回答写入了节点B。因为同步延迟,发起查询的用户可能会先看到回答,再看到问题。
2)“读己之所写(read-your-writes)”一致性。当进程A自己更新一个数据项之后,它自己总是访问到更新过的值,绝不会看到旧值。而其他用户来访问时,遵守一般的最终一致性规则,读到的不一定是最新值。这是因果一致性模型的一个特例。
它可以保证,如果用户刷新页面,他们总会看到自己刚提交的任何更新。它不会对其他用户的写入做出承诺,其他用户的更新可能稍等才会看到。
如何实现读写一致性?
1)最简单的方案,对于某些特定的内容,都从主库读。
2)客户端可以在本地记住最近一次写入的时间戳,发起请求时带着此时间戳。从库提供任何查询前,需确保该时间戳前的变更都已经同步到了本库中。如果当前从库不够新,则可以从另一个从库读,或者等待该从库节点更新数据。
3) 会话(Session)一致性。这是上一个模型的实用版本。只要更新了数据的会话还存在,系统就保证它的“读己之所写”一致性。如果由于某些失败情形令会话终止,就要建立新的会话,而且系统对读写一致性的保证不会延续到新的会话。
4) 单调(Monotonic)读一致性。如果一个用户进行多次读取时,先读取到较新的数据,后续读取不会得到更旧的数据。单调读比强一致性更弱,比最终一致性更强。
实现单调读取的一种方式是确保每个用户总是从同一个节点进行读取(不同的用户可以从不同的节点读取),比如可以基于用户ID的哈希值来选择节点,而不是随机选择节点。
5)单调写一致性。系统保证来自同一个进程的写操作顺序执行。要是系统不能保证这种程度的一致性,就非常难以编程了。