彻底搞懂CAP理论(电商系统)

1、理解CAP


CAP是 Consistency、Availability、Partition tolerance三个词语的缩写,分别表示一致性、可用性、分区容忍性。

下边我们分别来解释:

为了方便对CAP理论的理解,我们结合电商系统中的一些业务场景来理解CAP。

如下图,是商品信息管理的执行流程:


整体执行流程如下:

1、商品服务请求主数据库写入商品信息(添加商品、修改商品、删除商品)

2、主数据库向商品服务响应写入成功。

3、商品服务请求从数据库读取商品信息。

 

C - Consistency:

一致性是指写操作后的读操作可以读取到最新的数据状态,当数据分布在多个节点上,从任意结点读取到的数据都是最新的状态。

上图中,商品信息的读写要满足一致性就是要实现如下目标:

1、商品服务写入主数据库成功,则向从数据库查询新数据也成功。

2、商品服务写入主数据库失败,则向从数据库查询新数据也失败。

如何实现一致性?

1、写入主数据库后要将数据同步到从数据库。

2、写入主数据库后,在向从数据库同步期间要将从数据库锁定,待同步完成后再释放锁,以免在新数据写入成功后,向从数据库查询到旧的数据。

分布式系统一致性的特点:

1、由于存在数据同步的过程,写操作的响应会有一定的延迟。

2、为了保证数据一致性会对资源暂时锁定,待数据同步完成释放锁定资源。

3、如果请求数据同步失败的结点则会返回错误信息,一定不会返回旧数据。

A - Availability :

可用性是指任何事务操作都可以得到响应结果,且不会出现响应超时或响应错误。

上图中,商品信息读取满足可用性就是要实现如下目标:

1、从数据库接收到数据查询的请求则立即能够响应数据查询结果。

2、从数据库不允许出现响应超时或响应错误。

如何实现可用性?

1、写入主数据库后要将数据同步到从数据库。

2、由于要保证从数据库的可用性,不可将从数据库中的资源进行锁定。

3、即时数据还没有同步过来,从数据库也要返回要查询的数据,哪怕是旧数据,如果连旧数据也没有则可以按照约定返回一个默认信息,但不能返回错误或响应超时。

分布式系统可用性的特点:

1、 所有请求都有响应,且不会出现响应超时或响应错误。

P - Partition tolerance :

通常分布式系统的各各结点部署在不同的子网,这就是网络分区,不可避免的会出现由于网络问题而导致结点之间通信失败,此时仍可对外提供服务,这叫分区容忍性。

上图中,商品信息读写满足分区容忍性就是要实现如下目标:

1、主数据库向从数据库同步数据失败不影响读写操作。

2、其一个结点挂掉不影响另一个结点对外提供服务。

如何实现分区容忍性?

1、尽量使用异步取代同步操作,例如使用异步方式将数据从主数据库同步到从数据,这样结点之间能有效的实现松耦合。

2、添加从数据库结点,其中一个从结点挂掉其它从结点提供服务。

分布式分区容忍性的特点:

1、分区容忍性分是布式系统具备的基本能力

 

2、CAP组合方式


1、上边商品管理的例子是否同时具备 CAP呢?

 在所有分布式事务场景中不会同时具备CAP三个特性,因为在具备了P的前提下C和A是不能共存的。

比如:

下图满足了P即表示实现分区容忍:

本图分区容忍的含义是:

1)主数据库通过网络向从数据同步数据,可以认为主从数据库部署在不同的分区,通过网络进行交互。

2)当主数据库和从数据库之间的网络出现问题不影响主数据库和从数据库对外提供服务。

3)其一个结点挂掉不影响另一个结点对外提供服务。

如果要实现C则必须保证数据一致性,在数据同步的时候为防止向从数据库查询不一致的数据则需要将从数据库数据锁定,待同步完成后解锁,如果同步失败从数据库要返回错误信息或超时信息。

如果要实现A则必须保证数据可用性,不管任何时候都可以向从数据查询数据,则不会响应超时或返回错误信息。

通过分析发现在满足P的前提下C和A存在矛盾性。

2、CAP有哪些组合方式呢?

所以在生产中对分布式事务处理时要根据需求来确定满足CAP的哪两个方面。

1)AP

放弃一致性,追求分区容忍性和可用性。这是很多  分布式系统(分布式应用系统)设计时的选择。

例如1:商品管理(主备同步)

上边的商品管理,完全可以实现AP,前提是只要用户可以接受查询的到数据一定时间内 不是最新的即可。

通常实现AP都会保证 最终一致性,后面讲的BASE理论就是根据AP来扩展的,一些业务场景 比如:订单退款,今日退款成功,明日账户到账,只要用户可以接受在一定时间内到账即可。

 

例子2:小米抢购手机

典型的应用就如某米的抢购手机场景,可能前几秒你浏览商品的时候页面提示是有库存的,当你选择完商品准备下单的时候,系统提示你下单失败,商品已售完。这其实就是先在 A(可用性)方面保证系统可以正常的服务,然后在数据的一致性方面做了些牺牲,虽然多少会影响一些用户体验,但也不至于造成用户购物流程的严重阻塞。

 

例子3:跨行转账

下面是银行模拟转账业务,用户A(工商银行) 给 用户B(中国银行) 发起一笔转账业务 100元, 用户A调用

工商银行(ServiceA)-100元,在调用 中国银行(ServiceB)给用户B +100,由于ServiceA 调用ServiceB有延时,这时候用户A账号已经少了100元,但是由于延时原因,用户B在立马查询的时候发现账号此刻并未到账100元。 

这个场景在 用户提现,支付宝提现提示2小时到账  (其实是走CP)

Partition Tolerance 【可用性】 收到请求必须相应

同样还是上面银行转账的例子,用户A在工商银行系统-100,在中国银行给用户B加100的操作中,如果要保证用户B已经收到了100元才能查询,那么次数可以把 这个整个操作当做一个整体,只有全部完成的时候才能够对外提供服务。这时候就牺牲了可用性

所以在分布式环境中,CAP是不能够同时满足的。至于是选择CA CP AP,根据自己的业务需求

在银行系统中, 一致性最重要,数据不能错,CP微服务中,系统的可用性,尤其是分布式,可用性尤为重要,没有可用性是跑不起来的 AP
 

例如:

  • 同步调用HSF,为了追求可用性A,客户端会有超时时间
  • 异步消息MQ,就无法保证一致性P,但是可以保障可用性A

2)CP

放弃可用性,追求一致性和分区容错性,我们的zookeeper其实就是追求的强一致,又比如跨行转账,一次转账请求要等待双方银行系统都完成整个事务才算完成。

 

如果不要求A(可用),相当于每个请求都需要在服务器之间保持强一致,而P(分区)会导致同步时间无限延长(也就是等待数据同步完才能正常访问服务),一旦发生网络故障或者消息丢失等情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统

 

设计成CP的系统其实不少,最典型的就是分布式数据库,如Redis、HBase等。对于这些分布式数据库来说,数据的一致性是最基本的要求,因为如果连这个标准都达不到,那么直接采用关系型数据库就好,没必要再浪费资源来部署分布式数据库。

3)CA

放弃分区容忍性,即不进行分区,不考虑由于网络不通或结点挂掉的问题,则可以实现一致性和可用性。那么系统将不是一个标准的分布式系统,我们最常用的关系型数据就满足了CA。

上边的商品管理,如果要实现CA则架构如下:

 

主数据库和从数据库中间不再进行数据同步,数据库可以响应每次的查询请求,通过事务隔离级别实现每个查询请求都可以返回最新的数据。

3、总结


通过上面我们已经学习了CAP理论的相关知识,CAP是一个已经被证实的理论:一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容忍性(Partition tolerance)这三项中的两项。它可以作为我们进行架构设计、技术选型的考量标准。对于多数大型互联网应用的场景,结点众多、部署分散,而且现在的集群规模越来越大,所以节点故障、网络故障是常态,而且要保证服务可用性达到N个9(99.99..%),并要达到良好的响应性能来提高用户体验,因此一般都会做出如下选择:保证P和A,舍弃C强一致,保证最终一致性。

 

作者:攀博课堂Java编程 https://www.bilibili.com/read/cv11112175?from=search 出处:bilibili

4、分布式事务

4.1 概览-柔性事务

  https://blog.csdn.net/qq_35642036/article/details/89509519,https://blog.csdn.net/weixin_44543482/article/details/136262675

  柔性事务,是业内解决分布式事务的主要方案。所谓柔性事务,相比较与数据库事务中的ACID这种刚性事务来说,柔性事务保证的是“基本可用,最终一致。”这其实就是基于BASE理论,保证数据的最终一致性。

柔性事务的分类
柔性事务分为:两阶段型(三阶段)、补偿型、异步确保型、最大努力通知型。

  • 两阶段型 :分布式事务二阶段提交,对应技术上的 XA、JTA/JTS,这是分布式环境下事务处理的典型模式。
  • 补偿型 TCC 型事务(Try-Confirm-Cancel)可以归为补偿型。在 Try 成功的情况下,如果事务要回滚,Cancel 将作为一个补偿机制,回滚 Try 操作;TCC 各操作事务本地化,且尽早提交(没有两阶段约束);当全局事务要求回滚时,通过另一个本地事务实现“补偿”行为。 TCC 是将资源层的二阶段提交协议转换到业务层,成为业务模型中的一部分。
  • 异步确保型 :将一些有同步冲突的事务操作变为异步操作,避免对数据库事务的争用,如消息事务机制。
  • 最大努力通知型 :通过通知服务器(消息通知)进行,允许失败,有补充机制。

4.2 两阶段(三阶段型)

4.2.1 mysql事务的两阶段提交

1.引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
2.执行器生成这个操作的 binlog,并把 binlog 写入磁盘。
3.执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。

 

4.2.2 分布式事务的两阶段提交(2PC)

分布式事务的两阶段提交(Two-Phase Commit, 2PC)是一种确保在分布式系统中多个节点间数据一致性的重要协议。它主要用于跨多个数据库或者服务的事务处理,以达到类似单点事务的ACID(原子性、一致性、隔离性、持久性)特性。两阶段提交分为两个步骤:

第一阶段:准备阶段(Preparation Phase)

  1. 事务协调者(Coordinator) 发起一个事务提案向所有参与该事务的节点(称为参与者,Participants)发送“准备(Prepare)”请求,询问它们是否准备好提交事务。
  2. 每个参与者接收到“准备”请求后,会执行事务操作,比如锁定所需的资源,检查事务是否能成功执行,但不实际提交更改。如果参与者确定自己可以提交事务,它会向协调者回应一个“准备好(Ready)”的消息;如果不能提交(例如因资源冲突),则回应“中止(Abort)”。

第二阶段:提交阶段(Commit Phase)

  • 提交(Commit):

    1. 如果协调者在第一阶段收到所有参与者的“准备好”响应,它将发送“提交(Commit)”命令给所有参与者,指示它们正式提交事务,即永久性地更新其状态。
    2. 参与者接收到“提交”命令后,会将之前准备好的更改正式提交,释放资源锁,并向协调者发送“已提交(Committed)”的确认消息。
  • 中止(Abort):

    1. 如果在第一阶段有任何参与者回复“中止”,或者协调者在等待期间出现问题(如崩溃),协调者将发送“中止(Abort)”命令给所有参与者。
    2. 收到“中止”命令的参与者会撤销之前的所有准备工作,回滚事务,释放资源,并向协调者回复“已中止(Aborted)”。

两阶段提交保证了事务的原子性和一致性,但存在一些缺点,如性能瓶颈(特别是在Commit阶段的同步等待)、单点故障问题(协调者故障可能导致整个事务阻塞)以及潜在的长时间阻塞。因此,现代分布式系统中往往会采用改进的协议,如三阶段提交(3PC)、Paxos、Raft等,来提高容错性和效率

4.2.3 分布式事务的三阶段提交(3PC)

三阶段提交(Three-Phase Commit, 3PC)是对两阶段提交(2PC)协议的一种改进,旨在减少阻塞和提升容错性。3PC通过增加一个预提交阶段来减少参与者在等待最终提交决策时的不确定时间,从而降低了锁定资源的时间,提高了系统的并发性和响应速度。下面是3PC的三个阶段概览:

第一阶段:CanCommit(能力询问)

  1. 协调者 向所有参与者发送 CanCommit 请求,询问它们是否有能力执行即将发起的事务。
  2. 参与者评估本地情况(如资源可用性),如果可以参与事务,则回复 Yes(同意);若不可,则回复 No(拒绝)。
  3. 协调者收集所有参与者的响应,只有当所有参与者都回复 Yes 时,才继续到下一阶段;否则,协调者直接发送 Abort 指令终止事务。

第二阶段:PreCommit(预提交)

  1. 若第一阶段所有参与者同意,协调者向所有参与者发送 PreCommit 请求,要求参与者做好事务提交前的最终准备。
  2. 参与者在收到 PreCommit 后,执行事务的预提交操作,如锁定必要资源,但依然不提交事务,然后向协调者回复 ACK(确认)。
  3. 协调者等待所有参与者的 ACK。如果收到所有确认,进入第三阶段;如果有任何参与者失败或回复否定,协调者将发送 Abort 给所有参与者。

第三阶段:DoCommit(提交/中止)

  1. 提交:如果第二阶段所有参与者都预提交成功,协调者向所有参与者发送 DoCommit,指示它们正式提交事务。
  2. 参与者接收到 DoCommit 后,将事务永久提交,释放资源锁,并向协调者发送 已提交 的确认。
  3. 若第二阶段中有任何问题或协调者决定中止事务,它会发送 Abort,参与者接收到后将回滚事务并释放资源。

三阶段提交增强了系统的健壮性,减少了阻塞时间,但仍未完全解决协调者单点故障的问题,且增加了协议的复杂度。为了解决这些问题,进一步的优化和协议如Paxos和Raft等被提出和应用。

三阶段提交(3PC)相较于两阶段提交(2PC)主要解决了以下问题:

  1. 减少阻塞时间: 在2PC中一旦事务进入提交阶段,所有参与者都会阻塞等待协调者的指令,这可能导致长时间的等待。而3PC通过增加预提交阶段,参与者在收到PreCommit之后可以提前准备提交,但在未收到最终的DoCommit之前仍保持预提交状态。这样,即使协调者在此阶段发生故障,参与者也能基于预提交的结果更快地做出决策,减少阻塞时间。

  2. 提高容错性: 3PC在参与者收到PreCommit后会局部提交(预提交),即使之后协调者出现故障,参与者也可以根据预提交的状态自行决定提交或中止事务。例如,如果参与者在等待DoCommit时超时,它可以询问其他参与者来确定事务的整体状态,从而减少单点故障对整个事务的影响。

  3. 降低了单点故障影响: 虽然3PC并未完全消除单点故障问题,但它通过让参与者在预提交阶段有更大的自主性,减轻了协调者故障对事务处理流程的影响。参与者可以根据预提交阶段的信息和其他参与者的反馈来推断事务应如何进行,提高了系统的自我恢复能力。

综上所述,3PC通过细化提交过程,增加了事务处理的灵活性和系统的鲁棒性,从而改善了两阶段提交中的一些固有问题。然而,它也引入了额外的通信开销和复杂性,并且在某些场景下可能仍需额外机制来彻底解决单点故障问题。

4.3 TCC

3PC(Three-Phase Commit,三阶段提交)和TCC(Try-Confirm-Cancel,尝试-确认-取消)是两种不同的分布式事务处理机制,它们在设计目的和实现方式上都有所区别。

3PC(三阶段提交)

  • 目标:3PC是作为一种对两阶段提交(2PC)的改进而提出的,主要解决2PC中可能存在的阻塞和单点故障问题。
  • 过程:分为CanCommit、PreCommit和DoCommit三个阶段,通过增加预提交阶段来减少锁资源的时间,提高系统的并发性和响应速度,并试图在一定程度上降低协调者单点故障的影响。
  • 特点:仍然是一个集中式协调的过程,依赖于一个中心协调者来驱动事务的各个阶段。

TCC(Try-Confirm-Cancel)

  • 目标:TCC模式主要用于解决分布式事务的最终一致性问题,特别适用于微服务架构下的长事务处理
  • 过程:分为Try(预留资源)、Confirm(确认执行)和Cancel(取消执行)三个操作阶段。在Try阶段,业务系统会预先检查业务执行的条件并预留必须的资源;Confirm阶段则是真正执行业务;Cancel阶段则是Try阶段预留资源的释放操作。
  • 特点:TCC是一种业务侵入性的解决方案,需要业务代码的直接支持,每个参与者需要实现Try、Confirm和Cancel三个操作。它不依赖于一个中心协调者来完成事务,而是通过业务层面的逻辑来保证事务的原子性,提高了系统的解耦和容错能力。

总结来说,3PC和TCC都是为了解决分布式事务一致性问题,但它们的设计思路和应用场景有所不同。3PC是通过增加事务处理阶段来优化2PC的性能和容错性,而TCC则是通过业务层面的三次操作来实现最终一致性,更加适合复杂的微服务环境。

 

常见的交易下单举例:

  • 下单——>预扣资源(资源检查 如优惠、限购、资金工具)——>冻结资源——>创单成功——>下单减库存/付款减库存 (消息)——>扣减资源

4.2 举例

微服务架构中的分布式事务

https://github.com/changmingxie/tcc-transaction

随着传统的单体架的构微服务化,原本单体架构中不同模块,被拆分为若干个功能简单、松耦合的服务。
系统微服务化后,内部可能需要调用多个服务并操作多个数据库实现,服务调用的分布式事务问题变的非常突出。

比如支付退款场景需要从各分账方退回平台收益户(退分账),再退还给付款方。其中退分账阶段, 涉及从多个分账方(商家1收益户,商家2收益户,商家3收益户,平台手续费账户)扣款,这些账户分布在不同数据库, 比如商家3收益户扣款失败,其他成功扣款需要回滚,这里需要分布式事务保证一致性。 支付退款流程

如何解决

如何解决上面退分账中分布式事务问题呢? 选择使用tcc-transaction框架,执行流程如下:  (分布式事务-02:2PC 二阶段提交协议实现过程及原理https://cloud.tencent.com/developer/article/1995289

  • Try:
    商家1收益户->冻结分账金额
    商家2收益户->冻结分账金额
    商家3收益户->冻结分账金额
    平台手续费->冻结手续费
  • Try成功 => Confirm:
    商家1收益户->扣除分账金额
    商家2收益户->扣除分账金额
    商家3收益户->扣除分账金额
    平台手续费->扣除手续费
    平台收益户-> 增加金额(总分账金额+手续费)
  • Try失败 => Cancel:
    商家1收益户->解冻分账金额
    商家2收益户->解冻分账金额
    商家3收益户->解冻分账金额
    平台手续费->解冻手续费

工作原理

TCC原理

第一阶段:主业务服务分别调用所有从业务的 try 操作,并在活动管理器中登记所有从业务服务。当所有从业务服务的 try 操作都调用成功或者某个从业务服务的 try 操作失败,进入第二阶段。
第二阶段:活动管理器根据第一阶段的执行结果来执行 confirm 或 cancel 操作。
如果第一阶段所有 try 操作都成功,则活动管理器调用所有从业务活动的 confirm操作。否则调用所有从业务服务的 cancel 操作。
需要注意的是第二阶段 confirm 或 cancel 操作本身也是满足最终一致性的过程,在调用 confirm 或 cancel 的时候也可能因为某种原因(比如网络)导致调用失败,所以需要活动管理支持重试的能力,同时这也就要求 confirm 和 cancel 操作具有幂等性。

 

MQ分布式事务--本地消息表--基于消息的一致性

  1. 上游投递消息

  2. 下游获取消息

  3. 上游投递稳定性

  4. 下游接受稳定性

 

 

上游投递消息稳定性

如何保证数据一定能写入到队列,只有保障了投递的稳定性,才能保障流程的正常。

 上层应用在操作订单服务的时候,先将数据写入到临时数据表 Publisher中,另外一部分处理业务逻辑,这部分是一个事务。Publisher表中有一些软状态,成功和失败。表示是否成功写入到消息队列中。
队列数据可靠性

如何保证队列数据不丢失?RabbitMQ—集群&持久化

下游获取消息稳定性

如何保证下游一定获取到数据?Ack 就够了吗?

库存服务通过消息队列中拿到上游给的信息,但同时也有可能失败,比如库存服务宕机,或是网络超时等原因,这时候理应有从试、过期等机制,拿到数据后ACK将数据从消息队列中移除,避免消息队列有脏数据。


作者:MrWu
链接:https://www.jianshu.com/p/c89b4fba863f
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

posted on 2023-04-08 09:57  gogoy  阅读(377)  评论(0编辑  收藏  举报

导航