CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性)这3个基本需求,最多只能同时满足其中的2个。

首先对于分布式系统,分区是必然存在的,所谓分区指的是分布式系统可能出现的字区域网络不通,成为孤立区域的的情况。

那么分区容错性(P)就必须要满足,因为如果要牺牲分区容错性,就得把服务和资源放到一个机器,或者一个“同生共死”的集群,那就违背了分布式的初衷。

假如现在有两个分区和,N1和N2分别有不同的分区存储D1和D2,以及不同的服务S1和S2。

保证:此时D1和D2数据不一致,要保证一致性就不能返回不一致的数据,无法保证。

保证:立即响应,可用性得到了保证,但是此时响应的数据和D1不一致,无法保证。

理论上放弃P(分区容错性),则C(强一致性)和A(可用性)是可以保证的。实际上分区是不可避免的,严格上CA指的是允许分区后各子系统依然保持CA。

放弃A(可用),相当于每个请求都需要在Server之间强一致,而P(分区)会导致同步时间无限延长,如此CP也是可以保证的。很多传统的数据库分布式事务都属于这种模式。

要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。现在众多的NoSQL都属于此类。

什么是基本可用呢?假设系统出现了不可预知的故障,但还是能用,只是相比较正常的系统而言,可能会有响应时间上的损失,或者功能上的降级。

软状态也称为弱状态,相比较硬状态而言,允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。

上面说了软状态,但是不应该一直都是软状态。在一定时间后,应该到达一个最终的状态,保证所有副本保持数据一致性,从而达到数据的最终一致性。这个时间取决于网络延时、系统负载、数据复制方案设计等等因素。

单体时代,可以直接用本地锁来实现对竞争资源的加锁,分布式环境下就要用到分布式锁了。

用数据库实现分布式锁比较简单,就是创建一张锁表,数据库对字段作唯一性约束。

这种属于数据库 IO 操作,效率不高,而且频繁操作会增大数据库的开销,因此这种方式在高并发、高性能的场景中用的不多。

ZooKeeper的数据节点和文件目录类似,例如有一个lock节点,在此节点下建立子节点是可以保证先后顺序的,即便是两个进程同时申请新建节点,也会按照先后顺序建立两个节点。

所以我们可以用此特性实现分布式锁。以某个资源为目录,然后这个目录下面的节点就是我们需要获取锁的客户端,每个服务在目录下创建节点,如果它的节点,序号在目录下最小,那么就获取到锁,否则等待。释放锁,就是删除服务创建的节点。

ZK实际上是一个比较重的分布式组件,实际上应用没那么多了,所以用ZK实现分布式锁,其实相对也比较少。

实现分布式锁最简单的一个命令:setNx(set if not exist),如果不存在则更新:

加锁了之后如果机器宕机,那我这个锁就无法释放,所以需要加入过期时间,而且过期时间需要和setNx同一个原子操作,在Redis2.8之前需要用lua脚本,但是redis2.8之后redis支持nx和ex操作是同一原子操作。

当然,一般生产中都是使用Redission客户端,非常良好地封装了分布式锁的api,而且支持RedLock。

分布式事务是相对本地事务而言的,对于本地事务,利用数据库本身的事务机制,就可以保证事务的ACID特性。

分布式事务其实就是将对同一库事务的概念扩大到了对多个库的事务。目的是为了保证分布式系统中的数据一致性。

分布式常见的实现方案有2PC、3PC、TCC、本地消息表、MQ消息事务、最大努力通知、SAGA事务等等。

XA协议采用两阶段提交方式来管理分布式事务。XA接口提供资源管理器与事务管理器之间进行通信的标准接口。

两阶段提交的思路可以概括为:参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情况决定各参与者是否要提交操作还是回滚操作。

准备阶段:事务管理器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交

优点:尽量保证了数据的强一致,实现成本较低,在各大主流数据库都有自己实现,对于MySQL是从5.5开始支持。

单点问题:事务管理器在整个流程中扮演的角色很关键,如果其宕机,比如在第一阶段已经完成,在第二阶段正准备提交的时候事务管理器宕机,资源管理器就会一直阻塞,导致数据库无法使用。

同步阻塞:在准备就绪之后,资源管理器中的资源一直处于阻塞,直到提交完成,释放资源。

数据不一致:两阶段提交协议虽然为分布式数据强一致性所设计,但仍然存在数据不一致性的可能,比如在第二阶段中,假设协调者发出了事务commit的通知,但是因为网络问题该通知仅被一部分参与者所收到并执行了commit操作,其余的参与者则因为没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。

三阶段提交()是二阶段提交()的一种改进版本 ,为解决两阶段提交协议的单点故障和同步阻塞问题。

CanCommit:准备阶段。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。

PreCommit:预提交阶段。协调者根据参与者在准备阶段的响应判断是否执行事务还是中断事务,参与者执行完操作之后返回ACK响应,同时开始等待最终指令。

DoCommit:提交阶段。协调者根据参与者在准备阶段的响应判断是否执行事务还是中断事务:

如果参与者无法及时接收到来自协调者的提交或者中断事务请求时,在等待超时之后,会继续进行事务提交

可以看出,三阶段提交解决的只是两阶段提交中单体故障和同步阻塞的问题,因为加入了超时机制,这里的超时的机制作用于预提交阶段和提交阶段。如果等待预提交请求超时,参与者直接回到准备阶段之前。如果等到提交请求超时,那参与者就会提交事务了。

TCC(Try Confirm Cancel),是两阶段提交的一个变种,针对每个操作,都需要有一个其对应的确认和取消操作,当操作成功时调用确认操作,当操作失败时调用取消操作,类似于二阶段提交,只不过是这里的提交和回滚是针对业务上的,所以基于TCC实现的分布式事务也可以看做是对业务的一种补偿机制。

Try:尝试待执行的业务。订单系统将当前订单状态设置为支付。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注