转载声明:文章来源https://blog.csdn.net/weixin_28788561/article/details/140941128
CAP理论
CAP理论是分布式系统领域的一个基本概念,描述了分布式数据存储系统在设计和实现中的三个核心属性:一致性(Consistency)、可用性(Availability)和分区容错性(Partition Tolerance)。
一致性(Consistency):
同一时间, 从系统不同结点读取同一份数据, 结果应该是相同的;
一致性保证了数据在多个节点之间是同步的,无论从哪个节点读取数据,结果都是相同的。
可用性(Availability):
每次请求都能收到响应。即使有部分节点发生故障,系统仍然能够响应请求。
可用性保证了系统始终可用,并且总是可以接收并处理请求。
分区容错性(Partition Tolerance):
即使在网络分区(节点之间的网络通信失败)的情况下系统能够继续运行并响应请求。
分区容错性保证了系统能够处理结点之间通信失败带来的问题,继续运行并维持服务可用。
CAP定理的核心结论是,在一个分布式数据存储系统中,最多只能同时满足三个特性中的两种。
CP(Consistency and Partition Tolerance):
系统在网络分区时保持一致性,但可能牺牲可用性。
当网络分区发生时,为了保证一致性,系统可能拒绝请求,导致部分服务不可用。
Zookeeper的服务注册中心( 大数据领域, 管理 Hadoop, Hive...的工具, 提供分布式配置服务, 同步服务) 就是 CP 系统
AP(Availability and Partition Tolerance):
系统在网络分区时保持可用性,但可能牺牲一致性。
当网络分区发生时,系统仍然会响应请求,但可能导致不同节点的数据不一致。
Eureka, Redis就是AP系统;
CA(Consistency and Availability):
系统在正常运行时保持一致性和可用性,但一出现网络分区行为就不可预期。
因为在广域网(如互联网)中,网络分区是不可避免的, 所以分布式环境中不考虑 CA 系统;
刚性事务
定义: 追求数据的强一致性, 遵循事务的 ACID 原则, 遵循 CP 模型;
强一致的思想是各个分支事务执行完不要提交, 等待所有分支事务都有了结果, 再统一提交或回滚;
代表: 两阶段提交 2PC;
2PC
X/Open组织定义了解决分布式事务问题的一套规范, 即XA规范; 2PC协议实现了XA规范;
它通过将事务分为两个阶段来协调多个节点,使所有参与节点要么全部提交事务,要么全部回滚事务。
由一个协调者(Coordinator)和多个参与者(Participants)组成。
协调者负责管理事务的提交过程,确保所有参与者都达成一致。
协议分为两个阶段:准备阶段(Prepare Phase)和提交阶段(Commit Phase)。
2PC流
在准备阶段,协调者向所有参与者发送prepare请求,驱动参与者执行本地事务。
协调者发送准备请求:
协调者向所有参与者发送prepare请求,询问是否可以准备提交事务。
协调者等待所有参与者的响应。
参与者响应准备请求:
每个参与者执行本地事务操作,但不提交
如果参与者可以提交事务,则返回vote-commit给协调者。
如果参与者不能提交事务(例如遇到错误或冲突),则返回vote-abort给协调者。
所有参与者响应后, 进入提交阶段,根据参与者的响应,协调者决定是否提交或回滚事务。
协调者决定提交或回滚:
如果所有参与者都返回vote-commit,协调者发送commit消息给所有参与者,指示它们提交事务。
如果有任何参与者返回vote-abort,协调者发送abort消息给所有参与者,指示它们回滚事务。
参与者提交或回滚事务:
如果接收到commit消息,参与者提交事务并释放资源。
如果接收到abort消息,参与者回滚事务并释放资源。
可以看出, 当前处于两阶段中的哪个阶段, 数据库是可以感知到的; 对比后面的 TCC , TCC也是两阶段, 但是是在业务层面实现的两阶段, 数据库无感;
2PC缺陷
优点就是强一致, 实现简单, 这里说一下缺点;
阻塞问题:
参与者在准备阶段会锁定数据库中自己所涉及的记录, 到参与者提交成功才会释放;
这期间要等待协调者收集所有参与者的响应并进行决策, 这可能导致数据库资源长时间被锁定;
性能开销:
两阶段提交协议需要多次网络通信和等待参与者的响应,导致性能开销较大。
数据库支持:
需要数据库支持2PC协议, 常用的关系型数据库基本支持, 非关系型数据库基本不支持;
BASE理论
接近于CAP理论中的 AP, 强调系统的可用性和一致性, 但是采取了一种比较宽松的策略, 只要保证基本可用和最终一致;
与CAP理论相比,BASE理论更注重实际应用和用户体验,通过容忍短暂的不一致来实现系统的高可用性和分区容错性。
将基于BASE理论的分布式事务解决方案称为柔性事务;
基本可用(Basically Available):
系统保证在大多数情况下是可用的,但不需要保证完全可用。
在出现部分故障或网络分区时,系统可以降级服务,提供部分功能或延迟响应,而不是完全不可用。
软状态(Soft State):
系统中的状态可以在不影响整体系统运行的情况下变化,即状态不需要总是保持一致。
允许数据在不同节点之间存在暂时的不一致。
最终一致性(Eventual Consistency):
系统保证在没有新的更新操作后,经过一段时间,所有节点的数据最终会达到一致。
不要求强一致性(每次读取都能获得最新的数据),而是允许读取到旧的数据,但保证在一定时间内数据会完成同步。
基于BASE理论实现的分布式事务解决方案称为柔性事务, 追求的是最终一致性, 允许暂时存在不一致的情况;
柔性事务之TCC
简介
同样是两阶段提交, 但是把两阶段拿到业务层面, 不需要数据库支持;
Seata 就有 TCC 模式的实现方案;
划分出三个阶段或者说接口 Try, Confirm, Cancel, 参与分布式事务的业务需要实现这三个接口。
Try(尝试阶段):
在这个阶段,每个参与者执行初步操作,预留必要的资源;
如果任何一个参与者的 Try 操作失败,整个分布式事务将不会进入Confirm阶段,而是直接进入Cancel阶段。
Confirm(确认阶段):
如果所有参与者的Try操作都成功,分布式事务进入Confirm阶段。
在这个阶段,每个参与者完成业务操作,将预留的资源进行正式的变更。
因为有重试机制, 所以 Confirm 操作必须是幂等的
Cancel(取消阶段):
如果任何一个参与者的Try操作失败,事务进入Cancel 阶段。
在这个阶段,每个参与者撤销Try阶段的预留操作,释放预留的资源。
因为有重试机制, Cancel操作也必须是幂等的。
举例
假设一个转账请求, A账户转账给B账户100元; 由增加余额和扣减余额两个服务实现; 如果没有分布式事务, 两个服务不能保证都成功或都失败, 可能出现扣减成功但增加失败的情况;
在账户表中设置一个额外的字段, 冻结资金;
在 Try 阶段中, 扣减服务检查A账户是否存在, 检查A账户余额是否够100, 修改A账户的冻结资金为100, 将A账户余额扣减100;
增加服务检查B账户是否存在, 修改 B 账户冻结资金为100;
这几步操作任意一步失败, 都认为 Try 失败;
在 Confirm 阶段, 扣减服务将账户 A 的冻结余额置零, 增加服务将账户 B 的冻结资金加到余额中;
Cancel 阶段, 扣减服务将冻结金额加回到 A 的账户余额, 增加服务将 B 的冻结资金清零;
Try, Confirm, Cancel 的逻辑, 都由开发者自己实现;
Try 由开发者自己调用, Confirm 和 Cancel 由TCC框架自动调用;
流程
开启分布式事务(使用注解等方式, 框架会自动开启事务)
在方法内部由开发者自己去调用不同服务的 Try 接口; 然后开发者就可以不用管了;
如果任意一个 Try 失败, 协调器自动调用所有成功服务的 Cancel 接口;
如果所有 Try 都成功, 协调器自动调用所有服务的 Confirm 接口;
重试
如果在二阶段调用 Confirm 或者 Cancel 因为网络等原因出现失败的情况, 那么会不断地进行重试;
所以 Confirm 和 Cancel 都需要具有幂等性;
对比2PC
TCC 不需要数据库支持两阶段协议, 是业务层面的两阶段, 非关系型数据库也能用, 甚至跨数据库也没关系; 而 2PC 需要数据库支持;
不会导致资源长时间被锁定, 性能好;
缺陷
不是强一致;
分布式事务的代码侵入业务, 耦合性很强, 一旦业务逻辑发生改变, 分布式事务代码也需要改变;
Confirm 和 Cancel 需要实现幂等性, 比较复杂;
柔性事务之本地消息表
借助于消息队列和本地消息表, 实现分布式事务;
一致性保证
如果本地消息表中一直是 (库存已扣减, 订单未生成), 就会一直发生成订单的消息给订单服务, 由此来尽力保证都提交;
如果生成订单失败, 怎么保证都失败? 可以对已经生成超过一定时间的(库存已扣减, 订单未生成) 的消息记录, 放弃发送生成消息, 并且根据消息内容添加库存;
缺陷
需要扫描本地消息表, 随着分布式事务的累计, 本地消息表会越来越大, 扫描也越来越慢, 不适合高并发的场景;
需要由消息队列保证消息投递的可靠性;
柔性事务之RocketMQ
事务消息 | RocketMQ (apache.org)
实现了一个两阶段提交的消息机制:
Sender, 即上游服务, Subscriber, 即下游服务;
Server, 即消息队列;
LocalTransaction 为上游服务的本地事务;
过程
1.上游服务将 Half消息(半事务消息) 发送至消息队列。
2.消息队列将消息持久化成功之后,向上游服务返回Ack确认消息已经发送成功,此时 Half消息 被标记为"暂不能投递"
3.上游服务收到确认之后, 开始执行本地事务逻辑。
4.上游服务根据本地事务执行结果向服务端提交 Commit 或 Rollback 消息(称为二次确认) ,消息队列收到后:
如果是 Commit:消息队列将半事务消息标记为可投递,并投递给下游服务, 相当于把这条消息 Commit 了。
如果是 Rollback:消息队列不会投递半事务消息, 相当于把这条消息 Rollback了。
5.消息队列 将 Half消息投递给下游服务, 并且这个投递会基于 RocketMQ 的重试机制; 消费重试 | RocketMQ (apache.org)
6.若消息队列超过一定时间 ( 回查的间隔时间 ) 未收到上游服务提交的二次确认结果,消息队列将对上游服务的一个实例发送回查消息。回查的间隔时间和最大回查次数,都可以配置;
7.上游服务收到回查消息后,需要检查对应消息的本地事务执行的最终结果。
8.上游服务根据检查到的本地事务的最终状态提交二次确认,消息队列对其进行处理。
帖子还没人回复快来抢沙发