数据一致性(1)- CAP

最后更新:2020-04-11

1. 分布式基础

随着移动互联网的快速发展,互联网的用户数量越来越多,产生的数据规模也越来越大,对应用系统提出了更高的要求,我们的系统必须支持高并发访问和海量数据处理。

分布式系统技术就是用来解决集中式架构的性能瓶颈问题,来适应快速发展的业务规模,一般来说,分布式系统是建立在网络之上的硬件或者软件系统,彼此之间通过消息等方式进行通信和协调。

分布式系统的核心是可扩展性,通过对服务、存储的扩展,来提高系统的处理能力,通过对多台服务器协同工作,来完成单台服务器无法处理的任务,尤其是高并发或者大数据量的任务。

除了对可扩展性的需求,分布式系统还有不出现单点故障、服务或者存储无状态等特点。

  • 单点故障(Single Point Failure)是指在系统中某个组件一旦失效,这会让整个系统无法工作,而不出现单点故障,单点不影响整体,就是分布式系统的设计目标之一
  • 无状态,是因为无状态的服务才能满足部分机器宕机不影响全部,可以随时进行扩展的需求

分布式系统比起单机系统存在哪些难点呢?

  • 网络因素

由于服务和数据分布在不同的机器上,每次交互都需要跨机器运行,这带来如下几个问题:网络延迟:性能、超时

同机房的网络IO还是比较块的,但是跨机房,尤其是跨IDC,网络IO就成为不可忽视的性能瓶颈了。并且,延迟不是带宽,带宽可以随便增加,千兆网卡换成万兆,只是成本的问题,但延迟是物理限制,基本不可能降低。

这带来的问题就是系统整体性能的降低,会带来一系列的问题,比如资源的锁住,所以系统调用一般都要设置一个超时时间进行自我保护,但是过度的延迟就会带来系统的RPC调用超时,引发一个令人头疼的问题:分布式系统调用的三态结果:成功、失败、超时。不要小看这个第三态,这几乎是所有分布式系统复杂性的根源。

针对这个问题有一些相应的解决方案:异步化,失败重试。 而对于跨IDC数据分布带来的巨大网络因素影响,则一般会采用数据同步,代理专线等处理方式。

  • 网络故障:丢包、乱序、抖动。

这个可以通过将服务建立在可靠的传输协议上来解决,比如TCP协议。不过带来的是更多的网络交互。因此是性能和流量的一个trade off。这个在移动互联网中更需要考虑。

由于分布式系统的特点,在分布式环境中更容易出现问题,比如节点之间通信失败、网络分区故障、多个副本的数据不一致等,为了更好的在分布式系统下进行开发,学者们提出了一系列的理论,其中具有代表性的就是CAP理论

2. CAP

2000年7月,加州大学伯克利分校的Eric Brewer教授在ACM PODC会议上提出CAP猜想。2年后,麻省理工学院的Seth Gilbert和Nancy Lynch从理论上证明了CAP。之后,CAP理论正式成为分布式计算领域的公认定理。

一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。

  • C(一致性):所有节点同时看到相同的数据
  • A(可用性):每个请求都能接受到一个响应,无论响应成功或失败
  • P(分区容错):当部分节点出现消息丢失或者分区故障的时候,分布式系统仍然能够继续运行

2.1. Consistency 一致性

一致性指“all nodes see the same data at the same time”,即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致。

对于一致性,可以分为从客户端和服务端两个不同的视角。从客户端来看,一致性主要指的是多并发访问时更新过的数据如何获取的问题。从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。一致性是因为有并发读写才有的问题,因此在理解一致性的问题时,一定要注意结合考虑并发读写的场景。

从客户端角度,多进程并发访问时,更新过的数据在不同进程如何获取的不同策略,决定了不同的一致性。对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这是强一致性。如果能容忍后续的部分或者全部访问不到,则是弱一致性。如果经过一段时间后要求能访问到更新后的数据,则是最终一致性

这里并不是强调同一时刻拥有相同的数据,对于系统执行事务来说,在事务执行过程中,系统其实处于一个不一致的状态,不同的节点的数据并不完全一致。

一致性强调客户端读操作能够获取最新的写操作结果,是因为事务在执行过程中,客户端是无法读取到未提交的数据的。

只有等到事务提交后,客户端才能读取到事务写入的数据,而如果事务失败则会进行回滚,客户端也不会读取到事务中间写入的数据。

2.2. Availability 可用性

可用性指“Reads and writes always succeed”,即服务一直可用,而且是正常响应时间。

对于一个可用性的分布式系统,每一个非故障的节点必须对每一个请求作出响应(不是错误和超时的响应)。也就是,该系统使用的任何算法必须最终终止。当同时要求分区容忍性时,这是一个很强的定义:即使是严重的网络错误,每个请求必须终止

好的可用性主要是指系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。可用性通常情况下和分布式数据冗余,负载均衡等有着很大的关联

这里强调的是合理的响应,不能超时,不能出错。注意并没有说“正确”的结果,例如,应该返回 100 但实际上返回了 90,肯定是不正确的结果,但可以是一个合理的结果。

2.3. Partition Tolerance分区容错性

分区容错性指“the system continues to operate despite arbitrary message loss or failure of part of the system”,即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。

这里网络分区是指:一个分布式系统里面,节点组成的网络本来应该是连通的。然而可能因为一些故障(节点间网络连接断开、节点宕机),使得有些节点之间不连通了,整个网络就分成了几块区域,数据就散布在了这些不连通的区域中。

分区容错性和扩展性紧密相关。在分布式应用中,可能因为一些分布式的原因导致系统无法正常运转。好的分区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转正常的整体。比如现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统需求,或者是机器之间有网络异常,将分布式系统分隔为独立的几个部分,各个部分还能维持分布式系统的运作,这样就具有好的分区容错性。

3. CAP举例

假设网络中有两个节点N1和N2,可以简单的理解N1和N2分别是两台计算机,他们之间网络可以连通,N1中有一个应用程序A,和一个数据库V,N2也有一个应用程序B和一个数据库V。现在,A和B是分布式系统的两个部分,V是分布式系统的数据存储的两个子数据库。

	N1	+-----+      +-----+
		| A   |      | V0  |
		+-----+      +-----+
	
	N2	+-----+      +-----+
		| B   |      | V0  |
		+-----+      +-----+
  • 在满足一致性的时候,N1和N2中的数据是一样的,V0=V0。
  • 在满足可用性的时候,用户不管是请求N1或者N2,都会得到立即响应。
  • 在满足分区容错性的情况下,N1和N2有任何一方宕机,或者网络不通的时候,都不会影响N1和N2彼此之间的正常运作。

下图描述了分布式系统正常运转的流程,用户向N1机器请求数据更新,程序A更新数据库V0为V1,分布式系统将数据进行同步操作M,将V1同步的N2中V0,使得N2中的数据V0也更新为V1,N2中的数据再响应N2的请求。

第一步,用户向N1机器请求数据更新,程序A更新数据库V0为V1

	N1	+-----+  V1  +-----+
		| A   + ---->| V0  |
		+-----+      +-----+
	
	N2	+-----+      +-----+
		| B   |      | V0  |
		+-----+      +-----+

第二步,分布式系统将数据进行同步操作M,将V1同步的N2中V0,使得N2中的数据V0也更新为V1

	N1	+-----+      +-----+
		| A   +      | V1  |
		+-----+      +-----+
		                |M
		                v
	N2	+-----+      +-----+
		| B   |      | V0  |
		+-----+      +-----+

第三步,N2中的数据再响应N2的请求

	N1	+-----+      +-----+
		| A   +      | V1  |
		+-----+      +-----+
	
	N2	+-----+   V1 +-----+
		| B   | <----| V1  |
		+-----+      +-----+

这里,可以定义N1和N2的数据库V之间的数据是否一样为一致性;外部对N1和N2的请求响应为可用行;N1和N2之间的网络环境为分区容错性。这是正常运作的场景,也是理想的场景,然而现实是残酷的,当错误发生的时候,一致性和可用性还有分区容错性,是否能同时满足,还是说要进行取舍呢?

作为一个分布式系统,它和单机系统的最大区别,就在于网络,现在假设一种极端情况,N1和N2之间的网络断开了,我们要支持这种网络异常,相当于要满足分区容错性,能不能同时满足一致性和响应性呢?还是说要对他们进行取舍。

假设在N1和N2之间网络断开的时候,有用户向N1发送数据更新请求

那N1中的数据V0将被更新为V1,由于网络是断开的,所以分布式系统同步操作M失败,所以N2中的数据依旧是V0;这个时候,有用户向N2发送数据读取请求,由于数据还没有进行同步,应用程序没办法立即给用户返回最新的数据V1,怎么办呢?有二种选择,第一,牺牲数据一致性,响应旧的数据V0给用户;第二,牺牲可用性,阻塞等待,直到网络连接恢复,数据更新操作M完成之后,再给用户响应最新的数据V1。

第一步,用户向N1机器请求数据更新,程序A更新数据库V0为V1

	N1	+-----+  V1  +-----+
		| A   + ---->| V0  |
		+-----+      +-----+
	
	N2	+-----+      +-----+
		| B   |      | V0  |
		+-----+      +-----+

第二步,分布式系统将数据进行同步操作M,由于网络是断开的,所以分布式系统同步操作M失败,N2中的数据依旧是V0

	N1	+-----+      +-----+
		| A   +      | V1  |
		+-----+      +-----+
		                |M(失败)
		                x
	N2	+-----+      +-----+
		| B   |      | V0  |
		+-----+      +-----+

第三步,有用户向N2发送数据读取请求,由于数据还没有进行同步,应用程序没办法立即给用户返回最新的数据V1,此时有两种选择 1)牺牲数据一致性,响应旧的数据V0给用户;2)牺牲可用性,阻塞等待,直到网络连接恢复,数据更新操作M完成之后,再给用户响应最新的数据V1

	N1	+-----+      +-----+
		| A   +      | V1  |
		+-----+      +-----+
	
	N2	+-----+   V0 +-----+
		| B   | <----| V0  |
		+-----+      +-----+

这个过程,证明了要满足分区容错性的分布式系统,只能在一致性和可用性两者中,选择其中一个。

4. CAP权衡

通过CAP理论,我们知道无法同时满足一致性、可用性和分区容错性这三个特性,那要舍弃哪个呢?

  • CA without P:如果不要求P(不允许分区),则C(强一致性)和A(可用性)是可以保证的。但其实分区不是你想不想的问题,而是始终会存在,因此CA的系统更多的是允许分区后各子系统依然保持CA。
  • CP without A:如果不要求A(可用),相当于每个请求都需要在Server之间强一致,而P(分区)会导致同步时间无限延长,如此CP也是可以保证的。很多传统的数据库分布式事务都属于这种模式。
  • AP wihtout C:要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。现在众多的NoSQL都属于此类。

在分布式的环境下,网络无法做到 100% 可靠,有可能出现故障,因此分区是一个必须的选项。CAP的应用模型就是CP架构和AP架构。分布式系统所关注的,就是在PartitionTolerance的前提下,如何实现更好的 A,和更稳定的 C。

对于大型网站来说,分区容错和可用性的要求更高,所以一般都会选择适当放弃一致性。

对应CAP理论,NoSQL追求的是AP,而传统数据库追求的是CA,这也可以解释为什么传统数据库的扩展能力有限的原因。

5. CAP 理论的应用

CAP 理论提醒我们,在架构设计中,不要把精力浪费在如何设计能满足三者的完美分布式系统上,而要合理进行取舍,CAP 理论类似数学上的不可能三角,只能三者选其二,不能全部获得。

不同业务对于一致性的要求是不同的。举个例来讲,在微博上发表评论和点赞,用户对不一致是不敏感的,可以容忍相对较长时间的不一致,只要做好本地的交互,并不会影响用户体验;而我们在电商购物时,产品价格数据则是要求强一致性的,如果商家更改价格不能实时生效,则会对交易成功率有非常大的影响。

需要注意的是,CAP理论中是忽略网络延迟的,也就是当事务提交时,节点间的数据复制一定是需要花费时间的。即使是同一个机房,从节点A复制到节点B,由于现实中网络不是实时的,所所以总会有一定的时间不一致。

6. CAP的目标状态

如上图所示:在未发生网络分区的T1时刻,这时的分布式系统是同时满足CAP三要素的,各份数据一致性状态是S,当到了T2时刻,发生了网络分区情况,此时各个节点,会记录各个节点的状态S1、S2,当T3时刻网络分区恢复时,需要将S1、S2状态进行相应处理,这里的部分实际上是很多分布式服务的重点:比如可以通过log end offset(Kafka)或者transaction id(Zookeeper)在分区发生后进行leader比对选举,然后再进行数据同步。

7. CP 和 AP 架构的取舍

在通常的分布式系统中,为了保证数据的高可用,通常会将数据保留多个副本(Replica),网络分区是既成的现实,于是只能在可用性和一致性两者间做出选择。CAP理论关注的是在在绝对情况下,在工程上,可用性和一致性并不是完全对立的,我们关注的往往是如何在保持相对一致性的前提下,提高系统的可用性。

业务上对一致性的要求会直接反映在系统设计中,典型的就是 CP 和 AP 结构。

CP 架构:对于 CP 来说,放弃可用性,追求一致性和分区容错性。

Zookeeper 保证 CP,即任何时刻对 Zookeeper 的访问请求能得到一致性的数据结果,同时系统对网络分割具备容错性,但是它不能保证每次服务的可用性。

从实际情况来分析,在使用 Zookeeper 获取服务列表时,如果 ZK 正在选举或者 ZK 集群中半数以上的机器不可用,那么将无法获取数据。所以说,ZK 不能保证服务可用性

AP 架构:对于 AP 来说,放弃强一致性,追求分区容错性和可用性,这是很多分布式系统设计时的选择,后面的 Base 也是根据 AP 来扩展的。

Eureka 保证 AP,Eureka 在设计时优先保证可用性,每一个节点都是平等的。

一部分节点挂掉不会影响到正常节点的工作,不会出现类似 ZK 的选举 Leader 的过程,客户端发现向某个节点注册或连接失败,会自动切换到其他的节点

只要有一台 Eureka 存在,就可以保证整个服务处在可用状态,只不过有可能这个服务上的信息并不是最新的信息。

8. 参考资料

http://www.hollischuang.com/archives/666

http://blog.csdn.net/chen77716/article/details/30635543

http://www.infoq.com/cn/articles/cap-twelve-years-later-how-the-rules-have-changed

《分布式技术原理与实战45讲》

Edgar

Edgar
一个略懂Java的小菜比