【校招VIP】分布式事务的多种实现方案

9小时前 收藏 0 评论 0 java开发

【校招VIP】分布式事务的多种实现方案

转载声明:文章来源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.上游服务根据检查到的本地事务的最终状态提交二次确认,消息队列对其进行处理。

C 0条回复 评论

帖子还没人回复快来抢沙发