1.内容介绍
1. 什么是分布式事务;(掌握)
2. 常见分布式事务实现;(掌握)
3. Seata分布式事务解决方案实现;(掌握)
2.什么是分布式事务
2.1.事务是什么
事务: 指作为单个逻辑工作单元(Service方法)执行的一系列操作(数据库操作。),要么完全地执行,要么完全地不执行.
事务可以看做是一次大的活动,它由不同的小活动组成,这些活动要么全部成功,要么全部失败。
2.2.本地事务
2.2.1.是什么
什么是本地事务(Local Transaction)?本地事务也称为数据库事务或传统事务(相对于分布式事务而言)。它的执行模式就是常见的:
1.transaction begin
2.insert/delete/update
3.insert/delete/update
4…
5.transaction commit/rollback
本地事务有这么几个特征:
1.一次事务只连接一个支持事务的数据库(一般来说都是关系型数据库)
2.事务的执行结果保证ACID
3.会用到数据库锁
2.2.2.ACID
Atomicity 原子性-undo log
Atomicity requires that each transaction be “all or nothing”: if one part of the transaction fails, then the entire transaction fails, and the database state is left unchanged. An atomic system must guarantee atomicity in each and every situation, including power failures, errors and crashes. To the outside world, a committed transaction appears (by its effects on the database) to be indivisible (“atomic”), and an aborted transaction does not happen.
关键词在于:
1.all or nothing,它的意思是数据库要么被修改了,要么保持原来的状态。所谓保持原来的状态不是我先insert再delete,而是压根就没有发生过任何操作。因为insert然后再delete实际上还是修改了数据库状态的,至少在数据库日志层面是这样。
2.indivisible,不可分割,一个事务就是一个最小的无法分割的独立单元,不允许部分成功部分失败。
利用Innodb的undo log。
undo log名为回滚日志,是实现原子性的关键,当事务回滚时能够撤销所有已经成功执行的sql语句,他需要记录你要回滚的相应日志信息。
例如
(1)当你delete一条数据的时候,就需要记录这条数据的信息,回滚的时候,insert这条旧数据
(2)当你update一条数据的时候,就需要记录之前的旧值,回滚的时候,根据旧值执行update操作
(3)当年insert一条数据的时候,就需要这条记录的主键,回滚的时候,根据主键执行delete操作
undo log记录了这些回滚需要的信息,当事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。
Consistency 一致性-基于其他三个
The consistency property ensures that any transaction will bring the database from one valid state to another. Any data written to the database must be valid according to all defined rules, including constraints, cascades, triggers, and any combination thereof. This does not guarantee correctness of the transaction in all ways the application programmer might have wanted (that is the responsibility of application-level code), but merely that any programming errors cannot result in the violation of any defined rules.
一致性要求任何写到数据库的数据都必须满足于预先定义的规则(比如余额不能小于0、外键约束等),简单来说就是在任何时间点都不能出现违反一致性要求的状态。
Isolation 隔离性-锁&MVCC
The isolation property ensures that the concurrent execution of transactions results in a system state that would be obtained if transactions were executed sequentially, i.e., one after the other. Providing isolation is the main goal of concurrency control. Depending on the concurrency control method (i.e., if it uses strict - as opposed to relaxed - serializability), the effects of an incomplete transaction might not even be visible to another transaction.
隔离性要求如果两个事务修改同一个数据,则必须按顺序执行,并且前一个事务如果未完成,那么未完成的中间状态对另一个事务不可见。
利用的是锁和MVCC机制。
MVCC,即多版本并发控制(Multi Version Concurrency Control),一个行记录数据有多个版本对快照数据,这些快照数据在undo log中。 如果一个事务读取的行正在做DELELE或者UPDATE操作,读取操作不会等行上的锁释放,而是读取该行的快照版本
Durability 持久性 redo log
The durability property ensures that once a transaction has been committed, it will remain so, even in the event of power loss, crashes, or errors. In a relational database, for instance, once a group of SQL statements execute, the results need to be stored permanently (even if the database crashes immediately thereafter). To defend against power loss, transactions (or their effects) must be recorded in a non-volatile memory.
持久性的关键在于一旦“完成提交”(committed),那么数据就不会丢失。
采用redo log的好处?
redo log进行刷盘比对数据页刷盘效率高,具体表现如下
redo log体积小,毕竟只记录了哪一页修改了啥,因此体积小,刷盘快。
redo log是一直往末尾进行追加,属于顺序IO。效率显然比随机IO来的快。
https://www.cnblogs.com/CuiHongYu/p/10845354.html
https://blog.csdn.net/zhuguang10/article/details/90899673
数据库锁&MVCC
在提到隔离性的时候我们提到,在修改同一份数据的情况下,两个事务必须挨个执行以免出现冲突情况。而数据库有四种隔离级别(注意:不是所有数据库支持所有隔离级别)
Isolation Level | Dirty Reads | Non-Repeatable Reads | Phantom Reads |
---|---|---|---|
Read uncommitted | 允许 | 允许 | 允许 |
Read committed | 不允许 | 允许 | 允许 |
Repeatable reads | 不允许 | 不允许 | 允许 |
Serializable | 不允许 | 不允许 | 不允许 |
PS. 大多数数据库的默认隔离级别是Read committed。
来复习一下Dirty reads、Non-repeatable reads、Phantom reads的概念:
Dirty reads:A事务可以读到B事务还未提交的数据
Non-repeatable read:A事务读取一行数据,B事务后续修改了这行数据,A事务再次读取这行数据,结果得到的数据不同。
Phantom reads:A事务通过SELECT … WHERE得到一些行,B事务插入新行或者更新已有的行使得这些行满足A事务的WHERE条件,A事务再次SELECT … WHERE结果比上一次多了一些行。
大多数数据库在实现以上事务隔离级别(Read uncommitted除外)时采用的机制是锁。这也就是为什么经常说当应用程序里大量使用事务或者高并发情况下会出现性能低下、死锁的问题。
https://zhuanlan.zhihu.com/p/117476959
https://www.cnblogs.com/wyaokai/p/10921323.html
2.3.分布式事务
2.3.1.什么是分布式事务。
同一个操作,跨数据源(数据库实例 ),要保证一致性必须用到分布式事务。
下单:订单库 支付:支付库
2.3.2.为什么需要分布式事务
1)系统微服务化后,一个看似简单的功能,内部可能需要调用多个服务并操作多个数据库实现,服务调用的分布式事务问题变的非常突出。
要解决
微服务拆分原则,尽量让大部分操作都不要跨微服务操作,也就是跨库。 分布式事务比本地事务耗费的资源更多。
2.4.小结
本地事务(数据库事务,传统事务):只有一个数据源,只操作一个数据库。
分布式事务:有多个数据源,要同时操作多个数据库实例。
下单:订单库 支付:支付库 库存:库存库
3. 常见分布式事务解决方案
前面已经学习了分布式事务的基础理论,以理论为基础,针对不同的分布式场景业界常见的解决方案有2PC、TCC、 可靠消息最终一致性、最大努力通知这几种。
3.1.2pc
3.1.1.什么是2pc
2PC即两阶段提交协议,是将整个事务流程分为两个阶段,准备阶段( Prepare phase).提交阶段
( commit phase ) , 2是指两个阶段, P是指准备阶段, C是指提交阶段。
常见的标准是XA, JTA,Seata等。
大家都ok,才OK,有一个失败就回滚。
3.1.2.基于XA协议的两阶段提交方案
2PC的传统方案是在数据库层面实现的,如Oracle、MySQL都支持2PC协议,为了统一标准,减少行业内不必要的对接成本,需要制定标准化的处理模型及接口标准,国际开放标准组织Open Group定义了分布式事务处理模型DTP ( Distributed Transaction Processing Reference Model )。
DTP模型定义如下角色:
- AP(Application Program) :即应用程序,可以理解为使用DTP分布式事务的程序。
- RM(Resource Manager) :即资源管理器,可以理解为事务的参与者, 一般情况下是指一个数据库实例,通过资源管理器对该数据库进行控制,资源管理器控制着分支事务。
- TM(Transaction Manager) :事务管理器,负责协调和管理事务,事务管理器控制着全局事务,管理事务生命周期,并协调各个RM。
全局事务是指分布式事务处理环境中,需要操作多个数据库共同完成一个工作,这个工作即是一个全局事务。 - DTP模型定义TM和RM之间通讯的接口规范叫XA ,简单理解为数据库提供的2PC接口协议,基于数据库的XA协议来实现2PC又称为XA方案。
- 以上三个角色之间的交互方式如下:
1 ) TM向AP提供应用程序编程接口, AP通过TM提交及回滚事务。
2 ) TM交易中间件通过XA接口来通知RM数据库事务的开始、结束以及提交、回滚等。
以以上案例为例步骤:
执行流程如下:
1、应用程序( AP )持有用户库和积分库两个数据源(RM)。
2、应用程序( AP )通过TM通知用户库RM新增用户,同时通知积分库RM为该用户新增积分, RM此时并未提交事务,此时用户和积分资源锁定。
3、TM收到执行回复,只要有一方执行失败则分别向其他RM发起回滚事务,回滚完毕,资源锁释放。
4、TM收到执行回复,全部成功,此时向所有RM发起提交事务,提交完毕,资源锁释放。
总结:
整个xa协议2PC的事务流程涉及到三个角色AP、RM、TM。 AP指的是使用2PC分布式事务的应用程序; RM指的是资源管理器,它控制着分支事务; TM指的是事务管理器,它控制着整个全局事务。
1 )在准备阶段RM执行实际的业务操作,但不提交事务,资源锁定;
2 )在提交阶段TM会接受RM在准备阶段的执行回复,只要有任一个RM执行失败 , TM会通知所有RM执行回滚操作,否则, TM将会通知所有RM提交该事务。提交阶段结束资源锁释放。
XA方案的问题:
1、需要本地数据库支持XA协议。Mysql5.7及以后才支持 oracle
2、资源锁需要等到两个阶段结束才释放,性能较差。
3.1.3.Seata的两阶段提交方案
Seata是由阿里中间件团队发起的开源项目Fescar ,后更名为Seata ,它是一个是开源的分布式事务框架。传统2PC的问题在Seata中得到了解决,它通过对本地关系数据库的分支事务的协调来驱动完成全局事务,是工作在应用层的中间件。主要优点是性能较好,且不长时间占用连接资源,它以高效并且对业务0侵入的方式解决微服务场景下面临的分布式事务问题,它目前提供AT模式(即2PC)及TCC模式的分布式事务解决方案。
- Transaction Coordinator(TC):事务协调器,它是独立的中间件,需要独立部署运行,它维护全局事务的运行状态,接收TM指令发起全局事务的提交与回滚,负责与RM通信协调各各分支事务的提交或回滚。 相当于是一个软件需要单独部署
- Transaction Manager ™:事务管理器, TM需要嵌入应用程序中工作,它负责开启一个全局事务,并最终向TC发起全局提交或全局回滚的指令。
- Resource Manager (RM):控制分支事务, 负责分支注册、状态汇报,并接收事务协调器TC的指令, 驱动分支(本地)事务的提交和回滚。
案例:注册用户送积分
具体的执行流程如下:|
1.用户服务的TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID。
2.用户服务的RM向TC注册分支事务,该分支事务在用户服务执行新增用户逻辑,并将其纳入XID对应全局事务的管辖。
3.用户服务执行分支事务,向用户表插入一条记录。
4.逻辑执行到远程调用积分服务时(XID在微服务调用链路的,上下文中传播)。积分服务的RM向TC注册分支事务,该分支事务执行增加积分的逻辑,并将其纳入XID对应全局事务的管辖。
5.积分服务执行分支事务,向积分记录表插入一条记录,执行完毕后,返回用户服务。
3.1.4.Seata实现2PC与传统2PC的差别
架构层次方面,传统2PC方案的RM实际上是在数据库层, RM本质上就是数据库自身,通过XA协议实现,而Seata的RM是以jar包的形式作为中间件层部署在应用程序这一侧的。
两阶段提交方面,传统2PC无论第二阶段的决议是commit还是rollback ,事务性资源的锁都要保持到Phase2完成才释放。而Seata的做法是在Phase1就将本地事务提交,这样就可以省去Phase2持锁的时间,整体提高效率。
3.2.TCC (Try-Confirm-Cancle)
3.2.1.什么是TCC
TCC, 是基于补偿型事务的AP系统的一种实现, 具有弱一致性
事务开始时,业务应用会向事务协调器注册启动事务。之后业务应用会调用所有服务的try接口,完成一阶段准备。之后事务协调器会根据try接口返回情况,决定调用confirm接口或者cancel接口。如果接口调用失败,会进行重试。
代码层次两阶段提交
3.2.2.常见解决方案
目前市面上的TCC框架众多比如下面这几种:
Seata也支持了,现在支持了springcloud
3.2.3.优缺点
优点: 异步执行效率高
TCC能够对分布式事务中的各个资源进行分别锁定, 分别提交与释放, 例如, 假设有AB两个操作, 假设A操作耗时短, 那么A就能较快的完成自身的try-confirm-cancel流程, 释放资源. 无需等待B操作. 如果事后出现问题, 追加执行补偿性事务即可.
TCC是绑定在各个子业务上的(除了cancle中的全局回滚操作), 也就是各服务之间可以在一定程度上”异步并行”执行.
缺点:
对应用的侵入性强。业务逻辑的每个分支都需要实现try、confirm、cancel三个操作,应用侵入性较强,改造成本高。实现难度较大。需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。为了满足一致性的要求,confirm和cancel接口必须实现幂等-一个操作多次循环都是一样。
注意:
事务管理器(协调器)这个节点必须以带同步复制语义的高可用集群(HAC)方式部署.
事务管理器(协调器)还需要使用多数派算法来避免集群发生脑裂问题.
使用场景:
严格一致性
执行时间短
实时性要求高
举例: 红包, 收付款业务.
3.3.可靠消息最终一致性
退货
订单-退货中 库存库-加库存(耗时级长)
3.3.1.方案讲解
可靠消息最终一致性方案是指当事务发起方执行完成本地事务后并发出一条消息,事务参与方(消息消费者)一定能够接收消息并处理事务成功,此方案强调的是只要消息发给事务参与方最终事务要达到一致。
此方案是利用消息中间件完成,如下图:
事务发起方-订单-改订单状态
事务参与方-库存-加库存
3.3.2.要解决的问题
1)本地事务和消息发送原子性
本地事务与消息发送的原子性问题即:事务发起方在本地事务执行成功后消息必须发出去,否则就丢弃消息。即实现本地事务和消息发送的原子性,要么都成功,要么都失败。本地事务与消息发送的原子性问题是实现可靠消息最终一致性方案的关键问题。
2)事务参与方接收消息可靠性 ack机制
事务参与方必须能够从消息队列接收到消息,如果接收消息失败可以重复接收消息
3)消息重复消息问题 幂等性校验
由于网络2的存在,若某一个消费节点超时但是消费成功,此时消息中间件会重复投递此消息,就导致了消息的重复消费。要解决消息重复消费的问题就要实现事务参与方的方法幂等性。
3.3.3.本地消息表方案
本地消息表这个方案最初是eBay提出的,此方案的核心是通过本地事务保证数据业务操作和消息的一致致性,然后通过定时任务将消息发送至消息中间件,待确认消息发送给消费方成功再将消息删除。
优点:简单
缺点:要创建很多本地消息表。
3.3.4.RocketMQ方案
RocketMQ是一个来自阿里巴巴的分布式消息中间件 ,于2012年开源,并在2017年正式成为Apache*项目。据了解,包括阿里云上的消息产品以及收购的子公司在内,阿里集团的消息产品全线都运行在
RocketMQ之.上,并且最近几年的双十一大促中, RocketMQ都有抢眼表现。Apache RocketMQ 4.3之后的版本正式支持事务消息;为分布式事务实现提供了便利性支持。
RocketMQ事务消息设计则主要是为了解决Producer端的消息发送与本地事务执行的原子性问题,
RocketMQ的设计中broker与producer端的双向通信能力,使得broker天生可以作为一个事务协调者存在;而RocketMQ本身提供的存储机制为事务消息提供了持久化能力; RocketMQ的高可用机制以及可靠消息设计则为事务消息在系统发生异常时依然能够保证达成事务的最终一致性。在RocketMQ 4.3后实现了完整的事务消息,实际上其实是对本地消息表的一个封装,将本地消息表移动到了MQ内部,解决Producer端的消息发送与本地事务执行的原子性问题。
执行步骤如下:
1.MQ发送方发送远程事务消息到MQ Server;
2.MQ Server给予响应, 表明事务消息已成功到达MQ Server.
3.MQ发送方Commit本地事务.
4.若本地事务Commit成功, 则通知MQ Server允许对应事务消息被消费; 若本地事务失败, 则通知MQ Server对应事务消息应被丢弃.
5.若MQ发送方超时未对MQ Server作出本地事务执行状态的反馈, 那么需要MQ Servfer向MQ发送方主动回查事务状态, 以决定事务消息是否能被消费.
6.当得知本地事务执行成功时, MQ Server允许MQ订阅方消费本条事务消息.
需要额外说明的一点, 就是事务消息投递到MQ订阅方后, 并不一定能够成功执行. 需要MQ订阅方主动给予消费反馈(ack)
如果MQ订阅方执行远程事务成功, 则给予消费成功的ack, 那么MQ Server可以安全将事务消息移除;
如果执行失败, MQ Server需要对消息重新投递, 直至消费成功.
https://www.cnblogs.com/capacity-yang/p/13067856.html
3.3.5.小结
注意事项
- 消息中间件在系统中扮演一个重要的角色, 所有的事务消息都需要通过它来传达, 所以消息中间件也需要支持 ack 来确保事务消息不丢失.
- 根据业务逻辑的具体实现不同,还可能需要对消息中间件增加消息不重复, 不乱序等其它要求.
适用场景
- 执行周期较长
- 实时性要求不高
例如:
- 跨行转账/汇款业务(两个服务分别在不同的银行中)
- 退货/退款业务
- 财务, 账单统计业务(先发送到消息中间件, 然后进行批量记账)
3.4.最大努力通知
3.4.1.什么是最大努力通知
目标:发起通知方(接口提供方)通过一定的机制最大努力将业务处理结果通知到接收方(接口调用方)。
具体包括:
1、有一定的消息重复通知机制。
因为接收通知方可能没有接收到通知,此时要有一定的机制对消息重复通知。 10s 1min 10min 1h 5h 1d
2、消息校对机制。
如果尽最大努力也没有通知到接收方,或者接收方消费消息后要再次消费,此时可由接收方主动向通知方查询消息信息来满足需求。
3.4.2.最大努力通知与可靠消息一致性有什么不同 ?
可靠消息最终一致性:自己处理完成,发消息让对方处理。
最大努力通知: 先调用别人,别人调用成功,再尽最大努力来通知我。我得到通知了以后再来操作。
3.4.3.方案设计
方案1:
本方案是利用MQ的ack机制由MQ向接收通知方发送通知,流程如下:
1、发起通知方将通知发给MQ。
使用普通消息机制将通知发给MQ。|
注意:如果消息没有发出去可由接收通知方主动请求发起通知方查询业务执行结果。( 后边会讲)
2、接收通知方监听MQ。
3、接收通知方接收消息,业务处理完成回应ack.
4、接收通知*没有回应ack则MQ会重复通知。
MQ会按照间隔1min、5min、 10min、 30min、1h、 2h、 5h、10h的方式,逐步拉大通知间隔( 如果MQ用rocketMq ,在broker中可进行配置) , 直到达到通知要求的时间窗口上限。
5、接收通知方可通过消息校对接口来校对消息的一致性。
内部调用
方案2:
方案1和方案2的不同点:
1、方案1中接收通知方与MQ接口,即接收通知方案监听MQ ,此方案主要应用与内部应用之间的通知。
2、方案2中由通知程序与MQ接口,通知程序监听MQ ,收到MQ的消息后由通知程序通过互联网接口协议调用接收通知方。此方案主要应用于外部应用之间的通知,例如支付宝、微信的支付结果通知。
3.5.各种方案选型
在学习各种分布式事务的解决方案后,我们了解到各种方案的优缺点:
2PC最大的诟病是一个阻塞协议。RM在执行分支事务后需要等待TM的决定,此时服务会阻塞并锁
定资源。由于其阻塞机制和最差时间复杂度高,因此,这种设计不能适应随着事务涉及的服务数量增加而扩
展的需要,很难用于并发较高以及子事务生命周期较长(long-running transactions)的分布式服务中。
如果拿TCC事务的处理流程与2PC两阶段提交做比较, 2PC通常都是在跨库的DB层面,而TCC则在应用层面的处理,需要通过业务逻辑来实现。这种分布式事务的实现方式的优势在于,可以让应用自己定义数据操作的粒度,使得降低锁冲突、提高吞吐量成为可能。而不足之处则在于对应用的侵入性非常强,业务逻辑的每个分支都需要实现try、confirm、 cancel三个操作。 此外,其实现难度也比较大,需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。典型的使用场景:登录送优惠券等。
可靠消息最终一致性事务适合执行周期长且实时性要求不高的场景。引入消息机制后,同步的事务操作变
为基于消息执行的异步操作,避免了分布式事务中的同步阻塞操作的影响,并实现了两个服务的解耦。典型的使用场景:注册送积分,登录送优惠券等。
最大努力通知是分布式事务中要求最低的一种,适用于一些最终一致性时间敏感度低的业务 ;允许发起通知方处理业务失败,在接收通知方收到通知后积极进行失败处理,无论发起通知方如何处理结果都会不影响到接收通知方的后续处理;发起通知方需提供查询执行情况接口,用于接收通知方校对结果。典型的使用 场景:银行通知、支付结果通知等。
总结:
在条件允许的情况下,我们尽可能选择本地事务单数据源,因为它减少了网络交互带来的性能损耗,且避
免了数据弱一致性带来的种种问题。若某系统频繁且不合理的使用分布式事务,应首先从整体设计角度观察服务的拆分是否合理,是否高内聚低耦合?是否粒度太小?分布式事务一直是业界难题,因为网络的不确定性,
而且我们习惯于拿分布式事务与单机事务ACID做对比。
无论是数据库层的XA、还是应用层TCC、可靠消息、最大努力通知等方案,都没有完美解决分布式事务问题,它们不过是各自在性能、一致性、可用性等方面做取舍,寻求某些场景偏好下的权衡。
实时性不高:可靠消息最终一致性
跨行转账/汇款业务(两个服务分别在不同的银行中)
退货/退款业务
财务, 账单统计业务(先发送到消息中间件, 然后进行批量记账)
有的回滚太难,要求必须实现:最大努力通知
支付通知,银行通知。。。。
Seata 2pc或tcc
4. Seata 2pc案例
http://seata.io/zh-cn/
http://seata.io/zh-cn/docs/user/quickstart.html
4.1.启动tc
seata-server.bat -p 8091 -h 127.0.0.1 -m file
4.2.代码实现
4.2.1.准备一个Eureka
启动
4.2.2.准备三个工程
准备sql
导入seata jar
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
<version>2.1.0.RELEASE</version>
<exclusions>
<exclusion>
<artifactId>seata-all</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>${seata.version}</version>
</dependency>
配置-reg,file 数据源
File
#transaction service group mapping
vgroupMapping.fsp_tx_group = "default"
#only support when registry.type=file, please don't set multiple addresses
default.grouplist = "127.0.0.1:8091"
事务处理
@GlobalTransactional
测试
5.课程总结
5.1.重点
5.2.难点
5.3.如何掌握?
1.多多理解
2.学会看说明手册
5.4.排错技巧(技巧)
1…
2…
6.课后练习
1.总结
2.课堂作业
7.面试题
8.扩展知识或课外阅读推荐(可选)
8.1.扩展知识
8.2.课外阅读
微服务那点儿事情
https://blog.csdn.net/w05980598/article/details/79007194/