CAP理论,对分布式系统的特性做了高度抽象,比如抽象成了一致性、可用性和分区容错性,并对特性间的冲突(也就是CAP不可能三角)做了总结。
CAP理论对分布式系统的特性做了高度抽象,形成了三个指标:
一致性:指的是客户端的每次读操作,不管访问哪个节点,要么读到的都是同一份最新写入的数据,要么读取失败。
可用性:指的是任何来自客户端的请求,不管访问哪个非故障节点,都能得到响应数据,但不保证是同一份最新数据。这个指标强调的是服务可用,但不保证数据正确。
分区容错性:指的是当节点间出现任意数量的消息丢失或高延迟的时候,系统仍然在继续工作。也就是说,分布式系统在告诉访问本系统的客户端:不管我的内部出现了什么样的数据同步问题,我会一直运行。这个指标强调的是集群对分区故障的容错能力。
在分布式系统中,分区容错性是必须要考虑的。
CAP 不可能三角说的是对于一个分布式系统而言,一致性(Consistency)、可用性(Availability)、分区容错性(Partition Tolerance)3 个指标不可兼得,只能在 3 个指标中选择 2 个。
只要有网络交互就一定会有延迟和数据丢失,而这种状况必须接受,还必须保证系统不能挂掉。所以节点间的分区故障是必然发生的。也就是说,分区容错性(P)是前提,是必须要保证的。
那么剩下一致性(C)和可用性(A)可以选择了:要么选择一致性,保证数据正确;要么选择可用性,保证服务可用。
许多人认为无论在什么情况下,分布式系统都只能在C和A中选择1个。其实在分布式系统正常运行时,就是说在不需要P时,C和A能够同事保证。只有当发生分区故障的时候,也就是说需要P时,才会在C和A之间做出选择。
CA模型,在分布式系统中不存在。因为舍弃 P,意味着舍弃分布式系统,就比如单机版关系型数据库 MySQL,如果 MySQL 要考虑主备或集群部署时,它必须考虑 P。
CP模型,采用 CP 模型的分布式系统,舍弃了可用性,一定会读到最新数据,不会读到旧数据。一旦因为消息丢失、延迟过高发生了网络分区,就影响用户的体验和业务的可用性(比如基于 Raft 的强一致性系统,此时可能无法执行读操作和写操作)。典型的应用是 Etcd,Consul 和 Hbase。
AP模型,采用 AP 模型的分布式系统,舍弃了一致性,实现了服务的高可用。用户访问系统的时候,都能得到响应数据,不会出现响应错误,但会读到旧数据。典型应用就比如 Cassandra 和 DynamoDB。
在当前分布式系统开发中,延迟是非常重要的一个指标,比如,在 QQ 后台的名字路由系统中,通过延迟评估服务可用性,进行负载均衡和容灾;再比如,在 Hashicorp/Raft 实现中,通过延迟评估领导者节点的服务可用性,以及决定是否发起领导者选举。所以,在分布式系统的开发中,需要意识到延迟的重要性,能通过延迟来衡量服务的可用性。
ACID理论可以看成是对事务特性的抽象和总结,方便实现事务。可以理解为:如果实现了操作的ACID特性,那么就实现了事务。在单机上实现事务并不难,如可以通过锁、时间序列等机制保障操作的顺序执行,让系统实现ACID特性。但在分布式系统中比较困难,因为分布系统涉及多个节点间的操作。加锁、时间序列等机制,只能保证单个节点上的ACID特性,无法保证节点间操作的ACID特性。
通过分布式事务协议,如二阶段提交协议和**TCC(Try-Confirm-Cancel)**可以实现分布式系统上的ACID特性。
问题:
如何保证节点A、B、C执行分布式事务操作X,要么全部执行,要么全部不执行。
二阶段提交协议(2pc),就是通过二阶段的协商来完成一个提交操作,具体的操作如下:
客户端发送消息给节点A,节点A收到消息后就扮演协调者(Coordinator)的身份,由节点A通信节点B和节点C,发起二阶段提交。
第一阶段为提交请求节点(又称投票阶段)。首先节点A向节点B和节点C发送消息:“能否执行操作X”,节点B和节点C判断 能否执行操作X,如果可以就预留一部分资源给操作X。最后将能否执行操作X发送给节点A,节点A收到全部的回复结果(包括自己的结果),这里假设全都是能够执行的回复。
第二阶段为提交执行阶段(又称完成阶段),也就是执行具体的操作了。首先节点A按照要么全部执行,要么全部放弃的原则,统计回复结果,由于所有的回复结果都是能够执行,所以节点A决定执行分布式事务操作X。然后通知节点B、节点C执行操作X。节点B和节点C在收到通知后,执行事务操作X。最后节点B和节点C将执行事务的结果返回给节点A。
在第一个阶段中,每个节点决定是放弃还是提交,一旦参与者决定提交事务,那么就不允许放弃事务。也就是说,在一个节点决定提交事务之前,它必须保证能够执行事务操作,即使出现故障或者中途被替换掉。
在第二个阶段中,事务的每个参与节点执行最终统一的决定,提交或者放弃事务。这个约定,实现了ACID中的原子性。
二阶段提交协议最早时用来实现数据库的分布式事务的,不过现在常用的协议是XA协议,该协议是基于二阶段提交协议提出的。不管是原始的二阶段提交协议,还是XA协议,都存在一些问题:
个人感觉2pc依赖每个数据库节点的事务。
TCC 是Try(预留)、Confirm(确认)、Cancel(撤销)3个操作的简称,它包含了预留、确认或撤销这2个阶段。
第一阶段为预留阶段。客户端通知节点A、节点B、节点C让它们预留执行操作X的相关资源,客户端实现确认操作(执行操作X)和撤销操作(取消执行操作X)。然后客户端收到节点A、节点B、节点C的预留答复,假设这里都是OK。
如果预留阶段的执行都没有问题,就进入确认阶段。客户端执行确认操作,通知节点A、节点B、节点C执行操作,客户端收到确认操作的响应后,完成分布式事务。
如果预留阶段出错,比如节点A无法执行操作X,那么就进入撤销阶段。客户端执行撤销操作,通知节点A、节点B、节点C取消执行操作X,客户端收到撤销操作的响应。
TCC本质上是补偿事务,它的核心思想是针对每个操作都要注册一个与其对应的确认操作和补偿操作(也就是撤销操作)。它是一个业务层面的协议,可以将TCC理解为编程模型,即TCC的三个操作都是在业务代码中编程实现的。为了实现一致性,确认操作和补偿操作必须是幂等的。这两个操作会失败重试。
TCC不依赖于数据库的事务(2pc应该是要依赖的),而是在业务中实现了分布式事务,这能减轻数据库的压力,但对业务代码的入侵性比较高,实现比较复杂。
ps:我感觉这点是与2pc不同的点吧,还有2pc是有一个节点作为协调者,而TCC是由客户端作为协调者。
二阶段提交协议和TCC是实现分布式系统ACID特性的方法
可以将 ACID 特性理解为 CAP 中一致性的边界,最强的一致性。根据 CAP 理论,如果在分布式系统中实现了一致性,可用性必然受到影响。比如,如果出现一个节点故障,则整个分布式事务的执行都是失败的。实际上,绝大部分场景对一致性要求没那么高,短暂的不一致是能接受的,另外,也基于可用性和并发性能的考虑,在开发实现分布式系统,如果不是必须,尽量不要实现事务,可以考虑采用最终一致性。
BASE 理论是 CAP 理论中的 AP 的延伸,是对互联网大规模分布式系统的实践总结,强调可用性。
BASE理论的核心是基本可用(Basically Available)和最终一致性(Eventually consistent)。还有一种过度状态—软状态(Soft state),软状态描述的是实现服务可用性的时候系统数据的一种过度状态,也就是说不通节点间,数据副本存在短暂的不一致。
最终一致性是说,系统中所有的数据副本在经过一段时间的同步之后,最终能够达到一个一致的状态。在数据一致性上,存在一个短暂的延迟。
那么如何实现最终一致性? 首先确定它以什么为准,因为这是实现最终一致性的关键。一般来说,工程实践中有如下几种方式:
常见的实现最终一致性的具体方式如下:
写修复就是写的时候没有成功就将数据缓存下来,然后定时重传,保证数据写入成功。写修复不需要做数据一致性对比,性能消耗比较低,对系统运行影响不大。而读修复和异步修复需要做数据的一致性对比,性能消耗比较多。因此推荐写修复实现最终一致性。