分布式事务
原生的事务提交:
在多线程环境下,线程之间的隔离性不能保证。
分布式环境的一致性问题:
原因:服务与服务之间没有办法通信。
解决方案:
CAP理论
强一致性(Consistency
),可用性(Avilable
),分区容错性(Tolerance of network Partition
)
可用性:
可用性:读写操作在单台服务器出问题后,在其他服务器上依然能够完成读写操作;
重点在于:某个读写操作在出问题的机器上不能读写了,但是在其他机器可以完成;
一致性:
一致性就是数据保持一致,在分布式系统中,可以理解为多个节点中数据的值是一致的。
一致性又可以分为强一致性与弱一致性 。 1.强一致性
强一致性可以理解为在任意时刻,所有节点中的数据是一样的。同一时间点,你在节点A中获取到key1的值与在节点B中获取到key1的值应该都是一样的。 2.弱一致性
弱一致性包含很多种不同的实现,分布式系统中广泛实现的是最终一致性。 3.最终一致性
所谓最终一致性,是弱一致性的一种特例,保证用户最终能够读取到某操作对系统特定数据的更新。但是随着时间的迁移,不同节点上的同一份数据总是在向趋同的方向变化。也可以简单的理解为在一段时间后,节点间的数据会最终达到一致状态。对于最终一致性最好的例子就是DNS系统,由于DNS多级缓存的实现,所以修改DNS记录后不会在全球所有DNS服务节点生效,需要等待DNS服务器缓存过期后向源服务器更新新的记录才能实现。
分区容错性:
单台服务器,或多台服务器出问题(主要是网络问题)后,正常服务的服务器依然能正常提供服务,并且满足设计好的一致性和可用性;
重点在于:部分服务器因网络问题,业务依然能够继续运行;
结论:
BASE理论:
BASE 理论是对 CAP 中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。
BA:
Basic Availability
基本业务可用性(支持分区失败)S:
Soft state
柔性状态(状态允许有短时间不同步,异步)E:
Eventual consistency
最终一致性(最终数据是一致的,但不是实时一致)
原子性(A)与持久性(D)必须根本保障
为了可用性、性能与降级服务的需要,只有降低一致性( C ) 与 隔离性( I ) 的要求
基于可靠消息的最终一致性方案
消息中间件
队列消息模型的特点:
1、消息生产者将消息发送到Queue中,然后消息消费者监听Queue并接收消息;
2、消息被确认消费以后,就会从Queue中删除,所以消息消费者不会消费到已经被消费的消息;
3、Queue支持存在多个消费者,但是对某一个消息而言,只会有一个消费者成功消费。
4、消息是局部有序的;
从生产到消费需要经历的环节:
① Producer生成消息并发送给MQ(同步、异步);
② MQ接收消息并将消息数据持久化到消息存储(持久化操作为可选配置);
③ MQ向Producer返回消息的接收结果(返回值、异常);
④ Consumer监听并消费MQ中的消息;
⑤ Consumer获取到消息后执行业务处理;
⑥ Consumer对已成功消费的消息向MQ进行ACK确认(确认后的消息将从MQ中删除)。
消息发送和消息投递的不可靠性
分布式环境下,需要通过网络进行通讯,就引入了数据传输的不确定性
消息发送一致性
消息发送一致性:是指产生消息的业务动作和消息发送的一致。
(也就是说,如果业务操作成功,那么由这个业务操作锁产生的消息一定要成功投递出去,否则就是丢消息)
保证消息的可靠性:要保证生产者一定要能够将消息发送到消息队列中。即1、2、3步不能出问题。
消息发送一致性如何保障?
1、如果业务操作成功,执行消息发送前应用故障,消息发送不出去,导致消息丢失(订单系统和会计系统的数据不一致);
2、如果业务操作成功,应用成功,但是消息系统故障或者网络故障,也会导致消息发送不出去(订单系统和会计系统的数据不一致)
消息发送流程:
1.消费者接收到消息,业务处理完成后应用出现问题,消息中间件不知道消息处理结果,会重新投递消息;
2.消费者接收到消息,业务处理完成后网络出现问题,消息中间件收不到消息处理结果,会重新投递消息;
3.消费者接收到消息,业务处理时间过程,消息中间件因消息超时未确认,会再次投递消息;
4.消费者接收消息,业务处理完成,消息中间件问题导致收不到消息处理结果,消息会重新投递;
5.消费者接收到消息,业务处理完成,消息中间件受到消息处理结果,但是由于消息存储故障导致消息没能成功确认,消息会再次投递;
因为上面的过程不成功就会不断重试,所以:消费者对于消息的业务处理要实现幂等
保证可靠消息的流程:
流程分析:
1.消息发送一致性正向流程
2.消息发送一致性异常处理流程
3.消息投递(消费)的正向流程
4.消息投递(消费)的异常流程
弊端/局限:
1.与具体的业务场景绑定,消息处理的业务耦合度高;
2.消息数据与业务数据同库,占用业务系统资源;
3.只有事务的提交,没有回滚。即这个场景是一定要消费成功的。如果消费是失败的就会一直重试消耗系统资源。不具有回滚的功能。
可靠消息的优化方案:
提高可用性的消息处理系统:
优点:
1、消息服务独立部署,独立维护。
2、消息存储可以按需选择不同的数据库来实现。
3、消息服务可以被相同的使用场景共用,降低重复建设消息服务的成本。
4、降低了业务系统与消息服务间的耦合,有利于系统的拓展维护
弊端/局限:
1、一次消息发送需要两次请求。
2、主动方应用系统需要实现业务操作状态查询接口
2PC&3PC
2PC:两阶段提交
二阶段提交(Two-phaseCommit)是指,使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法(Algorithm)。通常,二阶段提交也被称为是一种协议(Protocol))。在分布式系统中,每个节点虽然可以知晓自己的操作时成功或者失败,却无法知道其他节点的操作的成功或失败。当一个事务跨越多个节点时,为了保持事务的ACID特性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。因此,二阶段提交的算法思路可以概括为:参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。 所谓的两个阶段是指:第一阶段:准备阶段(投票阶段)和第二阶段:提交阶段(执行阶段)。
准备阶段
事务协调者(事务管理器)给每个参与者(资源管理器)发送Prepare消息,每个参与者要么直接返回失败(如权限验证失败),要么在本地执行事务,写本地的redo和undo日志,但不提交,到达一种“万事俱备,只欠东风”的状态。
可以进一步将准备阶段分为以下三个步骤:
1)协调者节点向所有参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者节点的响应。
2)参与者节点执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志。(注意:若成功这里其实每个参与者已经执行了事务操作)
3)各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个”同意”消息;如果参与者节点的事务操作实际执行失败,则它返回一个”中止”消息。
提交阶段
如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据协调者的指令执行提交或者回滚操作,释放所有事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源)
接下来分两种情况分别讨论提交阶段的过程。
当协调者节点从所有参与者节点获得的相应消息都为”同意”时:
提交阶段的两种情况(都成功和任意失败):
如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据协调者的指令执行提交或者回滚操作,释放所有事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源)
接下来分两种情况分别讨论提交阶段的过程。
当协调者节点从所有参与者节点获得的相应消息都为”同意”时:
1)协调者节点向所有参与者节点发出”正式提交(commit)”的请求。
2)参与者节点正式完成操作,并释放在整个事务期间内占用的资源。
3)参与者节点向协调者节点发送”完成”消息。
4)协调者节点受到所有参与者节点反馈的”完成”消息后,完成事务。
如果任一参与者节点在第一阶段返回的响应消息为”中止”,或者协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:
1)协调者节点向所有参与者节点发出”回滚操作(rollback)”的请求。
2)参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。
3)参与者节点向协调者节点发送”回滚完成”消息。
4)协调者节点受到所有参与者节点反馈的”回滚完成”消息后,取消事务。
不管最后结果如何,第二阶段都会结束当前事务。
2PC弊端:
同步阻塞
超时导致数据不一致
二阶段提交看起来确实能够提供原子性的操作,但是不幸的事,二阶段提交还是有几个缺点的:
1、同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
2、单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
3、数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。
4、二阶段无法解决的问题:协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
由于二阶段提交存在着诸如同步阻塞、单点问题、脑裂等缺陷,所以,研究者们在二阶段提交的基础上做了改进,提出了三阶段提交。
3PC
三阶段提交(Three-phase commit),也叫三阶段提交协议(Three-phase commit protocol),是二阶段提交(2PC)的改进版本。
与两阶段提交不同的是,三阶段提交有两个改动点。
1、引入超时机制。同时在协调者和参与者中都引入超时机制。
2、在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。
也就是说,除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit
、PreCommit
、DoCommit
三个阶段。
CanCommit阶段
3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。
1.事务询问:协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。
2.响应反馈:参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则反馈No
PreCommit阶段
协调者根据参与者的反应情况来决定是否可以记性事务的PreCommit操作。根据响应情况,有以下两种可能。 假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行。
1.发送预提交请求 协调者向参与者发送PreCommit请求,并进入Prepared阶段。
2.事务预提交 参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。
3.响应反馈 如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。
假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。
1.发送中断请求 协调者向所有参与者发送abort请求。
2.中断事务 参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。
doCommit阶段
该阶段进行真正的事务提交,也可以分为以下两种情况。
执行提交
1.发送提交请求 协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。
2.事务提交 参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。
3.响应反馈 事务提交完之后,向协调者发送Ack响应。
4.完成事务 协调者接收到所有参与者的ack响应之后,完成事务。
中断事务
协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。
1.发送中断请求 协调者向所有参与者发送abort请求
2.事务回滚 参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。
3.反馈结果 参与者完成事务回滚之后,向协调者发送ACK消息
4.中断事务 协调者接收到参与者反馈的ACK消息之后,执行事务的中断。
在doCommit阶段,如果参与者无法及时接收到来自协调者的doCommit或者rebort请求时,会在等待超时之后,会继续进行事务的提交。(其实这个应该是基于概率来决定的,当进入第三阶段时,说明参与者在第二阶段已经收到了PreCommit请求,那么协调者产生PreCommit请求的前提条件是他在第二阶段开始之前,收到所有参与者的CanCommit响应都是Yes。(一旦参与者收到了PreCommit,意味他知道大家其实都同意修改了)所以,一句话概括就是,当进入第三阶段时,由于网络超时等原因,虽然参与者没有收到commit或者abort响应,但是他有理由相信:成功提交的几率很大。)
2PC与3PC的区别
相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。
TCC分布式事务框架
TCC型分布式事务是2PC
两阶段提交的方式实现的。
一个完整的 TCC 业务由一个主业务服务和若干个从业务服务组成,主业务服务发起并完成整个业务活动,TCC 模式要求从服务提供三个接口:Try
、Confirm
、Cancel
。
Try
:尝试执行业务
完成所有业务检查(一致性);
预留必须业务资源(准隔离性);
Confirm
:确认执行业务
真正执行业务;
不作任何业务检查;
只使用 Try 阶段预留的业务资源;
Confirm 操作满足幂等性;
Cancel
:取消执行业务
释放 Try 阶段预留的业务资源;
Cancel 操作满足幂等性;
整个 TCC 业务分成两个阶段完成
第一阶段:try 操作
主业务服务分别调用所有从业务的 try 操作,并在活动管理器中登记所有从业务服务。当所有从业务服务的 try 操作都调用成功或者某个从业务服务的 try 操作失败,进入第二阶段。
第二阶段:confirm 或 cancel
活动管理器根据第一阶段的执行结果来执行 confirm 或 cancel 操作。
如果第一阶段所有 try 操作都成功,则活动管理器调用所有从业务活动的 confirm操作。否则调用所有从业务服务的 cancel 操作。
需要注意的是第二阶段 confirm 或 cancel 操作本身也是满足最终一致性的过程,在调用 confirm 或 cancel 的时候也可能因为某种原因(比如网络)导致调用失败,所以需要活动管理支持重试的能力,同时这也就要求 confirm 和 cancel 操作具有幂等性。
在补偿模式中一个比较明显的缺陷是,没有隔离性。从第一个工作服务步骤开始一直到所有工作服务完成(或者补偿过程完成),不一致是对其他服务可见的。另外最终一致性的保证还充分的依赖了协调服务的健壮性,如果协调服务异常,就没法达到一致性。
TCC模式在一定程度上弥补了上述的缺陷,在TCC模式中直到明确的confirm动作,所有的业务操作都是隔离的(由业务层面保证)。另外工作服务可以通过指定 try 操作的超时时间,主动的 cancel 预留的业务资源,从而实现自治的微服务。
TCC模式和补偿模式一样需要需要有协调服务和工作服务,协调服务也可以作为通用服务一般实现为框架。与补偿模式不同的是 TCC 服务框架不需要记录详细的业务流水,完成 confirm 和 cancel 操作的业务要素由业务服务提供。
TCC业务处理的特点:
Try Confirm Cancle 如何实现A转账给B的呢?
//尝试方法
function try(){
//记录日志
todo save A 转出了 100 元
todo save B 转入了 100 元
//执行转账
update amount set balacne = balacne-100 where id = 1
update amount set balacne = balacne+100 where id = 2
}
//确认方法
function confirm(){
//清理日志
clean save A 转出了 100 元
clean save B 转入了 100 元
}
//取消方法
function cancle(){
//加载日志
load log A
load log B
//退钱
update amount set balacne = balacne+100 where id = 1
update amount set balacne = balacne-100 where id = 2
}
特点:
该模式对代码的嵌入性高,要求每个业务需要写三种步骤的操作。
该模式对有无本地事务控制都可以支持使用面广。
数据一致性控制几乎完全由开发者控制,对业务开发难度要求高。
自己的理解:TCC中事务的实现有一个提前的处理(Try阶段)。比如:用户购买商品会先将商品费用从用户扣除(资源预减),保存进订单表中,修改状态;业务执行没有异常,进入comfirm阶段,修改状态,这个时候才将订单中的预减的钱加到商家的余额当中;如果其中某个业务逻辑出现异常,回滚的过程是自己的代码实现的:就是将原来预减的金额重新加到买家中,卖家的账户没有变化。库存中的变化是先减,再操作,回滚就是让库存回补。提前的操作就是有一个记录做中转,这里是订单。
TX-LCN分布式事务框架
LCN并不生产事务,LCN只是本地事务的协调工作。
TXC逆向SQL
特点:
该模式同样对代码的嵌入性低。
该模式仅限于对支持SQL方式的模块支持。
该模式由于每次执行SQL之前需要先查询影响数据,因此相比LCN模式消耗资源与时间要多。
该模式不会占用数据库的连接资源,但中间状态可见
LCN代理连接
特点:
该模式对代码的嵌入性低。
该模式仅限于本地存在连接对象且可通过连接对象控制事务的模块。
该模式下的事务提交与回滚是由本地事务方控制,对于数据一致性上有较高的保障。
该模式缺陷在于代理的连接需要随事务发起方一共释放连接,增加了连接占用的时间。
超时机制
当业务模块在接受到事务请求,并完成响应以后会自动加入定时任务,等待TM通知,若TM迟迟不通知则触发TC主动请求的状况,若TC没有请求到数据就记录补偿(回滚事务)。
TM清理机制
TM全局都在记录着事务的状态消息,只有当TM确认完全都通知到了TC模块才能清除事务信息,不然将永久保存。
一些特殊的情况介绍:1、通知事务的时候通知不到的情况。(需要超时机制,超时机制有分为两种可能 1、联系不上TM不清楚事务状态,2提前询问了TM,业务还没有确认最终状态)2、通知事务组执行时没有响应。(1不清楚有没有执行完业务,2不清楚有没有收到消息)3、若业务模块死掉了,TM的日志在没有全部确认清楚之前,是不能清理事务数据,TM清理数据需要全部都确认OK方可清理。
由上述情况可见,需要补偿的情况有1、上面的情况1中对联系不上TM的情况需要记录补偿记录。2、上面的情况2、3中描述的场景可能会存在业务模块在没有接受到TM消息的时候联系不上了,若是服务挂了,那么就得需要在下次服务启动的时候通过切面日志来与TM通讯确认状态,然后在执行业务。若是通讯出现了故障,那么会除非超时机制自动写补偿日志。
由于这样的情况的存在 若是服务挂了,那么就得需要在下次服务启动的时候通过切面日志来与TM通讯确认状态,然后再执行业务。所以只能在切面进入是记录数据,当出现时可通过切面记录来触发业务,然后再补偿事务。
补偿出现的处理机制
1、自动补偿 (需要开启)
2、手动补偿 (自行处理删除补偿记录即可)
B站原理详解
项目集成LCN:
TC引入pom依赖
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-txmsg-netty</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
TC开启分布式事务注解
在主类上使用@EnableDistributedTransaction
// 开启LCN分布式事务
public class DemoAApplication {
public static void main(String[] args) {
SpringApplication.run(DemoDubboClientApplication.class, args);
}
}
TC微服务A业务方法配置
public class ServiceA {
private ValueDao valueDao; //本地db操作
private ServiceB serviceB;//远程B模块业务
//LCN分布式事务注解
//本地事务注解
public String execute(String value) throws BusinessException {
// step1. call remote service B
String result = serviceB.rpc(value); // (1)
// step2. local store operate. DTX commit if save success, rollback if not.
valueDao.save(value); // (2)
valueDao.saveBackup(value); // (3)
return result + " > " + "ok-A";
}
}
TC微服务B业务方法配置
public class ServiceB {
private ValueDao valueDao; //本地db操作
//分布式事务注解
//本地事务注解
public String rpc(String value) throws BusinessException {
valueDao.save(value); // (4)
valueDao.saveBackup(value); // (5)
return "ok-B";
}
}
TC配置信息说明
# 默认配置为TM的本机默认端口
tx-lcn.client.manager-address=127.0.0.1:8070
小伙砸,欢迎再看分享给其他小伙伴!共同进步!
本文分享自微信公众号 - java学途(javaxty)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
来源:oschina
链接:https://my.oschina.net/u/4673060/blog/4714893