拓冰建站拓冰建站
首页 / 资讯中心 / 正文

10. 软件设计架构-分布式-分布式事务

文章目录前言一、分布式事务基础1. 什么是事务2. 本地事务3. 分布式事务4. 分布式事务的场景二、分布式事务解决方案三、二阶段提交1. 概述2. 处理流程3. 问题四、三阶段提交1. 概述2. 处理流程3. 问题五、补偿事务TCC1. 概述2. 工作流程3. 问题六、 通过消息队列实现1. 本地消息表异步确保2. MQ 事务消息七、 Saga事务模型八、 事务隔离1. 事务隔离级别2. MySQL的默认隔离级别九、 并发事务带来哪些问题前言事务‌指的就是一个操作单元在这个操作单元中的所有操作最终要保持一致的行为要么所有操作都成功要么所有的操作都被撤销。简单地说事务提供一种“要么什么都不做要么做全套”机制。一、分布式事务基础1. 什么是事务事务指的就是一个操作单元在这个操作单元中的所有操作最终要保持一致的行为要么所有操作都成功要么所有的操作都被撤销。简单地说事务提供一种“要么什么都不做要么做全套”机制。2. 本地事务数据库事务中的四大特性ACID:A原子性(Atomicity)一个事务中的所有操作要么全部完成要么全部不完成。C一致性(Consistency)在一个事务执行之前和执行之后数据库都必须处于一致性状态。I隔离性(Isolation)在并发环境中当不同的事务同时操作相同的数据时事务之间互不影响。D持久性(Durability)指的是只要事务成功结束它对数据库所做的更新就必须永久的保存下来。数据库事务在实现时会将一次事务涉及的所有操作全部纳入到一个不可分割的执行单元该执行单元中的所有操作要么都成功要么都失败只要其中任一操作执行失败都将导致整个事务的回滚。3. 分布式事务分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。简单的说就是一次大的操作由不同的小操作组成这些小的操作分布在不同的服务器上且属于不同的应用分布式事务需要保证这些小操作要么全部成功要么全部失败。本质上来说分布式事务就是为了保证不同数据库的数据一致性。4. 分布式事务的场景单体系统访问多个数据库一个服务需要调用多个数据库实例完成数据的增删改操作。多个微服务访问同一个数据库多个服务需要调用同一个数据库实例完成数据的增删改操作。多个微服务访问多个不同数据库多个服务需要调用不同数据库实例完成数据的增删改操作。二、分布式事务解决方案目前知道的有五种两阶段提交(2PC)三阶段提交(3PC)补偿事务(TCCTry-Confirm-Cancel)本地消息队列表(MQ)Sagas事务模型(最终一致性)三、二阶段提交两阶段提交2PC是分布式事务中最强大的事务类型之一。1. 概述两段提交就是分两个阶段提交第一阶段询问各个事务数据源是否准备好。第二阶段才真正将数据提交给事务数据源。为了保证该事务可以满足ACID就要引入一个协调者Cooradinator。其他的节点被称为参与者Participant。协调者负责调度参与者的行为并最终决定这些参与者是否要把事务进行提交。2. 处理流程阶段一a) 协调者向所有参与者发送事务内容询问是否可以提交事务并等待答复。b) 各参与者执行事务操作将 undo 和 redo 信息记入事务日志中但不提交事务。c) 如参与者执行成功给协调者反馈 yes否则反馈 no。阶段二如果协调者收到了参与者的失败消息或者超时直接给每个参与者发送回滚(rollback)消息否则发送提交(commit)消息。两种情况处理如下情况1当所有参与者均反馈 yes提交事务a) 协调者向所有参与者发出正式提交事务的请求即 commit 请求。b) 参与者执行 commit 请求并释放整个事务期间占用的资源。c) 各参与者向协调者反馈 ack(应答)完成的消息。d) 协调者收到所有参与者反馈的 ack 消息后即完成事务提交。情况2当有一个参与者反馈 no回滚事务a) 协调者向所有参与者发出回滚请求即 rollback 请求。b) 参与者使用阶段 1 中的 undo 信息执行回滚操作并释放整个事务期间占用的资源。c) 各参与者向协调者反馈 ack 完成的消息。d) 协调者收到所有参与者反馈的 ack 消息后即完成事务。3. 问题性能问题所有参与者在事务提交阶段处于同步阻塞状态占用系统资源容易导致性能瓶颈。可靠性问题如果协调者存在单点故障问题或出现故障提供者将一直处于锁定状态。数据一致性问题在阶段 2 中如果出现协调者和参与者都挂了的情况有可能导致数据不一致。优点尽量保证了数据的强一致适合对数据强一致要求很高的关键领域。其实也不能100%保证强一致。缺点实现复杂牺牲了可用性对性能影响较大不适合高并发高性能场景。四、三阶段提交1. 概述三阶段提交是在二阶段提交上的改进版本3PC最关键要解决的就是协调者和参与者同时挂掉的问题所以3PC把2PC的准备阶段再次一分为二这样三阶段提交。2. 处理流程阶段一a) 协调者向所有参与者发出包含事务内容的 canCommit 请求询问是否可以提交事务并等待所有参与者答复。b) 参与者收到 canCommit 请求后如果认为可以执行事务操作则反馈 yes 并进入预备状态否则反馈 no。阶段二协调者根据参与者响应情况有以下两种可能。情况1所有参与者均反馈 yes协调者预执行事务a) 协调者向所有参与者发出 preCommit 请求进入准备阶段。b) 参与者收到 preCommit 请求后执行事务操作将 undo 和 redo 信息记入事务日志中但不提交事务。c) 各参与者向协调者反馈 ack 响应或 no 响应并等待最终指令。情况2只要有一个参与者反馈 no或者等待超时后协调者尚无法收到所有提供者的反馈即中断事务a) 协调者向所有参与者发出 abort 请求。b) 无论收到协调者发出的 abort 请求或者在等待协调者请求过程中出现超时参与者均会中断事务。阶段三该阶段进行真正的事务提交也可以分为以下两种情况。情况 1所有参与者均反馈 ack 响应执行真正的事务提交a) 如果协调者处于工作状态则向所有参与者发出 do Commit 请求。b) 参与者收到 do Commit 请求后会正式执行事务提交并释放整个事务期间占用的资源。c) 各参与者向协调者反馈 ack 完成的消息。d) 协调者收到所有参与者反馈的 ack 消息后即完成事务提交。情况2只要有一个参与者反馈 no或者等待超时后协调组尚无法收到所有提供者的反馈即回滚事务。a) 如果协调者处于工作状态向所有参与者发出 rollback 请求。b) 参与者使用阶段 1 中的 undo 信息执行回滚操作并释放整个事务期间占用的资源。c) 各参与者向协调组反馈 ack 完成的消息。d) 协调组收到所有参与者反馈的 ack 消息后即完成事务回滚。3. 问题优点相比二阶段提交三阶段提交降低了阻塞范围在等待超时后协调者或参与者会中断事务。避免了协调者单点问题。阶段 3 中协调者出现问题时参与者会继续提交事务。缺点数据不一致问题依然存在当在参与者收到 preCommit 请求后等待 do commite 指令时此时如果协调者请求中断事务而协调者无法与参与者正常通信会导致参与者继续提交事务造成数据不一致。五、补偿事务TCC1. 概述TCC Try Confirm Cancel是服务化的二阶段编程模型采用的补偿机制TCC 其实就是采用的补偿机制其核心思想是针对每个操作都要注册一个与其对应的确认和补偿撤销操作。2. 工作流程它分为三个步骤Try 阶段主要是对业务系统做检测及资源预留。Confirm 阶段主要是对业务系统做确认提交Try阶段执行成功并开始执行Confirm阶段时默认 Confirm阶段是不会出错的。即只要Try成功Confirm一定成功。3.Cancel 阶段主要是在业务执行错误需要回滚的状态下执行的业务取消预留资源释放。举个例子假入你要向 老田 转账思路大概是 我们有一个本地方法里面依次调用步骤1、首先在 Try 阶段要先调用远程接口把 你 和 老田 的钱给冻结起来。2、在 Confirm 阶段执行远程调用的转账的操作转账成功进行解冻。3、如果第2步执行成功那么转账成功如果第二步执行失败则调用远程冻结接口对应的解冻方法 (Cancel)。3. 问题优点性能提升具体业务来实现控制资源锁的粒度变小不会锁定整个资源。数据最终一致性基于 Confirm 和 Cancel 的幂等性保证事务最终完成确认或者取消保证数据的一致性。可靠性解决了 XA 协议的协调者单点故障问题由主业务方发起并控制整个业务活动业务活动管理器也变成多点引入集群。缺点TCC 的 Try、Confirm 和 Cancel 操作功能要按具体业务来实现业务耦合度较高提高了开发成本。六、 通过消息队列实现1. 本地消息表异步确保本地消息表这种实现方式应该是业界使用最多的其核心思想是将分布式事务拆分成本地事务进行处理这种思路是来源于ebay。我们可以从下面的流程图中看出其中的一些细节基本思路就是消息生产方需要额外建一个消息表并记录消息发送状态。消息表和业务数据要在一个事务里提交也就是说他们要在一个数据库里面。然后消息会经过MQ发送到消息的消费方。如果消息发送失败会进行重试发送。消息消费方需要处理这个消息并完成自己的业务逻辑。此时如果本地事务处理成功表明已经处理成功了如果处理失败那么就会重试执行。如果是业务上面的失败可以给生产方发送一个业务补偿消息通知生产方进行回滚等操作。生产方和消费方定时扫描本地消息表把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑这种方案还是非常实用的。这种方案遵循BASE理论采用的是最终一致性笔者认为是这几种方案里面比较适合实际业务场景的即不会出现像2PC那样复杂的实现(当调用链很长的时候2PC的可用性是非常低的)也不会像TCC那样可能出现确认或者回滚不了的情况。优点 一种非常经典的实现避免了分布式事务实现了最终一致性。在 .NET中 有现成的解决方案。缺点 消息表会耦合到业务系统中如果没有封装好的解决方案会有很多杂活需要处理。2. MQ 事务消息有一些第三方的MQ是支持事务消息的比如RocketMQ他们支持事务消息的方式也是类似于采用的二阶段提交但是市面上一些主流的MQ都是不支持事务消息的比如 RabbitMQ 和 Kafka 都不支持。以阿里的 RocketMQ 中间件为例其思路大致为第一阶段Prepared消息会拿到消息的地址。 第二阶段执行本地事务第三阶段通过第一阶段拿到的地址去访问消息并修改状态。也就是说在业务方法内要想消息队列提交两次请求一次发送消息和一次确认消息。如果确认消息发送失败了RocketMQ会定期扫描消息集群中的事务消息这时候发现了Prepared消息它会向消息发送者确认所以生产方需要实现一个check接口RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。遗憾的是RocketMQ并没有 .NET 客户端。优点 实现了最终一致性不需要依赖本地数据库事务。缺点 实现难度大主流MQ不支持没有.NET客户端RocketMQ事务消息部分代码也未开源。七、 Saga事务模型Saga模式是一种分布式异步事务一种最终一致性事务是一种柔性事务有两种不同的方式来实现saga事务最流行的两种方式是一、事件/编排Choreography没有中央协调器没有单点风险时每个服务产生并聆听其他服务的事件并决定是否应采取行动。该实现第一个服务执行一个事务然后发布一个事件。该事件被一个或多个服务进行监听这些服务再执行本地事务并发布或不发布新的事件当最后一个服务执行本地事务并且不发布任何事件时意味着分布式事务结束或者它发布的事件没有被任何Saga参与者听到都意味着事务结束。处理流程说明订单服务保存新订单将状态设置为pengding挂起状态并发布名为ORDER_CREATED_EVENT的事件。支付服务监听ORDER_CREATED_EVENT并公布事件BILLED_ORDER_EVENT。库存服务监听BILLED_ORDER_EVENT更新库存并发布ORDER_PREPARED_EVENT。货运服务监听ORDER_PREPARED_EVENT然后交付产品。最后它发布ORDER_DELIVERED_EVENT。最后订单服务侦听ORDER_DELIVERED_EVENT并设置订单的状态为concluded完成。假设库存服务在事务过程中失败了。进行回滚库存服务产生PRODUCT_OUT_OF_STOCK_EVENT订购服务和支付服务会监听到上面库存服务的这一事件①支付服务会退款给客户。②订单服务将订单状态设置为失败。优点事件/编排是实现Saga模式的自然方式; 它很简单容易理解不需要太多的努力来构建所有参与者都是松散耦合的因为他们彼此之间没有直接的耦合。如果您的事务涉及2至4个步骤则可能是非常合适的。二、命令/协调orchestrator中央协调器负责集中处理事件的决策和业务逻辑排序。saga协调器orchestrator以命令/回复的方式与每项服务进行通信告诉他们应该执行哪些操作。订单服务保存pending状态并要求订单Saga协调器简称OSO开始启动订单事务。OSO向收款服务发送执行收款命令收款服务回复Payment Executed消息。OSO向库存服务发送准备订单命令库存服务将回复OrderPrepared消息。OSO向货运服务发送订单发货命令货运服务将回复Order Delivered消息。OSO订单Saga协调器必须事先知道执行“创建订单”事务所需的流程(通过读取BPM业务流程XML配置获得)。如果有任何失败它还负责通过向每个参与者发送命令来撤销之前的操作来协调分布式的回滚。当你有一个中央协调器协调一切时回滚要容易得多因为协调器默认是执行正向流程回滚时只要执行反向流程即可。优点避免服务之间的循环依赖关系因为saga协调器会调用saga参与者但参与者不会调用协调器。集中分布式事务的编排。只需要执行命令/回复(其实回复消息也是一种事件消息)降低参与者的复杂性。在添加新步骤时事务复杂性保持线性回滚更容易管理。如果在第一笔交易还没有执行完想改变有第二笔事务的目标对象则可以轻松地将其暂停在协调器上直到第一笔交易结束。八、 事务隔离1. 事务隔离级别SQL 标准定义了四个隔离级别READ-UNCOMMITTED(读取未提交) 最低的隔离级别允许读取尚未提交的数据变更可能会导致脏读、幻读或不可重复读。READ-COMMITTED(读取已提交) 允许读取并发事务已经提交的数据可以阻止脏读但是幻读或不可重复读仍有可能发生。REPEATABLE-READ(可重复读) 对同一字段的多次读取结果都是一致的除非数据是被本身事务自己所修改可以阻止脏读和不可重复读但幻读仍有可能发生。SERIALIZABLE(可串行化) 最高的隔离级别完全服从ACID的隔离级别。所有的事务依次逐个执行这样事务之间就完全不可能产生干扰也就是说该级别可以防止脏读、不可重复读以及幻读。隔离级别脏读不可重复读幻影读READ-UNCOMMITTED√√√READ-COMMITTED×√√REPEATABLE-READ××√SERIALIZABLE×××2. MySQL的默认隔离级别MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ可重读。我们可以通过 SELECT tx_isolation; 命令来查看mysqlSELECTtx_isolation;-----------------|tx_isolation|-----------------|REPEATABLE-READ|-----------------这里需要注意的是与 SQL 标准不同的地方在于 InnoDB 存储引擎在 REPEATABLE-READ可重读 事务隔离级别下使用的是Next-Key Lock 锁算法因此可以避免幻读的产生这与其他数据库系统(如 SQL Server) 是不同的。所以说InnoDB 存储引擎的默认支持的隔离级别REPEATABLEREAD可重读 已经可以完全保证事务的隔离性要求即达到了 SQL标准的 SERIALIZABLE(可串行化) 隔离级别。因为隔离级别越低事务请求的锁越少所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容)但是你要知道的是InnoDB 存储引擎默认使用REPEAaTABLE-READ可重读并不会有任何性能损失。InnoDB 存储引擎在分布式事务的情况下一般会用到SERIALIZABLE(可串行化)隔离级别。九、 并发事务带来哪些问题在典型的应用程序中多个事务并发运行经常会操作相同的数据来完成各自的任务多个用户对同一数据进行操作。并发虽然是必须的但可能会导致以下的问题。脏读Dirty read: 当一个事务正在访问数据并且对数据进行了修改而这种修改还没有提交到数据库中这时另外一个事务也访问了这个数据然后使用了这个数据。因为这个数据是还没有提交的数据那么另外一个事务读到的这个数据是“脏数据”依据“脏数据”所做的操作可能是不正确的。丢失修改Lost to modify: 指在一个事务读取一个数据时另外一个事务也访问了该数据那么在第一个事务中修改了这个数据后第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失因此称为丢失修改。 例如事务1读取某表中的数据A20事务2也读取A20事务1修改AA-1事务2也修改AA-1最终结果A19事务1的修改被丢失。不可重复读Unrepeatableread: 指在一个事务内多次读同一数据。在这个事务还没有结束时另一个事务也访问该数据。那么在第一个事务中的两次读数据之间由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况因此称为不可重复读。幻读Phantom read: 幻读与不可重复读类似。它发生在一个事务T1读取了几行数据接着另一个并发事务T2插入了一些数据时。在随后的查询中第一个事务T1就会发现多了一些原本不存在的记录就好像发生了幻觉一样所以称为幻读。不可重复读和幻读区别不可重复读的重点是修改比如多次读取一条记录发现其中某些列的值被修改幻读的重点在于新增或者删除比如多次读取一条记录发现记录增多或减少了。本文的引用仅限自我学习如有侵权请联系作者删除。参考知识
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门