铜陵做网站的公司,tk域名网站,网站建设优化推广教程,emlog怎么转换到WordPress一、概述
1.1 什么是分布式事务
事务我们都很熟悉#xff0c;事务提供一种机制将一个活动涉及的所有操作纳入到一个不可分割的执行单元#xff0c;组成这组操作的各个单元#xff0c;要么全部成功#xff0c;要么全部失败。
事务有四大特性#xff1a;
Atomic#xf…一、概述
1.1 什么是分布式事务
事务我们都很熟悉事务提供一种机制将一个活动涉及的所有操作纳入到一个不可分割的执行单元组成这组操作的各个单元要么全部成功要么全部失败。
事务有四大特性
Atomic原子性事务是一个不可分割的工作单元事务中的操作要么都发生要么都不发生Consistent一致性事务完成时必须使所有数据都保持一致状态Isolation隔离性并发事务所做的修改必须和其他事务所做的修改是隔离的Duration持久性事务完成之后对数据库中数据的改变是永久性的
为了解决传统单体服务架构带来的各种问题分布式系统会把一个应用系统拆分成可独立部署的多个服务。如果把单体架构服务器比做篮子那代码就是鸡蛋不要让所有鸡蛋别装在一个篮子里。在分布式事务中事务的参与者、支持事务的服务器、事务管理器分别位于不同的分布式系统的不同节点之上。我们都知道一次大的操作由不同的小操作组成而在分布式系统中这些小的操作分布在不同的服务器上且属于不同的应用分布式事务需要保证这些小操作要么全部成功要么全部失败。本质上来说分布式事务就是为了保证不同数据库的数据一致性。
二、理论基础
2.1 CAP理论
CAP定理又被叫作布鲁尔定理。对于设计分布式系统来说(不仅仅是分布式事务)的架构师来说CAP就是你的入门理论。
C (一致性)指写操作后的读操作可以读取到最新的数据状态当数据分布在多个节点上从任意结点读取的到数据都是最新的状态。如果在某个节点更新了数据在其他节点如果都能读取到这个最新的数据那么就称为强一致如果有某个节点没有读取到那就是分布式不一致。A (可用性)指任何事务操作都可以得到响应结果且不会出现响应超时或响应错误。。可用性的两个关键一个是合理的时间一个是合理的响应。合理的时间指的是请求不能无限被阻塞应该在合理的时间给出返回。合理的响应指的是系统应该明确返回结果并且结果是正确的。P (分区容错性)分布式系统的各个节点一般部署在不同的子网上当有台机器的网络出现了问题此时整体仍然能够对外提供服务这叫分区容忍性。分区容忍性是分布式系统具备的基本能力。
CAP中三者不能共有只能选择其中的两项
AP放弃一致性追求分区容忍性和可用性。大部分分布式系统设计时的选择。CP放弃可用性追求一致性和分区容错性。如Zookeeper。CA放弃分区容错性不考虑网络因素或结点挂掉的问题。关系型数据库就满足了CA但不是一个标准的分布式系统。
顺便一提CAP理论中是忽略网络延迟的也就是当事务提交时从节点A复制到节点B但是在现实中这个是明显不可能的所以总会有一定的时间是不一致。同时CAP中选择两个比如你选择了CP并不是叫你放弃A。因为P出现的概率实在是太小了大部分的时间你仍然需要保证CA。就算分区出现了你也要为后来的A做准备比如通过一些日志的手段是其他机器回复至可用。
2.2 BASE理论
BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent (最终一致性)三个短语的缩写。是对CAP中AP的一个扩展
基本可用分布式系统在出现故障时允许损失部分可用功能保证核心功能可用。如电商网站交易付款出现问题了商品依然可以正常浏览。软状态允许系统中存在中间状态这个状态不影响系统可用性这个状态不影响系统可用性如订单的支付中、“数据同步中”等状态待数据最终一致后状态改为“成功”状态。最终一致最终一致是指经过一段时间后所有节点数据都将会达到一致。如订单的支付中状态最终会变 为“支付成功”或者支付失败。 BASE和 ACID 是相反的它完全不同于ACID的强一致性模型而是通过牺牲强一致性来获得可用性并允许数据在一段时间内是不一致的但最终达到一致状态。
三、常见解决方案
3.1 2PC
2PCTwo-phase commit protocol二阶段提交它将整个事务流程分为两个阶段准备阶段prepare phase、提交阶段commit phase。 二阶段提交是一种强一致性设计2PC 引入一个事务协调者的角色来协调管理各参与者的提交和回滚二阶段分别指的是准备和提交两个阶段。
准备阶段协调者会给各参与者发送准备命令但是不会提交事务此时除提交以外所有都准备完了就差临门一脚。提交阶段 a、假如在第一阶段所有参与者都返回准备成功那么协调者则向所有参与者发送提交事务命令然后等待所有事务都提交成功之后返回事务执行成功。 b、假如在第一阶段有一个参与者返回失败那么协调者就会向所有参与者发送回滚事务的请求即分布式事务执行失败。
假如第二阶段提交失败的话可以分为两种情况
第一种是第二阶段执行的是回滚事务操作那么答案是不断重试直到所有参与者都回滚了不然那些在第一阶段准备成功的参与者会一直阻塞着。第二种是第二阶段执行的是提交事务操作那么答案也是不断重试因为有可能一些参与者的事务已经提交成功了这个时候只有一条路就是头铁往前冲不断的重试直到提交成功到最后真的不行只能人工介入处理。
优点原理简单实现很方便。
缺点 同步阻塞在阶段一里执行prepare操作会占用资源一直到整个分布式事务完成才会释放资源这个过程中如果有其他人要访问这个资源就会被阻塞住。 单点故障协调者是个单点如果协调者在commit出现故障那么其它参与者一直处于锁定状态。 事务状态丢失即使把协调者做成一个双机热备的一个协调者挂了自动选举其他的协调者出来但如果协调者挂掉的同时接收到commit消息的某个库也挂了此时即使重新选举了其它的协调者也不知道这个分布式事务当前的状态。 数据不一致问题在commit阶段当协调者向所有的参与者发送commit请求后发生了网络异常导致协调者在还未发完commit请求之前崩溃可能会导致只有部分的参与者接收到commit请求剩下没收到commit请求的参与者将无法提交事务也就可能导致数据不一致的问题。 总结2PC 是一种尽量保证强一致性的分布式事务因此它是同步阻塞的而同步阻塞就导致长久的资源锁定问题总体而言效率低并且存在单点故障问题在极端条件下存在数据不一致的风险。
3.2 3PC
3PC 包含了三个阶段分别是准备阶段、预提交阶段和提交阶段对应的英文就是CanCommit、PreCommit 和 DoCommit。
1CanCommit阶段事务管理器向各个数据库发送CanCommit消息然后各个库返回结果。这一阶段主要是确定分布式事务的参与者是否具备了完成commit的条件并不会执行事务操作。
2PreCommit阶段事务管理器根据数据库的反馈情况来决定是否继续执行事务的PreCommit操作。如果各个数据库都返回成功则进入PreCommit阶段事务管理器发送PreCommit消息给各个数据库相当于2PC里的阶段一执行各个SQL语句但不提交。如果有某个库对CanCommit消息返回了失败则TM发送abort消息给各个库结束这个分布式事务。
3DoCommit阶段如果各个库对PreCommit阶段都返回了成功那么发送DoCommit消息给各个库提交事务各个库如果都返回成功给事务管理器那么分布式事务成功。如果有个库对PreCommit返回的是失败或者超时一直没返回那么事务管理器认为分布式事务失败直接发abort消息给各个库进行回滚各个库回滚成功之后通知事务管理器分布式事务回滚。
看起来是把 2PC 的提交阶段变成了预提交阶段和提交阶段但是 3PC 的CanCommit阶段协调者只是询问参与者的自身状况比如你现在还好吗负载重不重而预提交阶段就是和 2PC 的准备阶段一样除了事务的提交该做的都做了。提交阶段和 2PC 的一样让我们来看一下图。
不管哪一个阶段有参与者返回失败都会宣布事务失败这和 2PC 是一样的当然到最后的提交阶段和 2PC 一样只要是提交请求就只能不断重试。
与2PC相比主要有两个改进点 1引入了CanCommit阶段。 2在DoCommit阶段各个库自己也有超时机制。如果一个库收到了PreCommit自己还返回成功了过了一段时间还没收到事务管理器发送的DoCommit消息或者是abort消息直接判定为事务管理器可能出故障了则会自己执行DoCommit操作提交事务。在3PC里面不会因为故障导致某个库一直锁住某个资源导致长时间的资源阻塞。
3PC 的阶段变更有什么影响:
首先准备阶段的变更成不会直接执行事务而是会先去询问此时的参与者是否有条件接这个事务因此不会一来就干活直接锁资源使得在某些资源不可用的情况下所有参与者都阻塞着。
预提交阶段的引入起到了一个统一状态的作用它像一道栅栏表明在预提交阶段前所有参与者其实还未都回应在预处理阶段表明所有参与者都已经回应了。假如你是一位参与者你知道自己进入了预提交状态那你就可以推断出来其他参与者也都进入了预提交状态。
但是多引入一个阶段也多一个交互因此性能会差一些而且绝大部分的情况下资源应该都是可用的这样等于每次明知可用执行还得询问一次。
参与者超时能带来什么样的影响
2PC 是同步阻塞的事务管理器挂在了提交请求还未发出去的时候是最伤的所有参与者都已经锁定资源并且阻塞等待着。
那么引入了超时机制参与者就不会傻等了如果是等待提交命令超时那么参与者就会提交事务了因为都到了这一阶段了大概率是提交的如果是等待预提交命令超时那该干啥就干啥了反正本来啥也没干。
然而超时机制也会带来数据不一致的问题比如在等待提交命令时候超时了参与者默认执行的是提交事务操作但是有可能执行的是回滚操作这样一来数据就不一致了。
3PC 相对于 2PC 做了一定的改进引入了参与者超时机制并且增加了预提交阶段使得故障恢复之后协调者的决策复杂度降低但整体的交互过程更长了性能有所下降并且还是会存在数据不一致问题。
所以 2PC 和 3PC 都不能保证数据100%一致因此一般都需要有定时扫描补偿机制。
3.3 TCC
TCC 指的是Try - Confirm - Cancel2PC 和 3PC 都是数据库层面的而 TCC 是业务层面的分布式事务一共分为三个阶段
try阶段这个阶段是对各个服务的资源做检测以及对资源进行锁定或者预留。confirm阶段这个阶段是在各个服务中执行实际的操作。cancel阶段如果有任何一个服务的业务方法执行出错那么这里就需要进行补偿就是执行已经执行成功业务逻辑的回滚操作。 其实从思想上看和 2PC 差不多都是先试探性的执行如果都可以那就真正的执行如果不行就回滚。比如说一个事务要执行A、B、C三个操作那么先对三个操作执行预留动作。如果都预留成功了那么就执行确认操作如果有一个预留失败那就都执行撤销动作。 TCC模型还有个事务管理者的角色用来记录TCC全局事务状态并提交或者回滚事务。
可以看到流程还是很简单的难点在于业务上的定义对于每一个操作你都需要定义三个动作分别对应Try - Confirm - Cancel。
适用场景 对一致性要求很高的系统的核心链路如支付、交易相关的场景。严格保证分布式事务要么全部成功要么全部自动回滚严格保证链路的正确性。而且最好各个业务执行的时间都比较短。
相对于 2PC、3PC TCC 适用的范围更大但是开发量也更大毕竟都在业务上实现而且有时候你会发现这三个方法还真不好写。不过也因为是在业务上实现的所以TCC可以跨数据库、跨不同的业务系统来实现事务。
3.4 本地消息表
此方案的核心是将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列再通过业务规则自动或人工发起重试。人工重试更多的是应用于支付场景通过对账系统对事后问题的处理。 假设有一个在线商城系统包含订单服务和库存服务它们运行在不同的服务器上因此需要使用分布式事务来保证订单和库存的一致性。当一个客户下单时订单服务会向库存服务发起扣减库存的请求。如果请求成功订单服务再创建订单并提交事务如果请求失败则订单服务回滚事务。
然而在分布式事务中网络故障、服务故障或其他因素可能导致事务提交或回滚失败从而导致订单和库存不一致。因此我们需要使用本地消息表来解决这个问题。
本地消息表是一个本地数据库表用于保存需要在分布式事务中执行的操作。当订单服务接收到客户的下单请求时它会将扣减库存的请求和创建订单的操作插入到本地消息表中然后立即提交事务。此时订单服务并不会向库存服务发起扣减库存的请求而是将扣减库存的请求作为消息发送到一个消息队列中。
库存服务从消息队列中读取消息并执行扣减库存的操作。如果扣减库存成功则将消息标记为已消费并将操作的结果保存到本地数据库中。如果扣减库存失败则将消息标记为未消费并在一定时间后重新发送消息。这样可以保证库存服务在故障恢复后能够重新处理消息。
通过本地消息表订单服务和库存服务可以保证操作的原子性从而避免了分布式事务中的问题。如果订单服务在创建订单时发生故障它可以从本地消息表中读取操作重新执行扣减库存的请求从而保证订单和库存的一致性。同样的如果库存服务在扣减库存时发生故障它可以从本地数据库中读取操作重新执行扣减库存的请求从而保证订单和库存的一致性。
3.5 消息队列
为了确保消息的可靠性通常情况下在完成一阶段的 DB 事务之后再发送消息但这种方式存在一定的风险因为业务程序很难保证 DB 事务提交后消息一定能成功发送。即使将发送失败的消息放到内存队列中稍后重试也不能完全保证消息发送的成功率达到100%。
为了解决这个问题RocketMQ 提供了半消息Half Msg机制即先发送一个半消息类似于 Prepare 操作这个半消息不会立即被投递给消费者而是等待本地事务执行的结果。如果本地事务执行成功则将半消息状态改为“Committing”表示消息已经发送并且事务已经提交。如果本地事务执行失败则将半消息状态改为“Rollback”表示消息已经发送但是事务需要回滚。
RocketMQ 接收到半消息后会向消息生产者发送一个“预提交”Pre-Commit请求。生产者需要根据请求返回“提交”或“回滚”操作。如果生产者返回“提交”操作则 RocketMQ 将消息状态改为“已提交”Committed并将消息发送给消费者。如果生产者返回“回滚”操作则 RocketMQ 将消息状态改为“已回滚”Rollbacked消息不会被投递给消费者。
这种方式可以确保消息发送的可靠性和一致性并且避免了传统方式的风险。但需要注意该机制存在单点故障和性能瓶颈等问题因此需要综合考虑使用的利弊。
对于 RocketMQ 消费者而言事务消息和非事务消息是没有区别的具体流程如下图 如果不顺利以上任何一步发生了异常会如何我们挨个看一下
如果在第1步出现异常导致半消息发送失败则本地数据库事务不会执行整个操作会失败。在此情况下数据库和消息的状态是一致的即都没有提交。如果在第2步发生异常或返回超时生产者认为操作已失败因此不会执行数据库操作进而无法继续进行后续操作。而另一方面Broker已经成功存储了半消息但却迟迟等不到后续的提交操作等待时间超时后Broker会向生产者发送询问询问该半消息是应提交还是回滚。此时生产者可以通过访问数据库来确认本地数据库事务的完成状态并回答Broker的询问从而使Broker得知如何处理该半消息。如果在第3步中数据库操作失败生产者可以在第4步告知Broker回滚半消息或者报告状态为“未知”让Broker稍后通过回查决定是提交还是回滚该半消息。如果在第4步提交或回滚半消息失败Broker会在一段时间后发起回查与第2步异常情况类似。如果在第5、6、7步回查失败即回查发生异常或者回查仍然返回“未知”或回查失败RocketMQ会在稍后重新调度该消息最多回查15次。
3.6 最大努力通知
最大努力通知方案的核心在于最大努力通知服务这个服务的核心在于根据上游服务定义的重试规则对调用事变的消息重试几次最大努力尝试调用成功。 它跟可靠消息服务的区别在于
可靠消息服务会保证如果下游服务执行不成功会一直不停的重试直到下游服务执行成功为止最终达到数据一致性但是中间可能有很长的一段时间数据是不一致的。最大努力通知服务如果一次请求没成功那么就将消息存到数据库里去同时记录下它的重试规则以及上一次重试的时间是第几次重试然后开启一个后台线程进行扫描每次扫出来就根据规则去重新调用下游服务。