什么是分布式事务?原理是什么?_第3页回答

2021-04-07 16:07发布

27条回答
梵梵
2楼 · 2021-04-09 11:05

了解过TCC分布式事务的都知道它有三个阶段:try,confirm,cancel,但很多文章就只有原理图,和对原理图的解释,看一遍也留不下印象,这里用实际场景举个例子,说明TCC分布式事务原理

  • try阶段:假设我们又订单系统,它需要调用库存和积分系统,try阶段我们进行的是预处理,比如下单1个商品,在try操作中,我们在库存表设置个冻结字段,表示冻结1个商品,商品的存量先不扣除,而积分表同样添加个预增加积分字段,添加个预积分比如10

  • confirm阶段:我们为什么要经历try阶段?,为了尽可能的保证各个系统都是正常工作的,数据库,服务都没有挂掉,资源没有不足,则可以最大程度上保证confirm阶段能正确执行,confirm阶段也就是正式的扣除库存和增加积分

  • cancel阶段:若try阶段执行错误,则会对前面已经执行的try阶段的系统执行cancel操作,也就是反向SQL回滚,冻结的商品-1,预积分-10。到这里有没有疑问?我首先想到的是若confirm或cancel操作再执行失败怎么办?这里就要由TCC分布式事务框架保证了,它会记录事务活动日志,再confirm或cancel失败后不断尝试调用confirm和cancel的逻辑,所以这里需要开发者自己保证,你的SQL是正确的


py大白
3楼 · 2021-04-09 11:54

当遇到复杂业务调用时,可能会出现跨库多资源调用(一个事务管理器,多个资源)/多服务调用(多个事务管理器,多个资源),期望全部成功或失败回滚,这就是分布式事务,用以保证“操作多个隔离资源的数据一致性”。

TCC 分布式事务模型包括三部分

1.主业务服务:主业务服务为整个业务活动的发起方,服务的编排者,负责发起并完成整个业务活动.

2.从业务服务:从业务服务是整个业务活动的参与方,负责提供 TCC 业务操作,实现初步操作(Try)、确认操作(Confirm)、取消操作(Cancel)三个接口,供主业务服务调用。

3.业务活动管理器:业务活动管理器管理控制整个业务活动,包括记录维护 TCC 全局事务的事务状态和每个从业务服务的子事务状态,并在业务活动提交时调用所有从业务服务的 Confirm 操作,在业务活动取消时调用所有从业务服务的 Cancel 操作。

一个完整的 TCC 分布式事务流程如下:

1.主业务服务首先开启本地事务;

2.主业务服务向业务活动管理器申请启动分布式事务主业务活动;

3.然后针对要调用的从业务服务,主业务活动先向业务活动管理器注册从业务活动,然后调用从业务服务的 Try 接口;

4.当所有从业务服务的 Try 接口调用成功,主业务服务提交本地事务;若调用失败,主业务服务回滚本地事务;

5.若主业务服务提交本地事务,则 TCC 模型分别调用所有从业务服务的 Confirm 接口;若主业务服务回滚本地事务,则分别调用 Cancel 接口;

6.所有从业务服务的 Confirm 或 Cancel 操作完成后,全局事务结束


爱学习的小巴
4楼 · 2021-04-09 15:45

分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上;分布式事务处理 (TP) 系统旨在协助在分布式环境中跨异类的事务识别资源的事务。在分布式 TP 系统的支持下,应用程序可以将不同的活动合并为一个事务性单元,这些活动包括从“消息队列”队列检索消息、将消息存储在 Microsoft SQL Server 数据库中、将所有现有的消息引用从 Oracle Server 数据库中移除,等等。因为分布式事务跨多个数据库资源,故强制 ACID 属性维护所有资源上的数据一致性是很重要的

小小收藏家
5楼 · 2021-04-09 15:45

当遇到复杂业务调用时,可能会出现跨库多资源调用(一个事务管理器,多个资源)/多服务调用(多个事务管理器,多个资源),期望全部成功或失败回滚,这就是分布式事务,用以保证“操作多个隔离资源的数据一致性”。

TCC 分布式事务模型包括三部分

1.主业务服务:主业务服务为整个业务活动的发起方,服务的编排者,负责发起并完成整个业务活动.

2.从业务服务:从业务服务是整个业务活动的参与方,负责提供 TCC 业务操作,实现初步操作(Try)、确认操作(Confirm)、取消操作(Cancel)三个接口,供主业务服务调用。

3.业务活动管理器:业务活动管理器管理控制整个业务活动,包括记录维护 TCC 全局事务的事务状态和每个从业务服务的子事务状态,并在业务活动提交时调用所有从业务服务的 Confirm 操作,在业务活动取消时调用所有从业务服务的 Cancel 操作。

一个完整的 TCC 分布式事务流程如下:

1.主业务服务首先开启本地事务;

2.主业务服务向业务活动管理器申请启动分布式事务主业务活动;

3.然后针对要调用的从业务服务,主业务活动先向业务活动管理器注册从业务活动,然后调用从业务服务的 Try 接口;

4.当所有从业务服务的 Try 接口调用成功,主业务服务提交本地事务;若调用失败,主业务服务回滚本地事务;

5.若主业务服务提交本地事务,则 TCC 模型分别调用所有从业务服务的 Confirm 接口;若主业务服务回滚本地事务,则分别调用 Cancel 接口;

6.所有从业务服务的 Confirm 或 Cancel 操作完成后,全局事务结束。


清屿
6楼 · 2021-04-11 14:30

分布式事务 

指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上 。

换成比较容易理解的话,就是多个事务之间再保持事务的特性,也就是多个事务之间保证结果的一致性。


分布式事务解决方案

1、基于XA协议的两阶段提交

2、消息事务+最终一致性

3、TCC 模型


我的网名不再改
7楼 · 2021-04-11 16:13

对于分布式事务,相信所有人都应该很了解,为什么会有分布式事务?无论是数据量导致的分库,还是现在微服务盛行的场景都是他出现的原因。

这一篇内容还是避免不了俗套,主要的范围无非是XA、2PC、3PC、TCC,再最后到Seata。

但是,我认为这东西,只是适用于面试和理论的了解,你真要说这些方案实际生产中有人用吗?

有,但是会实现的更简单,不会套用理论来实现,大厂有大厂的解决方案,中小公司用框架或者压根就不存在分布式事务的问题。

那,为什么还要写这个?

为了你面试八股文啊,小可爱。

事务

要说分布式事务,首先还是从事务的基本特征说起。

A原子性:在事务的执行过程中,要么全部执行成功,要么都不成功。

C一致性:事务在执行前后,不能破坏数据的完整性。一致性更多的说的是通过AID来达到目的,数据应该符合预先的定义和约束,由应用层面来保证,还有的说法是C是强行为了ACID凑出来的。

I隔离性:多个事务之间是互相隔离的,事务之间不能互相干扰,涉及到不同事务的隔离级别的问题。

D持久性:一旦事务提交,数据库中数据的状态就应该是永久性的。

XA

XA(eXtended Architecture)是指由X/Open 组织提出的分布式事务处理的规范,他是一个规范或者说是协议,定义了事务管理器TM(Transaction Manager),资源管理器RM(Resource Manager),和应用程序。

事务管理器TM就是事务的协调者,资源管理器RM可以认为就是一个数据库。

2PC

XA定义了规范,那么2PC和3PC就是他的具体实现方式。

2PC叫做二阶段提交,分为投票阶段和执行阶段两个阶段。

投票阶段

TM向所有的参与者发送prepare请求,询问是否可以执行事务,等待各个参与者的响应。

这个阶段可以认为只是执行了事务的SQL语句,但是还没有提交。

如果都执行成功了就返回YES,否则返回NO。

执行阶段

执行阶段就是真正的事务提交的阶段,但是要考虑到失败的情况。

如果所有的参与者都返回YES,那么就执行发送commit命令,参与者收到之后执行提交事务。

反之,只要有任意一个参与者返回的是NO的话,就发送rollback命令,然后执行回滚的操作。

2PC的缺陷

  1. 同步阻塞,可以看到,在执行事务的过程当中,所有数据库的资源都被锁定,如果这时候有其他人来访问这些资源,将会被阻塞,这是一个很大的性能问题。

  2. TM单点问题,只要一个TM,一旦TM宕机,那么整个流程无法继续完成。

  3. 数据不一致,如果在执行阶段,参与者脑裂或者其他故障导致没有收到commit请求,部分提交事务,部分未提交,那么数据不一致的问题就产生了。

3PC

既然2PC有这么多问题,所以就衍生出了3PC的概念,也叫做三阶段提交,他把整个流程分成了CanCommit、PreCommit、DoCommit三个步骤,相比2PC,增加的就是CanCommit阶段。

CanCommit

这个阶段就是先询问数据库是否执行事务,发送一个canCommit的请求去询问,如果可以的话就返回YES,反之返回NO。

PreCommit

这个阶段就等同于2PC的投票阶段了,发送preCommit命令,然后去执行SQL事务,成功就返回YES,反之返回NO。

但是,这个地方的区别在于参与者有了超时机制,如果参与者超时未收到doCommit命令的话,将会默认去提交事务。

DoCommit

DoCommit阶段对应到2PC的执行阶段,如果上一个阶段都是收到YES的话,那么就发送doCommit命令去提交事务,反之则会发送abort命令去中断事务的执行。

相比2PC的改进

对于2PC的同步阻塞的问题,我们可以看到因为3PC加入了参与者的超时机制,所以原来2PC的如果某个参与者故障导致的同步阻塞的问题时间缩短了,这是一个优化,但是并没有完全避免。

第二个单点故障的问题,同样因为超时机制的引入,一定程度上也算是优化了。

但是数据不一致的问题,这个始终没有得到解决。

举个栗子:

在PreCommit阶段,某个参与者发生脑裂,无法收到TM的请求,这时候其他参与者执行abort事务回滚,而脑裂的参与者超时之后继续提交事务,还是有可能发生数据不一致的问题。

那么,为什么要加入DoCommit这个阶段呢?就是为了引入超时机制,事先我们先确认数据库是否都可以执行事务,如果都OK,那么才会进入后面的步骤,所以既然都可以执行,那么超时之后说明发生了问题,就自动提交事务。

TCC

TCC的模式叫做Try、Confirm、Cancel,实际上也就是2PC的一个变种而已。

实现这个模式,一个事务的接口需要拆分成3个,也就是Try预占、Confirm确认提交、最后Cancel回滚。

对于TCC来说,实际生产我基本上就没看见过有人用,考虑到原因,首先是程序员的本身素质参差不齐,多个团队协作你很难去约束别人按照你的规则来实现,另外一点就是太过于复杂。

如果说有简单的应用的话,库存的应用或许可以算做是一个。

一般库存的操作,很多实现方案里面都会会在下单的时候先预占库存,下单成功之后再实际去扣减库存,最终如果发生了异常再回退。

冻结、预占库存就是2PC的准备阶段,真正下单成功去扣减库存就是2PC的提交阶段,回滚就是某个发生异常的回滚操作,只不过在应用层面来实现了2PC的机制而已。

SAGA

Saga源于1987 年普林斯顿大学的 Hecto 和 Kenneth 发表的如何处理 long lived transaction(长活事务)论文。

主要思想就是将长事务拆分成多个本地短事务。

如果全部执行成功,就正常完成了,反之,则会按照相反的顺序依次调用补偿。

SAGA模式有两种恢复策略:

  1. 向前恢复,这个模式偏向于一定要成功的场景,失败则会进行重试

  2. 向后恢复,也就是发生异常的子事务依次回滚补偿

由于这个模式在国内基本没看见有谁用的,不在赘述。

消息队列

基于消息队列来实现最终一致性的方案,这个相比前面的我个人认为还稍微靠谱一点,那些都是理论啊,正常生产的实现很少看见应用。

基于消息队列的可能真正在应用的还稍微多一点。

一般来说有两种方式,基于本地消息表和依赖MQ本身的事务消息。

本地消息表的这个方案其实更复杂,实际上我也没看到过真正谁来用。这里我以RocketMQ的事务消息来举例,这个方式相比本地消息表则更完全依赖MQ本身的特性做了解耦,释放了业务开发的复杂工作量。

  1. 业务发起方,调用远程接口,向MQ发送一条半事务消息,MQ收到消息之后会返回给生产者一个ACK

  2. 生产者收到ACK之后,去执行事务,但是事务还没有提交。

  3. 生产者会根据事务的执行结果来决定发送commit提交或者rollback回滚到MQ

  4. 这一点是发生异常的情况,比如生产者宕机或者其他异常导致MQ长时间没有收到commit或者rollback的消息,这时候MQ会发起状态回查。

  5. MQ如果收到的是commit的话就会去投递消息,消费者正常消费消息即可。如果是rollback的话,则会在设置的固定时间期限内去删除消息。

这个方案基于MQ来保证消息事务的最终一致性,还算是一个比较合理的解决方案,只要保证MQ的可靠性就可以正常实施应用,业务消费方根据本身的消息重试达到最终一致性。

框架

以上说的都是理论和自己实现的方式,那么分布式事务就没有框架来解决我们的问题吗?

有,其实还不少,但是没有能扛旗者出现,要说有,阿里的开源框架Seata还有阿里云的GTS。

GTS(Global Transaction Service 全局事务服务)是阿里云的中间件产品,只要你用阿里云,付钱就可以用GTS。

Seata(Simple Extensible Autonomous Transaction Architecture)则是开源的分布式事务框架,提供了对TCC、XA、Saga以及AT模式的支持。

那么,GTS和Seata有什么关系呢?

实际上最开始的时候他们都是基于阿里内部的TXC(Taobao Transaction Constructor)分布式中间件产品,然后TXC经过改造上了阿里云就叫做GTS。

之后阿里的中间件团队基于TXC和GTS做出了开源的Seata,其中AT(Automatic Transaction)模式就是GTS原创的方案。

至于现在的版本,可以大致认为他们就是一样的就行了,到2020年,GTS已经全面兼容了Seata的 GA 版本。

整个GTS或者Seata包含以下几个核心组件:

  • Transaction Coordinator(TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。

  • Transaction Manager(TM):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。

  • Resource Manager(RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。

无论对于TCC还是原创的AT模式的支持,整个分布式事务的原理其实相对来说还是比较容易理解。

  1. 事务开启时,TM向TC注册全局事务,并且获得全局事务XID

  2. 这时候多个微服务的接口发生调用,XID就会传播到各个微服务中,每个微服务执行事务也会向TC注册分支事务。

  3. 之后TM就可以管理针对每个XID的事务全局提交和回滚,RM完成分支的提交或者回滚。

AT模式

原创的AT模式相比起TCC的方案来说,无需自己实现多个接口,通过代理数据源的形式生成更新前后的UNDO_LOG,依靠UNDO_LOG来实现回滚的操作。

执行的流程如下:

  1. TM向TC注册全局事务,获得XID

  2. RM则会去代理JDBC数据源,生成镜像的SQL,形成UNDO_LOG,然后向TC注册分支事务,把数据更新和UNDO_LOG在本地事务中一起提交

  3. TC如果收到commit请求,则会异步去删除对应分支的UNDO_LOG,如果是rollback,就去查询对应分支的UNDO_LOG,通过UNDO_LOG来执行回滚

TCC模式

相比AT模式代理JDBC数据源生成UNDO_LOG来生成逆向SQL回滚的方式,TCC就更简单一点了。

  1. TM向TC注册全局事务,获得XID

  2. RM向TC注册分支事务,然后执行Try方法,同时上报Try方法执行情况

  3. 然后如果收到TC的commit请求就执行Confirm方法,收到rollback则执行Cancel

XA模式

  1. TM向TC注册全局事务,获得XID

  2. RM向TC注册分支事务,XA Start,执行SQL,XA END,XA Prepare,然后上报分支执行情况

  3. 然后如果收到TC的commit请求就执行Confirm方法,收到rollback则执行Cancel

SAGA模式

  1. TM向TC注册全局事务,获得XID

  2. RM向TC注册分支事务,然后执行业务方法,并且上报分支执行情况

  3. RM收到分支回滚,执行对应的业务回滚方法

总结

这里从事务的ACID开始,向大家先说了XA是分布式事务处理的规范,之后谈到2PC和3PC,2PC有同步阻塞、单点故障和数据不一致的问题,3PC在一定程度上解决了同步阻塞和单点故障的问题,但是还是没有完全解决数据不一致的问题。

之后说到TCC、SAGA、消息队列的最终一致性的方案,TCC由于实现过于麻烦和复杂,业务很少应用,SAGA了解即可,国内也很少有应用到的,消息队列提供了解耦的实现方式,对于中小公司来说可能是较为低成本的实现方式。

最后再说目前国内的实现框架,云端阿里云的GTS兼容Seata,非云端使用Seata,它提供了XA、TCC、AT、SAGA的解决方案,可以说是目前的主流选择。


我是大脸猫
8楼 · 2021-04-11 22:02

事务是数据库系统中非常有趣也非常重要的概念,它是数据库管理系统执行过程中的一个逻辑单元,它能够保证一个事务中的所有操作要么全部执行,要么全不执行;在 SOA 与微服务架构大行其道的今天,在分布式的多个服务中保证业务的一致性就需要我们实现分布式事务。


640?wx_fmt=jpeg


在这篇文章中,我们将介绍 事务的实现原理、分布式事务的理论基础以及实现原理。


事务


在文章的开头,我们已经说过事务是数据库管理系统执行过程中的一个逻辑单位,它能保证一组数据库操作要么全部执行,要么全不执行,我们能够通过事务将数据库从一个状态迁移到另一个状态,在每一个状态中,数据库中的数据都保持一致性。


640?wx_fmt=png


数据库事务拥有四个特性,原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability):


640?wx_fmt=jpeg


我们经常将这上述的四大特性简写为 ACID,而数据库事务的实现原理其实也就是实现这四大特性的原理。


实现原理


在之前的文章 『浅入深出』MySQL 中事务的实现https://draveness.me/mysql-transaction 中其实已经对如何实现事务的 ACID 这几个基本属性给出了比较详细的介绍和分析,在这里就简单介绍几个比较重要的实现细节,关于展开的内容,可以阅读上述文章。


事务日志


为了实现确保事务能在执行的任意过程中回滚(原子性)并且提交的事务会永久保存在数据库中,我们会使用事务日志来存储事务执行过程中的数据库的变动,每一条事务日志中都包含事务的 ID、当前被修改的元素、变动前以及变动后的值。


640?wx_fmt=jpeg


当我们有以上的事务日志之后,一旦需要对事务进行回滚就非常容易了,数据库会根据上述日志生成一个相反的操作恢复事务发生之前的状态;事务日志除了能够对事务进行回滚保证原子性之外,还能够实现持久性,当一个事务常食对数据库进行修改时,它其实会先生成一条日志并刷新到磁盘上,写日志的操作由于是追加的所以非常快,在这之后才会向数据库中写入或者更新对应的记录。


在 MySQL 最常见的存储引擎 InnoDB 中,事务日志其实有两种,一种是回滚日志(undo log),另一种是重做日志(redo log),其中前者保证事务的原子性,后者保证事务的持久性,两者可以统称为事务日志。


并发控制


数据库作为最关键的后端服务,很难想象只能串行执行每一个数据库操作带来的性能影响,然而在并发执行 SQL 的过程中就可能无法保证数据库对于隔离性的要求,归根结底这就是一致性、隔离性与性能之间的权衡。


640?wx_fmt=jpeg


为了避免并发带来的一致性问题、满足数据库对于隔离性要求,数据库系统往往都会使用并发控制机制尽可能地充分利用机器的效率,最常见的几种并发控制机制就是锁、时间戳和 MVCC:


640?wx_fmt=jpeg


作为悲观并发控制机制,锁使用在更新资源之前对资源进行锁定的方式保证多个数据库的会话同时修改某一行记录时不会出现脱离预期的行为,而时间戳这种方式在每次提交时对资源是否被改变进行检查。


在 浅谈数据库并发控制 - 锁和 MVCC 中,作者介绍过几种不同的并发控制机制的实现原理,想要了解更多相关的内容的可以阅读这篇文章。

https://draveness.me/database-concurrency-control


分布式事务


从广义上来看,分布式事务其实也是事务,只是由于业务上的定义以及微服务架构设计的问题,所以需要在多个服务之间保证业务的事务性,也就是 ACID 四个特性;从单机的数据库事务变成分布式事务时,原有单机中相对可靠的方法调用以及进程间通信方式已经没有办法使用,同时由于网络通信经常是不稳定的,所以服务之间信息的传递会出现障碍。


640?wx_fmt=jpeg


模块(或服务)之间通信方式的改变是造成分布式事务复杂的最主要原因,在同一个事务之间的执行多段代码会因为网络的不稳定造成各种奇怪的问题,当我们通过网络请求其他服务的接口时,往往会得到三种结果:正确、失败和超时,无论是成功还是失败,我们都能得到唯一确定的结果,超时代表请求的发起者不能确定接受者是否成功处理了请求,这也是造成诸多问题的诱因。


640?wx_fmt=jpeg


系统之间的通信可靠性从单一系统中的可靠变成了微服务架构之间的不可靠,分布式事务其实就是在不可靠的通信下实现事务的特性。无论是事务还是分布式事务实现原子性都无法避免对持久存储的依赖,事务使用磁盘上的日志记录执行的过程以及上下文,这样无论是需要回滚还是补偿都可以通过日志追溯,而分布式事务也会依赖数据库、Zookeeper 或者 ETCD 等服务追踪事务的执行过程,总而言之,各种形式的日志是保证事务几大特性的重要手段


2PC 与 3PC


两阶段提交是一种使分布式系统中所有节点在进行事务提交时保持一致性而设计的一种协议;在一个分布式系统中,所有的节点虽然都可以知道自己执行操作后的状态,但是无法知道其他节点执行操作的状态,在一个事务跨越多个系统时,就需要引入一个作为协调者的组件来统一掌控全部的节点并指示这些节点是否把操作结果进行真正的提交,想要在分布式系统中实现一致性的其他协议都是在两阶段提交的基础上做的改进。


640?wx_fmt=jpeg


两阶段提交的执行过程就跟它的名字一样分为两个阶段,投票阶段和提交阶段,在投票阶段中,协调者(Coordinator)会向事务的参与者(Cohort)询问是否可以执行操作的请求,并等待其他参与者的响应,参与者会执行相对应的事务操作并记录重做和回滚日志,所有执行成功的参与者会向协调者发送 AGREEMENT 或者 ABORT 表示执行操作的结果。


640?wx_fmt=jpeg


当所有的参与者都返回了确定的结果(同意或者终止)时,两阶段提交就进入了提交阶段,协调者会根据投票阶段的返回情况向所有的参与者发送提交或者回滚的指令。


640?wx_fmt=png


当事务的所有参与者都决定提交事务时,协调者会向参与者发送 COMMIT 请求,参与者在完成操作并释放资源之后向协调者返回完成消息,协调者在收到所有参与者的完成消息时会结束整个事务;与之相反,当有参与者决定 ABORT 当前事务时,协调者会向事务的参与者发送回滚请求,参与者会根据之前执行操作时的回滚日志对操作进行回滚并向协调者发送完成的消息,在提交阶段,无论当前事务被提交还是回滚,所有的资源都会被释放并且事务也一定会结束。


两阶段提交协议是一个阻塞协议,也就是说在两阶段提交的执行过程中,除此之外,如果事务的执行过程中协调者永久宕机,事务的一部分参与者将永远无法完成事务,它们会等待协调者发送 COMMIT 或者 ROLLBACK 消息,甚至会出现多个参与者状态不一致的问题。


640?wx_fmt=png


3PC


为了解决两阶段提交在协议的一些问题,三阶段提交引入了超时机制和准备阶段,如果协调者或者参与者在规定的之间内没有接受到来自其他节点的响应,就会根据当前的状态选择提交或者终止整个事务,准备阶段的引入其实让事务的参与者有了除回滚之外的其他选择。


640?wx_fmt=png


当参与者向协调者发送 ACK 后,如果长时间没有得到协调者的响应,在默认情况下,参与者会自动将超时的事务进行提交,不会像两阶段提交中被阻塞住;上述的图片非常清楚地说明了在不同阶段,协调者或者参与者的超时会造成什么样的行为。


XA 事务


MySQL 的 InnoDB 引擎其实能够支持分布式事务,也就是我们经常说的 XA 事务;XA 事务就是用了我们在上一节中提到的两阶段提交协议实现分布式事务,其中事务管理器为协调者,而资源管理器就是分布式事务的参与者。


640?wx_fmt=jpeg


到这里,其实我们已经能够清晰地知道 MySQL 中的 XA 事务是如何实现的:


  • 资源管理器提供了访问事务资源的能力,数据库就是一种常见的资源管理器,它能够提交或者回滚其管理的事务;

  • 事务管理器协调整个分布式事务的各个部分,它与多个资源管理器通信,分别处理他们管理的事务,这些事务都是整体事务的一个分支。


640?wx_fmt=png


正如两阶段提交协议中定义的,MySQL 提供的 XA 接口可以非常方便地实现协议中的投票和提交阶段,我们可以通过一下的流程图简单理解一下 MySQL XA 的接口是如何使用的:


640?wx_fmt=png


XA 确实能够保证较强的一致性,但是在 MySQL XA 的执行过程中会对相应的资源加锁,阻塞其他事务对该资源的访问,如果事务长时间没有 COMMIT 或者 ROLLBACK,其实会对数据库造成比较严重的影响。


Saga


两阶段提交其实可以保证事务的强一致性,但是在很多业务场景下,我们其实只需要保证业务的最终一致性,在一定的时间窗口内,多个系统中的数据不一致是可以接受的,在过了时间窗口之后,所有系统都会返回一致的结果。


Saga 其实就一种简化的分布式事务解决方案,它将一系列的分布式操作转化成了一系列的本地事务,在每一个本地事务中我们都会更新数据库并且向集群中的其他服务发送一条的新的消息来触发下一个本地的事务;一旦本地的事务因为违反了业务逻辑而失败,那么就会立刻触发一系列的回滚操作来撤回之前本地事务造成的副作用。


LLT


相比于本地的数据库事务来说,长事务(Long Lived Transaction)会对一些数据库资源持有相对较长的一段时间,这会严重地影响其他正常数据库事务的执行,为了解决这一问题,Hector Garcia-Molina 和 Kenneth Salem 在 1987 发布了论文 Sagas 用于解决这一问题。


如果一个 LLT 能够被改写成一系列的相互交错重叠的多个数据库事务,那么这个 LLT 就是一个 Saga;数据库系统能够保证 Saga 中一系列的事务要么全部成功执行、要么它们的补偿事务能够回滚全部的副作用,保证整个分布式事务的最终一致性。Saga 的概念和它的实现都是非常简单的,但是它却能够有很大的潜力增加整个系统的处理能力。


640?wx_fmt=png


事务越长并且越复杂,那么这个事务由于异常而被回滚以及死锁的可能性就会逐渐增加,Saga 会将一个 LLT 分解成多个短事务,能够非常明显地降低事务被回滚的风险。


协同与编排


当我们使用 Saga 模式开发分布式事务时,有两种协调不同服务的方式,一种是协同(Choreography),另一种是编排(Orchestration):


640?wx_fmt=jpeg


如果对于一个分布式事务,我们采用协同的方式进行开发,每一个本地的事务都会触发一个其他服务中的本地事务的执行,也就是说事务的执行过程是一个流的形式进行的:


640?wx_fmt=jpeg


当我们选择使用协同的方式处理事务时,服务之间的通信其实就是通过事件进行的,每一个本的事务最终都会向服务的下游发送一个新的事件,既可以是消息队列中的消息,也可以是 RPC 的请求,只是下游提供的接口需要保证幂等和重入。


除此之外,通过协同方式创建的分布式事务其实并没有明显的中心化节点,多个服务参与者之间的交互协议要从全局来定义,每个服务能够处理以及发送的事件和接口都需要进行比较严谨的设计,尽可能提供抽象程度高的事件或者接口,这样各个服务才能实现自治并重用已有的代码和逻辑。


如果我们不想使用协同的方式对分布式事务进行处理,那么也可以选择编排的方式实现分布式事务,编排的方式引入了中心化的协调器节点,我们通过一个 Saga 对象来追踪所有的子任务的调用情况,根据任务的调用情况决定是否需要调用对应的补偿方案,并在网络请求出现超时时进行重试:


640?wx_fmt=jpeg


在这里我们就引入了一个中心化的『协调器』,它会保存当前分布式事务进行到底的状态,并根据情况对事务进行回滚或者提交操作,在服务编排的过程中,我们是从协调者本身触发考虑整个事务的执行过程的,相对于协同的方式,编排实现的过程相对来说更为简单。


协同与编排其实是两种思路截然相反的模式,前者强调各个服务的自治与去中心化,后者需要一个中心化的组件对事务执行的过程进行统一的管理,两者的优缺点其实就是中心化与去中心化的优缺点,中心化的方案往往都会造就一个『上帝服务』,其中包含了非常多组织与集成其他节点的工作,也会有单点故障的问题,而去中心化的方案就会带来管理以及调试上的不便,当我们需要追踪一个业务的执行过程时就需要跨越多个服务进行,增加了维护的成本。


下游约束


当我们选择使用 Saga 对分布式事务进行开发时,会对分布式事务的参与者有一定的约束,每一个事务的参与者都需要保证:


1. 提供接口和补偿副作用的接口;

2. 接口支持重入并通过全局唯一的 ID 保证幂等;


这样我们就能够保证一个长事务能够在网络通信发生超时时进行重试,同时在需要对事务进行回滚时调用回滚接口达到我们的目的。


小结


Saga 这种模式其实完全放弃了同时满足事务四大基本特性 ACID 的想法,而是选择降低实现分布式事务的难度并减少资源同步以及锁定带来的问题,选择实现 BASE(Basic Availability, Soft, Eventual consistency) 事务,达到业务上的基本可用以及最终一致性,在绝大多数的业务场景中,实现最终一致性就能够基本满足业务的全部需求,极端场景下还是应该选择两阶段提交或者干脆放弃分布式事务这种易错的实现方式,转而使用单机中的数据库事务来解决。


消息服务


分布式事务带来复杂度的原因其实就是由于各个模块之间的通信不稳定,当我们发出一个网络请求时,可能的返回结果是成功、失败或者超时。


640?wx_fmt=jpeg


网络无论是返回成功还是失败其实都是一个确定的结果,当网络请求超时的时候其实非常不好处理,在这时调用方并不能确定这一次请求是否送达而且不会知道请求的结果,但是消息服务可以保证某条信息一定会送达到调用方;大多数消息服务都会提供两种不同的 QoS,也就是服务的等级。


640?wx_fmt=jpeg


最常见的两种服务等级就是 At-Most-Once 和 At-Least-Once,前者能够保证发送方不对接收方是否能收到消息作保证,消息要么会被投递一次,要么不会被投递,这其实跟一次普通的网络请求没有太多的区别;At-Least-Once 能够解决消息投递失败的问题,它要求发送者检查投递的结果,并在失败或者超时时重新对消息进行投递,发送者会持续对消息进行推送,直到接受者确认消息已经被收到,相比于 At-Most-Once,At-Least-Once 因为能够确保消息的投递会被更多人使用。


除了这两种常见的服务等级之外,还有另一种服务等级,也就是 Exactly-Once,这种服务等级不仅对发送者提出了要求,还对消费者提出了要求,它需要接受者对接收到的所有消息进行去重,发送者和接受者一方对消息进行重试,另一方对消息进行去重,两者分别部署在不同的节点上,这样对于各个节点上的服务来说,它们之间的通信就是 Exactly-Once 的,但是需要注意的是,Exacly-Once 一定需要接收方的参与。


我们可以通过实现 AMQP 协议的消息队列来实现分布式事务,在协议的标准中定义了 tx_select、tx_commit 和 tx_rollback 三个事务相关的接口,其中 tx_select 能够开启事务,tx_commit 和 tx_rollback 分别能够提交或者回滚事务。


使用消息服务实现分布式事务在底层的原理上与其他的方法没有太多的差别,只是消息服务能够帮助我们实现的消息的持久化以及重试等功能,能够为我们提供一个比较合理的 API 接口,方便开发者使用。


总结


分布式事务的实现方式是分布式系统中非常重要的一个问题,在微服务架构和 SOA 大行其道的今天,掌握分布式事务的原理和使用方式已经是作为后端开发者理所应当掌握的技能,从实现 ACID 事务的 2PC 与 3PC 到实现 BASE 补偿式事务的 Saga,再到最后通过事务消息的方式异步地保证消息最终一定会被消费成功,我们为了增加系统的吞吐量以及可用性逐渐降低了系统对一致性的要求。


在业务没有对一致性有那么强的需求时,作者一般会使用 Saga 协议对分布式事务进行设计和开发,而在实际工作中,需要强一致性事务的业务场景几乎没有,我们都可以实现最终一致性,在发生脑裂或者不一致问题时通过补偿的方式进行解决,这就能解决几乎全部的问题。


相关文章


  • 分布式系统

    • 分布式键值存储 Dynamo 的实现原理(https://draveness.me/dynamo

    • 浅析 Bigtable 和 LevelDB 的实现(https://draveness.me/bigtable-leveldb

    • 分布式事务的实现原理(https://draveness.me/distributed-transaction-principle

    • 基础

    • 系统


Reference


  • Database transaction · Wikipedia(https://en.wikipedia.org/wiki/Database_transaction

  • 『浅入深出』MySQL 中事务的实现(https://draveness.me/mysql-transaction

  • MySQL · 特性分析 · 浅谈 MySQL 5.7 XA 事务改进(http://mysql.taobao.org/monthly/2017/09/05/

  • XA Transactions(https://dev.mysql.com/doc/refman/8.0/en/xa.html

  • Two-phase commit protocol(https://en.wikipedia.org/wiki/Two-phase_commit_protocol

  • Pattern: Saga(http://microservices.io/patterns/data/saga.html

  • Sagas(https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf

  • RocketMQ 4.3正式发布,支持分布式事务(http://www.infoq.com/cn/news/2018/08/rocketmq-4.3-release

  • Akka Message Delivery - At-Most-Once, At-Least-Once, and Exactly-Once

    • Part 1 At-Most-Oncehttps://developer.lightbend.com/blog/2017-08-10-atotm-akka-messaging-part-1/index.html

    • Part 2 At-Least-Once(https://developer.lightbend.com/blog/2017-11-01-atotm-akka-messaging-part-2/index.html

    • Part 3 Exactly-Once(https://developer.lightbend.com/blog/2018-02-01-atotm-akka-messaging-part-3/index.html

  • Message Delivery Reliability(https://doc.akka.io/docs/akka/current/general/message-delivery-reliability.html?language=java


是年糕麻麻啊
9楼 · 2021-04-12 09:18

当遇到复杂业务调用时,可能会出现跨库多资源调用(一个事务管理器,多个资源)/多服务调用(多个事务管理器,多个资源),期望全部成功或失败回滚,这就是分布式事务,用以保证“操作多个隔离资源的数据一致性”。

TCC 分布式事务模型包括三部分

1.主业务服务:主业务服务为整个业务活动的发起方,服务的编排者,负责发起并完成整个业务活动.

2.从业务服务:从业务服务是整个业务活动的参与方,负责提供 TCC 业务操作,实现初步操作(Try)、确认操作(Confirm)、取消操作(Cancel)三个接口,供主业务服务调用。

3.业务活动管理器:业务活动管理器管理控制整个业务活动,包括记录维护 TCC 全局事务的事务状态和每个从业务服务的子事务状态,并在业务活动提交时调用所有从业务服务的 Confirm 操作,在业务活动取消时调用所有从业务服务的 Cancel 操作。

一个完整的 TCC 分布式事务流程如下:

1.主业务服务首先开启本地事务;

2.主业务服务向业务活动管理器申请启动分布式事务主业务活动;

3.然后针对要调用的从业务服务,主业务活动先向业务活动管理器注册从业务活动,然后调用从业务服务的 Try 接口;

4.当所有从业务服务的 Try 接口调用成功,主业务服务提交本地事务;若调用失败,主业务服务回滚本地事务;

5.若主业务服务提交本地事务,则 TCC 模型分别调用所有从业务服务的 Confirm 接口;若主业务服务回滚本地事务,则分别调用 Cancel 接口;

6.所有从业务服务的 Confirm 或 Cancel 操作完成后,全局事务结束。


相关问题推荐

  • 回答 36

    看军事新闻的同学应该都知道,一艘航空母舰作战能力虽然很强,但是弱点太明显,就是防御能力太差,单艘的航空母舰很少单独行动,通常航空母舰战斗群才是主要军事力量,你可以把单艘航母理解为的单体应用(防御差,机动性不好),把航母战斗群(调度复杂,维护...

  • 回答 31

    初始化过程细节:首先进行的就是将服务装载到容器中,然后准备注册服务。和Spring中启动过程类似,Spring启动时,将bean装载进容器中的时候,首先要解析bean。所以dubbo也是先读配置文件解析服务。解析服务:基于dubbo.jar内的META-INF/spring.handlers配置,...

  • 什么是接口幂等性?2021-02-24 18:21
    回答 25

    1. 接口调用存在的问题        现如今我们的系统大多拆分为分布式SOA,或者微服务,一套系统中包含了多个子系统服务,而一个子系统服务往往会去调用另一个服务,而服务调用服务无非就是使用RPC通信或者restful,既然是通信,那么就有可能在服务器处理...

  • 回答 21

    前提是另外一个bean在bean容器中能找到

  • 回答 22

    1.View——表示层1.1准备数据实现方式:struts,servlet等1.2显示数据实现方式:extjs,jsp,jquery,html等2.Service——业务层实现方式:drools等3.Dao——数据访问层实现方式:hibernate、mybatis等...

  • SpringCloud Netflix和Sprin2020-11-12 09:41
    回答 8
    已采纳

    SpringCloud Netflix和SpringCloud Alibaba的区别如下图:

  • 回答 16

    哨兵(Sentinel)是 redis 的高可用性解决方案,前面我们讲的主从复制它是高可用的基础,需要人工介入才能完成故障转移,哨兵可以解决这个问题,在主从复制情况下,当主节点发生故障时,哨兵可以自动的发现故障并且完成故障转移,实现真正的 redis 高可用。在...

  • 回答 8

    redis为什么会有高并发问题redis的出身决定Redis是一种单线程机制的nosql数据库,基于key-value,数据可持久化落盘。由于单线程所以redis本身并没有锁的概念,多个客户端连接并不存在竞争关系,但是利用jedis等客户端对redis进行并发访问时会出现问题。发生连...

  • 回答 8

    用dubbo是想利用分布式集群的形式来提高服务的并发量,适用与大型项目.如果不用它还想提高并发另一个解决方案是springCloud+微服务.适合大中小型项目.当前对并发要求的项目还是很多的,所以dubbo用的也相对较多.建议如果是初学者可以跳过dubbo直接学习第二个方...

  • 回答 4

    不需要。一般是service分出去。然后其它放在web层即一个jar为service业务处理,一个为web层war包

  • 回答 7

    在回答这个问题之前,我们先回答一下什么是锁。普通的锁,即在单机多线程环境下,当多个线程需要访问同一个变量或代码片段时,被访问的变量或代码片段叫做临界区域,我们需要控制线程一个一个的顺序执行,否则会出现并发问题。如何控制呢?就是设置一个各个线...

  • 回答 6

     springmvc位于表现层,主要与浏览器进行交互(接收和响应浏览器请求)。springmvc采用MVC设计模型,模型由model、view和controller组成。         model(模型):对应JavaBean         view(视图):对应JSP         controller(控制器)...

没有解决我的问题,去提问