事务&分布式事务、分布式事务解决方案

1. 文章介绍

Ø 1. 什么是分布式事务;(掌握)

Ø 2. 常见分布式事务解决方案;(掌握)

Ø 3. Seata分布式事务解决方案实现;(掌握)

2. 什么是分布式事务

2.1. 事务是什么

事务: 指作为单个逻辑工作单元(Service方法)执行的一系列操作(数据库操作),要么完全地执行,要么完全地不执行.

事务可以看做是一次大的活动,它由不同的小活动组成,这些活动要么全部成功,要么全部失败。

姚桑总结: 一个service方法内,做了数据库多个数据库增删改,要么都执行,要么多不执行!

== 事务可以分为两大类:本地事务,分布式事务==

2.2. 本地事务-没有跨库操作

2.2.1. 是什么

在同一个服务中操作同一个数据库

什么是本地事务(Local Transaction)?本地事务也称为*数据库事务传统事务*(相对于分布式事务而言)。它的执行模式就是常见的:

  1. transaction begin
  2. insert/delete/update
  3. insert/delete/update

transaction commit/rollback

一个方法加了

@Transactional
@Override
public boolean insert(Employee entity) {
    //1 数据操作1
    //2 数据操作2
    //3 数据操作3
    //4 数据操作4
    // 以上操作都是操作同一个数据库
}
本地事务有这么几个特征:

在同一个服务中操作同一个数据库

l 一次事务只连接一个支持事务的数据库(一般来说都是关系型数据库)

l 事务的执行结果保证ACID

l 会用到数据库锁

事务具有

原子性(Atomicity):

一致性(Consistency):数据库的执行结果和预期保持一致

隔离性(Isolation): 操作是相互隔离的互不影响

持久性(Durability)

四个特性,简称 ACID,缺一不可。

本地事务: 一个方法内存涉及多个增删改数据库操作操作,并且操作都是同一个数据库,要么都成功,要么都失败! 遵循acid. 在springboot中直接加上@Transactional就ok了

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.

关键词在于:

l all or nothing,它的意思是数据库要么被修改了,要么保持原来的状态。所谓保持原来的状态不是我先insert再delete,而是压根就没有发生过任何操作。因为insert然后再delete实际上还是修改了数据库状态的,至少在数据库日志层面是这样。

l indivisible,不可分割,一个事务就是一个最小的无法分割的独立单元,不允许部分成功部分失败。

利用Innodb的undo log,undo log名为回滚日志,是实现原子性的关键,

当事务回滚时能够撤销所有已经成功执行的sql语句,他需要记录你要回滚的相应日志信息。 例如

l 当你delete一条数据的时候,就需要记录这条数据的信息,回滚的时候,insert这条旧数据

l 当你update一条数据的时候,就需要记录之前的旧值,回滚的时候,根据旧值执行update操作

l 当年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、外键约束等),简单来说就是在任何时间点都不能出现违反一致性要求的状态。
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),那么数据就不会丢失。undolog实现事务原子性,redolog实现事务的持久性

Redo Log记录的是新数据的备份。在事务提交前,只要将Redo Log持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是Redo Log已经持久化。系统恢复后可以根据Redo Log的内容,将所有数据恢复到最新的状态。

Undo + Redo**事务的简化过程【重要】**

开始事务

a=1,b=2

Update a=3 原来是1

Update b=4 原来是2

**提交事务/**回滚

\1. A.事务开始.

\2. B.记录A=1到undo log.

\3. C.修改A=3.

\4. D.记录A=3到redo log.

\5. E.记录B=2到undo log.

\6. F.修改B=4.

\7. G.记录B=4到redo log.

\8. H.将redo log写入磁盘。

I.事务提交

通过undo保证事务的原子性,redo保证持久性。

采用redo log****的好处?

redo log进行刷盘比对数据页刷盘效率高,具体表现如下

l redo log体积小,毕竟只记录了哪一页修改了啥,因此体积小,刷盘快。

l redo log是一直往末尾进行追加,属于顺序IO。效率显然比随机IO来的快。

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.

隔离性要求如果两个事务修改同一个数据,则必须按顺序执行

并且前一个事务如果未完成,那么未完成的中间状态对另一个事务不可见。

事务隔离性依赖数据库隔离级别!

SQL 标准定义了四种隔离级别,MySQL 全都支持。这四种隔离级别分别是:

l 读未提交(READ UNCOMMITTED) 出现脏读 a事务读取到b事务还未提交的数据

l 读已提交 (READ COMMITTED) 解决了脏读,出现了不可重复读

l 可重复读(REPEATABLE READ) 解决了不可重复读,出现幻读–间歇锁解决

l 串行化 (SERIALIZABLE) 排队执行,不存在并发操作!安全,但是效率低.

以下几个概念是事务隔离级别要实际解决的问题:(事务并发会出现的问题)脏读,不可重复读,幻读,丢失更新

l 脏读

脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。读到了不一定最终存在的数据,这就是脏读

读未提交会造成脏读,当设置隔离级别为读未提交:set global transaction isolation level read uncommitted 会出现如下问题

IMG_256

读提交可以解决脏读问题,设置为:set global transaction isolation level read committed; 但是没办法做到重复读也解决不了幻读

l 不可重复读

可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据都是一致的,那不可重复读就是一个事务多次读取到的数据是不一致的,通常针对数据更新(UPDATE)操作。即:同一个事务中多次使用相同条件读取到的数据是不一样的

当把事务隔离级别改为读提交级别:set global transaction isolation level read committed; 会出现如下问题:

IMG_256

事务B多次读数据读到的值不一样,要实现可重复读需要把事务隔离级别设置为:

set global transaction isolation level repeatable read;可重复读,这也是Mysql默认的事务隔离策略,但是该事务隔离级别没办法解决幻读问题

l 幻读

事务A 按照一定条件进行数据读取, 期间事务B 插入了相同搜索条件的新数据,事务A再次按照原先条件进行读取时,发现了事务B 新插入的数据 称为幻读 , 即:事物A两次读取相同条件的数据读到的条数不一样

把事务隔离级别设置为:set global transaction isolation level repeatable read;可重复读可能会出现如下问题:

img

事务A两次读取读取到了不同的数据条数,这个就是幻读,但是当你在 MySQL 中测试幻读的时候,并不会出现上图的结果,幻读并没有发生,因为MySQL 的可重复读隔离级别其实解决了幻读问题,它使用到了“间隙锁”。

https://www.cnblogs.com/fengzheng/p/12557762.html 间歇锁

https://www.cnblogs.com/myf008/p/14588380.html 间歇锁

l 串行化

串行化是4种事务隔离级别中隔离效果最好的,解决了脏读、可重复读、幻读的问题,但是效果最差,它将事务的执行变为顺序执行,与其他三个隔离级别相比,它就相当于单线程,后一个事务的执行必须等待前一个事务结束。

事务隔离其实就是为了解决上面提到的脏读、不可重复读、幻读这几个问题,下面展示了 4 种隔离级别对这三个问题的解决程度

隔离级别脏读不可重复读幻读写的同时读并发写
读未提交(Read uncommitted)可能可能可能不上锁不上锁
读提交(Read committed)不可能可能可能mvcc行锁
可重复读(Repeatable reads)不可能不可能可能(mysql间歇锁额外解决)mvcc行锁
串行化(Serializable)不可能不可能不可能读上锁行锁

MySQ事务隔离原来是依靠锁(行锁)来实现的,加锁自然会带来性能的损失。而读未提交隔离级别是不加锁的,所以它的性能是最好的,没有加锁、解锁带来的性能开销。但有利就有弊,这基本上就相当于裸奔啊,所以它连脏读的问题都没办法解决。

利用的是锁和MVCC机制

MVCC的全称是“多版本并发控制”(Multi Version Concurrency Control),。这项技术使得InnoDB的事务隔离级别下执行一致性读操作有了保证,换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值,一个行记录数据有多个版本对快照数据,这些快照数据在undo log中。 如果一个事务读取的行正在做DELELE或者UPDATE操作,读取操作不会等行上的锁释放,而是读取该行的快照版本

这是一个可以用来增强并发性的强大的技术,因为这样的一来的话查询就不用等待另一个事务释放锁。这项技术在数据库领域并不是普遍使用的。一些其它的数据库产品,以及mysql其它的存储引擎并不支持它。

可重复读是在事务开始的时候生成一个当前事务全局性的快照,而读提交则是每次执行读语句的时候都重新生成一次快照 age = 1

IMG_256

https://blog.csdn.net/dingjianmin/article/details/121722287

https://www.cnblogs.com/CuiHongYu/p/10845354.html

https://blog.csdn.net/zhuguang10/article/details/90899673

https://zhuanlan.zhihu.com/p/117476959

https://www.cnblogs.com/wyaokai/p/10921323.html

https://www.cnblogs.com/fengzheng/p/12557762.html

MVCC只在 READ COMMITTED (读取已提交) 和 REPEATABLE READ (可重复读) 两个隔离级别下工作。其他两个隔离级别够和MVCC不兼容, 因为 READ UNCOMMITTED (读取未提交) 总是读取最新的数据行, 而不是符合当前事务版本的数据行。而 SERIALIZABLE (可串行化) 则会对所有读取的行都加锁。

https://blog.csdn.net/SIESTA030/article/details/123113437?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-0-123113437-blog-125754002.pc_relevant_multi_platform_whitelistv3&spm=1001.2101.3001.4242.1&utm_relevant_index=3 mvcc

https://blog.csdn.net/m0_62436868/article/details/127202062

2.3. 分布式事务-跨库

2.3.1. 什么是分布式事务。

针对于单数据库的事务我们叫着本地事务/传统事务,在分布式环境中一个请求可能涉及到多个数据库的写操作(多数据源),要保证多数据源的一致性必须用到分布式事务。

2.3.2. 为什么需要分布式事务

系统微服务化后,一个看似简单的功能,内部可能需要调用多个服务并操作多个数据库实现,服务调用的分布式事务问题变的非常突出。

img

一个下单请求同时涉及到订单库,优惠券库,库存库的写操作,需要保证三个库写操作的一致性,就要用到分布式事务 即==:分布式事务就是要解决一个请求同时对多个数据库写操作的一致性==

注意:微服务拆分原则,尽量让大部分操作都不要跨微服务操作,也就是跨库。 分布式事务比本地事务耗费的资源更多。

项目中: 保存租户前需要保存对应管理员登录信息,这是一个微服务的远程调用,操作一个数据库.

保存租户等信息访问的是系统管理微服务,又是一个数据库,要保证他们一致性需要用分布式事务!

img

2.4. 小结

面试题:

l 什么是事务,解决什么问题(数据一致性)

l InnoDB是如何保证原子性的?a

l InnoDB是如保证持久化的? d

l 事务隔离级别的实现原理是怎么样的?i

读未提交:不上锁

读已提交/可重复读:行锁(写写)、间歇锁(范围查询幻读),mvcc(读写并发问题) 写写 写读 读读

串行化:任何操作都上锁

3.常见分布式事务解决方案

前面已经学习了分布式事务的基础理论,以理论为基础,针对不同的分布式场景业界常见的解决方案有2PC、TCC、 可靠消息最终一致性、最大努力通知这几种。

3.1. 2pc

3.1.1. 什么是2pc

2PC即两阶段提交协议,是将整个事务流程分为两个阶段,准备阶段( Prepare phase).提交阶段( commit phase ) , 2是指两个阶段, P是指准备阶段, C是指提交阶段。

img

img

在第一阶段(准备阶段),事务管理器向事务参与者(资源)们发送准备请求,大家都返回OK状态,那么就进入第二阶段,提交事务,如果在第一阶段有任何一个参与者没有OK,那么事务协调器通知其他所有事务参与者(资源)回滚事务。2PC常见的标准是XA, JTA,Seata等。

**二阶段能保证分布式事务的原子性,但是也有一些明显的缺陷。**比如:

l 在第一阶段,如果参与者迟迟不回复协调者,就会造成事务的阻塞,性能不好。

l 单节点故障,如果协调器挂了,参与者会阻塞,比如在第二阶段,如果事务协调器宕机,参与者没办法回复信息,长时间处于事务资源锁定,造成阻塞(事务操作是要加锁的)。

l 在第二阶段,如果在事务协调器发出"commit"执行后宕机,一部和参与者收到了消息提交了事务,而 一部分没有消息没法做出事务提交操作,这样就出现了数据不一致。

l 在第二阶段,如果事务事务协调器发出“commit”指令后宕机,收到“commmit”指令的参与者也宕机了, 那么事务最终变成了什么效果,提交了还是没提交?没有谁知道。

3.1.2. 基于XA协议的两阶段提交方案

2PC的传统方案是在数据库层面实现的,如Oracle、MySQL都支持2PC协议,为了统一标准,

减少行业内不必要的对接成本,需要制定标准化的处理模型及接口标准,国际开放标准组织Open Group定义了分布式事务处理模型DTP ( Distributed Transaction Processing Reference Model )。

img

DTP****模型定义如下角色

l AP(Application Program) :即应用程序,可以理解为使用DTP分布式事务的程序。

l TM(Transaction Manager) :事务管理器,负责协调和管理事务,事务管理器控制着全局事务,管理事务生 命周期,并协调各个RM。

l RM(Resource Manager) :即资源管理器,可以理解为事务的参与者, 一般情况下是指一个数据库实例,通过资源管理器对该数据库进行控制,资源管理器控制着分支事务。

l 全局事务是指分布式事务处理环境中,需要操作多个数据库共同完成一个工作,这个工作即是一个全局事务。

l DTP模型定义TM和RM之间通讯的接口规范叫XA ,

简单理解为数据库提供的2PC接口协议,基于 数据库的XA协议来实现2PC又称为XA方案。

以上三个角色之间的交互方式如下:

l TM向AP提供应用程序编程接口, AP通过TM提交及回滚事务。

l TM中间件通过XA接口来通知RM数据库事务的开始、结束以及提交、回滚等。

以以上案例为例步骤 执行流程如下:

1、应用程序( AP )持有用户库和积分库两个数据源(RM)。

2、应用程序( AP )通过TM通知用户库RM新增用户,同时通知积分库RM为该用户新增积分, RM此时 并未提交事务,此时用户和积分资源锁定。

3、TM收到执行回复,只要有一方执行失败则分别向其他RM发起回滚事务,回滚完毕,资源锁释放。

4、TM收到执行回复,全部成功,此时向所有RM发起提交事务,提交完毕,资源锁释放。

XA****方案的问题:

l 需要本地数据库支持XA协议。Mysql5.7及以后才支持 oracle

l 资源锁需要等到两个阶段结束才释放,性能较差

3.1.3. Seata的两阶段提交方案(AT模式)

Seata是由阿里中间件团队发起的开源项目Fescar ,后更名为Seata ,它是一个是开源的分布式事务框架。传统2PC的问题在Seata中得到了解决,它通过对本地关系数据库的分支事务的协调来驱动完成全局事务,是工作在应用层的中间件。主要优点是性能较好,且不长时间占用连接资源,它以高效并且对业务0侵入的方式解决微服务场景下面临的分布式事务问题,它目前提供AT模式(即2PC)及TCC模式的分布式事务解决方案。

l Transaction Coordinator(TC):事务协调器,它是独立的中间件,需要独立部署运行,它维护全局事务的运行状态,接收TM指令发起全局事务的提交与回滚,负责与RM通信协调各各分支事务的提交或回滚。 相当于是一个软件需要单独部署

l Transaction Manager ™:事务管理器, TM需要嵌入应用程序中工作,它负责开启一个全局事务,并最终 向TC发起全局提交或全局回滚的指令。

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

案例:注册用户送积分 @GlobalTrasationl

img

具体的执行流程如下:

\1. 用户服务的TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID。

\2. 用户服务的RM向TC注册分支事务,该分支事务在用户服务执行新增用户逻辑,并将其纳入XID对应 全局事务的管辖。

\3. 用户服务执行分支事务,向用户表插入一条记录。记录undolog

\4. 逻辑执行到远程调用积分服务时(XID在微服务调用链路的,上下文中传播)。积分服务的RM向TC 注册分支事务,该分支事务执行增加积分的逻辑,并将其纳入XID对应全局事务的管辖。

\5. 积分服务执行分支事务,向积分记录表插入一条记录,执行完毕后,返回用户服务。记录undolog

\6. 如果都是成功的执行第二阶级提交,不做数据库操作,需要分支事务删除undolog

\7. 如果失败 执行第二阶级提交,根据undolog日志做回滚

3.1.4. Seata实现2PC与传统2PC的差别

架构层次方面,传统2PC方案的RM实际上是在数据库层, RM本质上就是数据库自身,通过XA协议实现,而Seata的RM是以jar包的形式作为中间件层部署在应用程序这一侧的。

两阶段提交方面,传统2PC无论第二阶段的决议是commit还是rollback ,事务性资源的锁都要保持到Phase2完成才释放。而Seata的做法是在Phase1就将本地事务提交,这样就可以省去Phase2持锁的时间,整体提高效率。

2pc:

​ 优点:实时一致性

​ 缺点:子事务周期不能太长,并发不能太高

3.2. TCC (Try-Confirm-Cancle)

3.2.1. 什么是TCC

TCC, 是基于补偿型事务的AP系统的一种实现, 具有弱一致性

补偿理解:按照事先预定的方案去执行,如果失败了就走补偿方案(撤销) 下单(try confirm cancel) 扣库存(try confirm cancel)

img

事务开始时,业务应用会向事务协调器注册启动事务。之后业务应用会调用所有服务的try接口,完成一阶段准备。之后事务协调器会根据try接口返回情况,决定调用confirm接口或者cancel接口。如果接口调用失败,会进行重试。

3.2.2. 常见解决方案

目前市面上的TCC框架众多比如下面这几种:

img

Seata也支持TCC,现在支持了springcloud

3.2.3. 优缺点

l 优点: 异步执行效率高

TCC能够对分布式事务中的各个资源进行分别锁定, 分别提交与释放, 例如, 假设有AB两个操作, 假设A操作耗时短, 那么A就能较快的完成自身的try-confirm-cancel流程, 释放资源. 无需等待B操作. 如果事后出现问题, 追加执行补偿性事务即可.

TCC是绑定在各个子业务上的(除了cancle中的全局回滚操作), 也就是各服务之间可以在一定程度上”异步并行”执行.

l 缺点:

对应用的侵入性强。业务逻辑的每个分支都需要实现try、confirm、cancel三个操作,应用侵入性较强,改造成本高。实现难度较大。需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。为了满足一致性的要求,confirm和cancel接口必须实现幂等-一个操作多次循环都是一样。

l 注意:

事务管理器(协调器)这个节点必须以带同步复制语义的高可用集群(HAC)方式部署.

事务管理器(协调器)还需要使用多数派算法来避免集群发生脑裂问题.

使用场景:严格一致性 | 执行时间短 | 实时性要求高 | 举例: 红包, 收付款业务.

3.3. 可靠消息最终一致性

3.3.1. 方案讲解

可靠消息最终一致性方案是指当事务发起方执行完成本地事务后并发出一条消息,事务参与方(消息消费者)一定能够接收消息并处理事务成功,此方案强调的是只要消息发给事务参与方最终事务要达到一致。

此方案是利用消息中间件完成,如下图:

img

3.3.2. 要解决的问题

最终一致性要解决三个问题:本地事务和消息发送的原子性,接收消息的可靠性,消息重复消费问题

\1) 本地事务和消息发送原子性

本地事务 消息

本地事务与消息发送的原子性问题即:事务发起方在本地事务执行成功后消息必须发出去,否则就丢弃消息。即实现本地事务和消息发送的原子性,要么都成功,要么都失败。本地事务与消息发送的原子性问题是实现可靠消息最终一致性方案的关键问题。

//1.Begin transaction //2.数据库操作 //3.发送MQ //4.Commit transation;

\2) 事务参与方接收消息可靠性 消息持久化 手动ack机制

事务参与方必须能够从消息队列接收到消息,如果接收消息失败可以重复接收消息

\3) 消息重复消息问题 幂等性校验

由于网络2的存在,若某一个消费节点超时但是消费成功,此时消息中间件会重复投递此消息,就导致了消息的重复消费。要解决消息重复消费的问题就要实现事务参与方的方法幂等性。

3.3.3. 本地消息表方案

img

本地消息表这个方案最初是eBay提出的,此方案的核心是通过本地事务保证数据业务操作和消息的一致致性,然后通过定时任务将消息发送至消息中间件,待确认消息发送给消费方成功再将消息删除。

img

优点:简单

缺点:要创建很多本地消息表。

3.3.4. RocketMQ方案 rabbitmq kafka RocketMQ(延迟消息,事务消息)

RocketMQ是一个来自阿里巴巴的分布式消息中间件 ,于2012年开源,并在2017年正式成为Apache顶级项目。据了解,包括阿里云上的消息产品以及收购的子公司在内,阿里集团的消息产品全线都运行在

RocketMQ之.上,并且最近几年的双十一大促中, RocketMQ都有抢眼表现。Apache RocketMQ 4.3之后的版本正式支持事务消息;为分布式事务实现提供了便利性支持。

RocketMQ事务消息设计则主要是为了解决Producer端的消息发送与本地事务执行的原子性问题,

RocketMQ的设计中broker与producer端的双向通信能力,使得broker天生可以作为一个事务协调者存在;而RocketMQ本身提供的存储机制为事务消息提供了持久化能力; RocketMQ的高可用机制以及可靠消息设计则为事务消息在系统发生异常时依然能够保证达成事务的最终一致性。在RocketMQ 4.3后实现了完整的事务消息,实际上其实是对本地消息表的一个封装,将本地消息表移动到了MQ内部,解决Producer端的消息发送与本地事务执行的原子性问题。

img

执行步骤如下:

  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. 主流MQ对比
特性ActiveMQRabbitMQRocketMQ**Kafka(**大数据)
开发语言JavaErlangJavaScala-spark
单机吞吐量万级万级10万级10万级
时效性Ms级Us级Ms级Ms级别以内
可用性高(主从架构)高(主从架构)非常高(分布式架构)非常高(分布式架构)
功能特性老牌产品,很多公司得到应用基于erlang开发,并发能力强,性能好,管理界面丰富MQ功能完善,扩展性佳只支持主要的MQ功能,大数据领域应用广泛

img

3.3.6. 小结

注意事项

l 消息中间件在系统中扮演一个重要的角色, 所有的事务消息都需要通过它来传达, 所以消息中间件也需要支持 ack 来确保事务消息不丢失.

l 根据业务逻辑的具体实现不同,还可能需要对消息中间件增加消息不重复, 不乱序等其它要求.

适用场景

l 执行周期较长

l 实时性要求不高

例如:

l 跨行转账/汇款业务(两个服务分别在不同的银行中)

l 退货/退款业务

l 财务, 账单统计业务(先发送到消息中间件, 然后进行批量记账)

3.4. 最大努力通知

3.4.1. 什么是最大努力通知

最大努力通知服务表示在不影响主业务的情况下,尽可能地确保数据的一致性。它需要开发人员根据业务来指定通知规则,在满足通知规则的前提下,尽可能的确保数据的一致,以达到最大努力的目的。

img

目标:发起通知方(接口提供方)通过一定的机制最大努力将业务处理结果通知到接收方(接口调用方)。

具体包括:

l 有一定的消息重复通知机制。

因为接收通知方可能没有接收到通知,此时要有一定的机制对消息重复通知。 10s 1min 10min 1h 5h 1d

l 消息校对机制。

如果尽最大努力也没有通知到接收方,或者接收方消费消息后要再次消费,此时可由接收方主动向通知 方查询消息信息来满足需求。

l 消息处理方需要保证幂等性

场景:支付结果通知

3.4.2. 最大努力通知与可靠消息一致性有什么不同 ?

l 可靠消息最终一致性

系统A本地事务执行成功,通知系统B处理任务,通常通过MQ实现。一般适用于平台内部,对一 致性要求相对较高(微服务的2个子系统之间)。

l 最大努力通知

所谓最大努力通知就是系统A用最大努力通知系统B,能不能成功,不做完全保证,如果没通知到位, 系统B可以主动来调用系统A的接口查询结果状态。一般适用于跨平台业务,或对接了上方平台的业 务场景(支付结果通知)。

3.4.3. 方案设计 ronghua-pay

l 内部系统,子系统之间的消息通知

img

本方案是利用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、接收通知方可通过消息校对接口来校对消息的一致性。

l 外部应用,跨平台的数据一致性保证

img

方案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三个操作。 此外,其实现难度也比较大,需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。典型的使用场景:登录送优惠券等。

可靠消息最终一致性事务适合执行周期长或者并发高且实时性要求不高的场景。引入消息机制后,同步的事务操作变为基于消息执行的异步操作,避免了分布式事务中的同步阻塞操作的影响,并实现了两个服务的解耦。典型的使用场景:注册送积分,登录送优惠券等。 适用于并发较高以及子事务生命周期较长-----异步订单(下单,创建支付单,锁定库存)

最大努力通知是分布式事务中要求最低的一种,适用于一些最终一致性时间敏感度低的业务 ;允许发起通知方处理业务失败,在接收通知方收到通知后积极进行失败处理,无论发起通知方如何处理结果都会不影响到接收通知方的后续处理;发起通知方需提供查询执行情况接口,用于接收通知方校对结果。典型的使用 场景:银行通知、支付结果通知等。

img

总结:

在条件允许的情况下,我们尽可能选择本地事务,因为它减少了网络交互带来的性能损耗,且避免了数据弱一致性带来的种种问题。若某系统频繁且不合理的使用分布式事务,应首先从整体设计角度观察服务的拆分是否合理,是否高内聚低耦合?是否粒度太小?分布式事务一直是业界难题,因为网络的不确定性,而且我们习惯于拿分布式事务与单机事务ACID做对比。

无论是数据库层的XA、还是应用层TCC、可靠消息、最大努力通知等方案,都没有完美解决分布式事务问题,它们不过是各自在性能、一致性、可用性等方面做取舍,寻求某些场景偏好下的权衡。

如何选择:

l 实时性不高或并发高:可靠消息最终一致性

跨行转账/汇款业务(两个服务分别在不同的银行中)

退货/退款业务

财务, 账单统计业务(先发送到消息中间件, 然后进行批量记账)

下单

l 调用我后进最大努力通知

支付通知

l 实时要求高并且并发不高

Seata 2pc

4. Seata 2pc案例

img

http://seata.io/zh-cn/

http://seata.io/zh-cn/docs/user/quickstart.html

4.1. 启动tc

下载事务协调器TC:img ,解压后执行启动命令:

seata-server.bat -p 8091 -h 127.0.0.1 -m file

4.2. 代码实现
4.2.1. 准备一个Eureka

案例见:code/dwx-demo.zip

4.2.2. 准备三个工程
准备sql img

导入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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值