一、CAP原则与BASE定理
1、CAP原则
CAP 是Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性)的首字母组合,它是所有分布式系统在设计前设计师必须优先考虑的事情。

-
一致性 C 代表更新操作成功后,所有节点在同一时间的数据完全一致。 -
**可用性 A **代表用户访问数据时,系统是否能在正常响应时间返回预期的结果。 -
分区容错性 P 代表分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性或可用性的服务。
我们以实际案例来说明一下CAP概念。以订单系统和库存系统为例

在实际业务中订单的创建会伴随着库存的减少,在原本单体架构中,这是一个应用中的两个模块,但随着业务发展库存模块压力增加,架构师就将其剥离为新的库存系统,库存数据也存放在单独的数据库中,两个系统间通过网络进行通信,架构模式自然也转为分布式。但与此同时,加入网络通信后系统故障概率也在增加,架构师必须考虑应用“分区”后的容错性问题,这就是 CAP 中的 P 分区容错性的含义,分区容错性是任何分布式系统必须包含的因素。
那么后续如何处理呢?方案有两种:
-
第一种,放弃 C 一致性保障 A 可用性,简称 AP。具体表现为产生通信故障后,应用会完成新增订单放弃减少库存的操作,应用立即返回响应,并在响应中说明具体处理的细节,这种方案用户会有更好的体验,但数据层面是不完整的,需要后续补偿。
-
第二种,放弃 A 可用性保障一致性 C,简称 CP。具体表现为产生通信故障后,应用会进入阻塞状态,一直尝试与库存系统恢复通信直到完成所有数据处理。这种方案是优先保障数据完整性,但此方案用户体验极差,因为在所有操作完成前用户会一直处于等待的状态。
CAP 本身就是互斥的,只能三者选两个,对于 CA、AP、CP 都有它们自己的应用场景,要结合实际进行选择。例如,CA 因为不考虑分区容忍度,所以它所有操作需要在同一进程内完成(也就是我们常说的单体应用);AP 因为放弃数据一致性,适合数据要求不高但强调用户体验的项目,如博客、新闻资讯等;CP 反之放弃了可用性,适合数据要求很高的交易系统,如银行交易、电商的订单交易等,就算是用户长时间等待,也要保障数据的完整可靠。
以上就是 CAP 原则在实际项目中的运用,对于互联网应用来说,如果为了用户体验完全放弃数据一致性这也是不可取的,毕竟数据才是根本。那怎么解决呢?这又衍生出一个新的理论:BASE 定理。
2、BASE定理
BASE 定理是Basically Available(基本可用),Soft State(软状态)和Eventually Consistent(最终一致性)三个短语的缩写。BASE 是对 CAP 中一致性和可用性权衡后的选择,其来源于对大规模互联网系统分布式实践的结论,是基于 CAP 定理逐步演化而来的,其核心思想是即使无法做到强一致性,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。
在刚才案例中,订单与库存系统通信故障时,架构师选择AP方案保障了用户的响应速度,此时订单创建但库存并没有减少,这就是不一致的情况。为了解决这种问题,很多项目会写一个任务线程定时检查通信状态,当通信恢复后该任务会通知库存系统完成减库存的处理,使数据保持一致。
BASE 允许存在软状态,所谓软状态就是在上述案例中订单增加但库存没有减少时的中间状态,后续的补救措施中任务线程对库存进行补偿,此时数据最终做到了的一致,此时的状态就是最终一致性状态。BASE 定理只要求保障数据是最终一致的,因此存在中间的软状态。
保障最终一致性的措施有很多,如 TCC、任务定时检查、MQ 消息队列缓冲、ETL 做日终校对甚至人工补录都是常用策略。在这些策略中,TCC 是架构师门普遍采用的一致性实现方案,我们先来了解什么是 TCC。
二、TCC一致性方案
TCC 是三个字母Try(尝试)、Confirm(确认)与Cancel(取消)的首字母缩写。TCC 并不是指某一种技术,而是一种数据一致性方案,它将分布式处理过程分为两个阶段:
-
Try 是第一个阶段,用于尝试并锁定资源; -
如果资源锁定成功,第二个阶段进行 Confirm 提交完成数据操作; -
如果资源锁定失败,第二个阶段进行 Cancel 将数据回滚;
我们通过实例来理解一下:
假设张三购买了 10 瓶可乐总价 30 元,需要订单表增加一笔金额为 30元 的订单,库存表可乐减 10 瓶。如果是 TCC 方案,在订单表需要额外增加预增金额与状态两个字段,库存表额外增加冻结库存字段。因为 Try 阶段用于锁定资源,因此新订单的 30 元是在预增金额中,而不是直接更新订单金额字段,此时订单的状态是“初始”状态。同样的,库存表要冻结 10 瓶可乐,而不是直接将 100 更新为 90。
当两个系统所有资源都锁定完毕,便进入第二阶段执行 Confirm 操作。Confirm 操作用于提交数据,提交数据的过程非常简单,将订单表预增金额移动到订单金额中,订单状态变为完成,商品库存对应减少。
如果 Try 阶段锁定资源出现异常,比如出现“库存不足”的情况,则进入第二阶段执行 Cancel 操作撤销之前锁定的资源。订单表预增金额重置为 0,订单状态变为取消,而库存表冻结库存也重置为 0。
以上就是 TCC 在实际项目中的执行过程,可以发现 TCC 是在数据源做文章,通过控制表上增加额外的锁定资源字段保障数据的一致性。那 TCC 方案中有哪些注意事项呢?
-
要把绝大多数的业务逻辑在 Try 阶段完成,在 Try 阶段做尽可能多的事情。因为 TCC 设计之初认为 Confirm 或 Cancel 是一定要成功的,因此不要二阶段包含任何业务代码或者远程通信,只通过最简单的代码释放冻结资源。就像这个案例,Confirm 或 Cancel 只是在表上执行 update 语句来释放冻结资源,这个操作成功率 99.9999%,你可以认为二阶段是可靠的。
-
假设 Confirm 或 Cancel 执行时出现错误,那具体的 TCC 框架也会不断重试执行操作来尽量保证执行成功,这个过程中可能会多次执行 update 语句,因此要注意代码的幂等性。
极小概率下,Confim 或 Cancel 在多次重试后宣告失败,便会出现数据最终不一致的情况,这就需要自己开发额外的数据完整性校验程序补救或者通过人工进行补录。
三、Seata的TCC模式
1、Seata TCC的执行过程
对于 Seata 的 TCC 模式,与之前我们介绍过的 AT 模式高度相似。可以理解为 TCC 是 AT 模式的“手动版”,所有提交与回滚时的操作都要自己书写代码进行处理,而不能像 AT 那样自动执行反向 SQL 完成提交与回滚。

2、Seata AT和TCC如何取舍?
既然 Seata 已经有了 AT 模式,为何还要引入 TCC 保障事务一致性?在前面我们是在介绍分布式事务时讲解了 Seata AT 模式,但在复杂的企业应用中,不可能完全要求底层数据库统一使用 MySQL,甚至都不能保证所有的数据源都支持事务。
比如有个场景:用户上传了文件后结果系统出现异常需要全局回滚,回滚时需要把这个文件删除,此时基于 Seata AT 模式的反向 SQL 就无能为力了。而 TCC 就能良好解决此类问题,因为 TCC 过程中所有的逻辑都是程序员通过代码控制的,能很好地解决这类非事务型数据处理场景。
那 Seata AT 与 TCC 又该如何取舍呢?这要根据具体的业务场景了,如果在涉及的所有服务底层都是用 MySQL、Oracle 这样的事务型关系数据库,且业务都是对数据库的直接操作,那使用 AT 模式可以最快地实现分布式数据一致性,但是如果涉及非事务资源的操作,那 AT 模式就无能为力,必须采用 TCC 自己实现准备、提交、回滚的细节,但这也无疑对开发人员的能力提出了更高的要求,因此尽量将这些任务安排给团队中技术好的核心工程师来实现。
原文始发于微信公众号(服务端技术精选):【微服务-实战干货】微服务架构下数据一致性解决方案分享
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/275162.html