分布式系统中的CAP原理
C - Consistent ,一致性。具体是指,操作成功以后,所有的节点,在同一时间,看到的数据都是完全一致的。所以,一致性,说的就是数据一致性。
A - Availability ,可用性。指服务一致可用,在规定的时间内完成响应。
P - Partition tolerance ,分区容错性。指分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供服务。
【只能是AP 或者 CP】
ACID与BASE
ACID是事务处理的4个特性。 A - Atomicity(原子性),事务中的操作要么都做,要么都不做。 C - Consistency(一致性),系统必须始终处在强一致状态下。 I - Isolation(隔离性),一个事务的执行不能被其他事务所干扰。 D - Durability(持久性),一个已提交的事务对数据库中数据的改变是永久性的。
ACID强调的是强一致性,要么全做,要么全不做,所有的用户看到的都是一致的数据。传统的数据库都有ACID特性,它们在CAP原理中,保证的是CA。但是在分布式系统大行其道的今天,满足CA特性的系统很难生存下去。ACID也逐渐的向BASE转换。
什么是BASE?
BASE是Basically Available(基本可用), Soft-state(软状态), Eventually consistent(最终一致)的缩写。
Basically Available,基本可用是指分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用。电商大促时,为了应对访问量激增,部分用户可能会被引导到降级页面,服务层也可能只提供降级服务。这就是损失部分可用性的体现。
软状态( Soft State)
软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。分布式存储中一般一份数据至少会有两到三个副本,允许不同节点间副本同步的延时就是软状态的体现。mysql replication的异步复制也是一种体现。
最终一致性( Eventual Consistency)
最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况。
BASE模型是传统ACID模型的反面,不同与ACID,BASE强调牺牲高一致性,从而获得可用性,数据允许在一段时间内的不一致,只要保证最终一致就可以了。
在分布式事务的解决方案中,它们都是依赖了ACID或者BASE的模型而实现的。像基于XA协议的两阶段提交和实物补偿机制就是基于ACID实现的。而基于本地消息表和基于MQ的最终一致方案都是通过BASE原理实现的。这几种分布式事务的解决方案,我们会在视频的课程中给大家讲解。
分布式事务
强一致性基于XA协议(事务规范)的两阶段提交
- XA协议是X/Open组织提出的分布式事务规范
- 由一个事务管理器(TM)和多个资源管理器(RM)组成
- 提交分为两个阶段:prepare和commit
- 保证数据的强一致性。
- commit阶段出现问题,事务出现不一致,需人工处理。
- 效率低下
- Mysql 5.7 以上支持XA协议
两阶段提交 2PC
通过引入协调者来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。
- 阶段一:事务询问、执行事务、各参与者向协调者反馈事务询问的响应
- 阶段二:执行事务提交或者中断事务。
- 缺点:同步阻塞,单点问题,数据不一致,过于保守
2PC 是一种尽量保证强一致性的分布式事务,因此它是同步阻塞的,而同步阻塞就导致长久的资源锁定问题,总体而言效率低,并且存在单点故障问题,在极端条件下存在数据不一致的风险。
三阶段提交 3PC
3PC 相对于 2PC 做了一定的改进:引入了参与者超时机制,并且增加了预提交阶段使得故障恢复之后协调者的决策复杂度降低,但整体的交互过程更长了,性能有所下降,并且还是会存在数据不一致问题。
https://www.jianshu.com/p/5c6f195e091b
33PC 的引入是为了解决提交阶段 2PC 协调者和某参与者都挂了之后新选举的协调者不知道当前应该提交还是回滚的问题。相比于 2PC 它在参与者中也引入了超时机制,并且新增了一个阶段使得参与者可以利用这一个阶段统一各自的状态。(把 2PC 的提交阶段变成了预提交阶段和提交阶段,但是 3PC 的准备阶段协调者只是询问参与者的自身状况)
阶段一:CanCommit
事务询问:协调者向所有的参与者发送一个包含事务内容的 canCommit 请求,询问是否可以执行事务提交操作,并开始等待各参与者的响应。
各参与者向协调者反馈事务询问的响应:参与者接收来自协调者的 canCommit 请求,如果参与者认为自己可以顺利执行事务,就返回 Yes,否则反馈 No 响应。
阶段二:PreCommit
协调者在得到所有参与者的响应之后,会根据结果执行2种操作:执行事务预提交,或者中断事务。
阶段三:do Commit
该阶段做真正的提交,执行提交/中断事务
优点:相比较 2PC,最大的优点是减少了参与者的阻塞范围(第一个阶段是不阻塞的),并且能够在单点故障后继续达成一致(2PC 在提交阶段会出现此问题,而 3PC 会根据协调者的状态进行回滚或者提交)。
缺点:如果参与者收到了 preCommit 消息后,出现了网络分区,那么参与者等待超时后,都会进行事务的提交,这必然会出现事务不一致的问题。两段式(2PC)和三段式(3PC)
不一定能保证数据最终一致,但是效率上还算ok。
事务补偿机制(TCC)
2PC 和 3PC 都是数据库层面的强一致性事务,而 TCC 是业务层面的分布式事务(补偿性事务思想)。
- Try 指的是预留,即资源的预留和锁定,注意是预留。
- Confirm 指的是确认操作,这一步其实就是真正的执行了。
- Cancel 指的是撤销操作,可以理解为把预留阶段的动作撤销了
TCC 对业务的侵入较大和业务紧耦合,需要根据特定的场景和业务逻辑来设计相应的操作。
撤销和确认操作的执行可能需要重试,因此还需要保证操作的幂等。
- 使用
Mycat或者sharding-jdbc
也可以实现分布式事务强一致性。
本地消息表
有一张存放本地消息的表,一般都是放在数据库中,然后在执行业务的时候 将业务的执行和将消息放入消息表中的操作放在同一个事务中,这样就能保证消息放入本地表中业务肯定是执行成功的。
然后再去调用下一个操作,如果下一个操作调用成功了好说,消息表的消息状态可以直接改成已成功。
如果调用失败也没事,会有 后台任务定时去读取本地消息表,筛选出还未成功的消息再调用对应的服务,服务更新成功了再变更消息的状态。
事务消息
本地消息、事务消息和最大努力通知其实都是最终一致性事务,因此适用于一些对时间不敏感的业务。
基于本地消息表+定时任务的最终一致性
- 采用BASE原理,保证事务最终一致性
- 允许一段时间内的不一致,但最终会一致。
- 将本事务外的操作记录在消息表中。
- 定时任务将未执行的消息发送给操作接口。(设置最大失败次数)
- 本地消息表改成了MQ
- 定时任务改为MQ的消费者
TCC和最终一致性其实不是很适合,TCC开发成本很大,所有接口都要写三次,因为涉及TCC的三个阶段。
最终一致性基本上都是靠轮训的操作去保证一个操作一定成功,那时效性就大打折扣了。
本地消息表 / 消息事务 - 最终一致性
- 基于本地消息表 (避免了分布式事务,实现了最终一致性。)
-
如何处理异常,保证事务一致性
阻塞式重试。(对一致性要求不敏感)
- 异步队列。
- TCC补偿事务。(每个服务都要实现Try、Confirm、Cancel)
空释放:try调用失败,Cancel要支持空释放的执行。
时序:Cancel请求先到,Try后到。利用唯一事务ID区分Try请求。
调用失败:有中间态就会有失败的可能,用1&2解决。 - 本地消息表。本地消息表与业务数据表在同一个数据库,根据消息数据做后续操作。
配合MQ:先插入到本地消息表,尝试推送到MQ。(保证消息一定推送到MQ)
配合服务调用。
消息过期机制:要判断当前消息任务是否存在过久。 - 独立消息服务。操作之前现在消息服务添加消息,操作成功删除消息。
- MQ事务。如RocketMq。
- 2PC、3PC 传统事务
-不同公司 地区+国标码 对应不上的问题: 上传接口传入地区国标码 + 地区名称, 通过国标码反查地区id,能查到就保存地区id,查不到保存地区名称。
- 从用户的角度设计接口,避免出现定制化参数。
- 方案要有包容性。
- 没有最好的方案,只有最合适的方案。
- 进行有效的沟通,没有好的方案就先跳过。