一、MySQL事务
1、事务的基本概念
1》事务特性:
原子性:事务的所有操作要么全部执行成功,要么全部执行失败
一致性:事务操作执行之前和执行之后,数据始终处于一致的状态
隔离性:并发执行的两个事务之间互相不干扰。
持久性:事务提交完成之后,事务对数据的更改操作会被持久化到数据库中,并且不会被回滚
2》事务类型
扁平事务:最常见的事务。所有操作都是处于同一层次,其由BEGIN WORK开始,由COMMIT WORK或ROLLBACK WORK结束,期间的操作是原子的,要么都执行,要么都回滚
带有保存点的扁平事务:在事务内部某个位置设置了扁平点,达到将事务回滚到某个扁平点的目的【savepoint [savepoint_name]】
链式事务:在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式的传给下一个要开始的事务。提交事务操作和开始下一个事务操作将合并为一个原子操作
嵌套事务:是一个层次结构架构。由一个顶层事务(top-level transaction)控制着各个层次的事务。顶层事务之下嵌套的事务被称为子事务(subtransaction),其控制每个局部的变换。MySQL不支持嵌套事务
分布式事务:事务的参与者,事务所在的服务器、设计的资源服务器以及事务管理器等分别位于不同分布式系统的不同服务或数据库节点上。整个事务包含一个或者多个分支事务。
3》本地事务:基于关系型数据库的事务
2、MySQL事务基础
1》并发事务带来的问题:
a、更新丢失:两个及两个以上的事务更新同一行数据时,基于最初选定的值进行更新,会出现事务A的更新覆盖掉事务B的更新
b、脏读:一个事务读取到了另一个事务未提交的数据
c、不可重复读:同一个事务内,使用相同的查询语句,在不同时刻读取到的结果不一致
d、幻读:一个事务按照相同的查询条件重新读取之前读过的数据,其他事务插入了满足查询条件的新数据
不可重复读和幻读的区别:不可重复度重点在更新和删除;幻读重点在插入。幻读无法使用行级锁来避免,需要使用串行化的事务隔离级别来限制。
MySQL使用了基于乐观锁的MVCC机制来避免不可重复度和幻读
1》事务隔离级别:
InnoDB存储引擎支持的4中事务隔离级别:
读已提交、读未提交、可重复度、串行化。使用--transaction-isolation/在my.conf、my.ini里设置隔离级别
一般数据库默认设置的是RR,可重复度
2》锁分类:
3》InnoDB中的MVCC原理
3、MySQL事务的实现原理
1》Redo Log
2》Undo Log
3》Binlog
二、Spring事务
Spring事务的使用方式:
@Service
public class TestTransactional {
@Transactional(propagation = Propagation.REQUIRED, // 事务的传播行为,默认值为 REQUIRED
rollbackFor = Exception.class,
isolation = Isolation.DEFAULT, //事务的隔离级别,默认值采用 DEFAULT
timeout = 60, //事务的超时时间,默认值为-1(不会超时)单位是s
readOnly = true) //指定事务是否为只读事务,默认值为 false。
public void testTransactional() {
// TODO: 2022/7/4 事务处理
}
}
1、Spring事务原理
Spring事务极大的简化了对于数据库事务的管理操作。通过 @Transactional 注解实现对数据库事务的管理,不必手动开启、提交、回滚事务;Spring在启动创建bean的时候会扫描带有@Transactional的方法,并为其生成代理对象,注入注解带有的参数,在代理对象中处理相应的事务。
2、Spring事务回滚规则
@Transactional(rollbackFor=XXXException.class),没有设置rollBackFor时,默认回滚RuntimeException、Error异常
3、Spring事务注解原理
@Transactional 注解是使用AOP实现的,AOP又是基于动态代理实现的
@Transactional 注解的话,Spring 容器就会在启动的时候为其创建一个代理类,在调用被@Transactional 注解的 public 方法的时候,实际调用的是,TransactionInterceptor 类中的 invoke()方法,TransactionInterceptor 类中的 invoke()方法内部实际调用的是 TransactionAspectSupport 类的 invokeWithinTransaction()方法
4、Spring事务管理接口
PlatformTransactionManager 会根据 TransactionDefinition 的定义比如事务超时时间、隔离级别、传播行为等来进行事务管理 ,而 TransactionStatus 接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。
1、PlantformTransactionManager
Spring提供了多种事务管理器接口,例如PlantformTransactionManager,通过这个接口将事务管理职责委托给了持久化框架:MyBatis..
public interface PlatformTransactionManager {
//获得事务
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
//提交事务
void commit(TransactionStatus var1) throws TransactionException;
//回滚事务
void rollback(TransactionStatus var1) throws TransactionException;
}
2、TransactionDefinition接口
定义了与事务相关的方法,表示事务属性常量信息
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
int TIMEOUT_DEFAULT = -1;
// 返回事务的传播行为,默认值为 REQUIRED。
int getPropagationBehavior();
//返回事务的隔离级别,默认值是 DEFAULT
int getIsolationLevel();
// 返回事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
int getTimeout();
// 返回是否为只读事务,默认值为 false
boolean isReadOnly();
@Nullable
String getName();
}
3、TransactionStatus接口
该接口主要存储事务执行状态,同时有一些接口用来判断或读取事务状态信息
public interface TransactionStatus extends SavepointManager, Flushable {
boolean isNewTransaction(); // 是否是新的事务
boolean hasSavepoint(); // 是否有恢复点
void setRollbackOnly(); // 设置为只回滚
boolean isRollbackOnly(); // 是否为只回滚
boolean isCompleted; // 是否已完成
}
事务超时属性:事务超时,就是指一个事务所允许执行的最长时间,超过该时间,事务还未执行完,则事务执行回滚
在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒,默认值为-1,这表示事务的超时时间取决于底层事务系统或者没有超时时间
事务只读属性:
只读事务的好处:
- 避免了每提交一个sql,数据库对每个sql都开启事务
- 一批只读sql,在事务期间是可重复读的,不会读取到其他事务的变更
5、Spring事务隔离级别
5种隔离级别。包含在Isolation枚举中
Spring事务隔离级别比数据库事务隔离级别多一个default
1) DEFAULT (默认) 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应。
2) READ_UNCOMMITTED (读未提交) 这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
3) READ_COMMITTED (读已提交) 保证一个事务修改的数据提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。
4) REPEATABLE_READ (可重复读) 这种事务隔离级别可以防止脏读、不可重复读,但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了不可重复读。
5) SERIALIZABLE(串行化) 这是花费最高代价但是最可靠的事务隔离级别,事务被处理为顺序执行。除了防止脏读、不可重复读外,还避免了幻像读。
6、Spring事务传播类型
作用:事务传播行为是为了解决业务层方法之间互相调用的事务问题。
事务传播类型包含7种,包含在Propagation枚举类中。设置方式@Transactional(propagtion = Propagation.REQUIRED)
1) required(默认属性)
Spring中默认传播类型
如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。 被设置成这个级别时,会为每一个被调用的方法创建一个逻辑事务域。如果前面的方法已经创建了事务,那么后面的方法支持当前的事务,如果当前没有事务会重新建立事务。
2) Mandatory 支持当前事务,如果当前没有事务,就抛出异常。
3) Never 以非事务方式执行,如果当前存在事务,则抛出异常。
4) Not_supports 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
5) requires_new 新建事务,如果当前存在事务,把当前事务挂起。
6) Supports 支持当前事务,如果当前没有事务,就以非事务方式执行。
4)5)6):不经常使用,因为是非事务形式
7) Nested 支持当前事务,新增Savepoint点,与当前事务同步提交或回滚。 嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。
常用的类型:required、not_supports、requires_new
常问问题:Nested与requires_new的区别?
实践场景:
前提----内外方法在不同类里
① 外部方法无事务注解,内部方法添加REQUIRED事务传播类型时。内部方法抛出异常,内部方法执行失败不会影响外部方法的执行,外部方法执行成功。
② 外部方法添加REQUIRED事务传播类型,内部方法无事务注解。内部方法抛出异常,会导致外部事务回滚。
③ 内外部方法都添加REQUIRED事务传播类型时。内部方法抛出异常,会导致外部事务回滚。
④ 外部方法添加REQUIRED,内部方法添加NOT_SUPPORTED。内部方法抛异常,外部执行成功,事务会提交;外部方法执行失败,事务会回滚。
⑤ 外部方法添加REQUIRED,内部方法添加REQUIRES_NEW。
内部抛出异常,内外方法都执行失败,事务回滚。
异常在外部方法尾部时,内部方法执行失败,内外执行失败,事务回滚;内部方法执行成功,事务提交
异常在外部方法尾部时,内外方法在同一个类中。内部抛出异常,内外执行失败,事务回滚
7、事务失效场景
- 事务所在的类没有被Spring管理
- 方法没有被public修饰
- 同一个类中方法调用
- 不正确的使用try-catch捕获了异常
- 标注错误的异常类型,Spring中默认的事务异常类型是RuntimeException
三、分布式系统演进
1、架构演变
单体服务 -> 垂直架构 -> SOA架构 -> 微服务架构
SOA架构:
微服务:
SOA在垂直架构的基础上,抽离出重叠的功能作为公共的服务,进行规范的、统一的信息交互,解决混乱调用问题。
微服务架构将单一的应用程序拆分为一组小型服务,每个服务都是围绕业务能力进行构建,能够独立开发、测试、部署,服务间采用轻量级的通讯机制。
SOA架构和微服务架构的区别:
SOA架构下会多一层API服务,API服务调用多个独立的子服务,完成功能的聚合
微服务去中心化,去掉了API层
2、分布式事务场景
① 跨JVM进程。服务与服务之间通过RPC调用
② 跨数据库实例。服务跨数据源访问多个数据库
③ 多个服务访问同一个数据库实例。
3、数据一致性问题
① 多个节点缓存数据不一致问题。缓存的脑裂
② 缓存与数据库数据不一致。
③ 调用时超时。A服务调用B服务超时,B服务数据更新执行成功与否?不确定
④ 数据多副本场景。
4、分布式事务理论
CAP理论:一致性、可用性、分区容忍性
① 一致性:用户对数据的更新操作,要么在所有的数据副本都执行成功,要么在所有副本都是执行失败,数据副本的修改必须是原子性
② 可用性:应用程序访问数据时能够快速响应结果数据;不能出现超时、错误的情况
③ 分区容忍性:主从集群部署在不同的网络节点上,网络通信失败时保证系统扔能对外提供服务
CAP组合:
C:
A:
P:
AP、CP、CA三种组合
AP大部分分布式系统会采用AP方式,舍弃一致性,通过最终一致性保证数据的一致
Base理论:
base理论是AP理论的一个扩展,通过牺牲强一致性获得可用性,保证最终一致性
四、分布式事务解决方案
1、强一致性
强一致性:任何时刻查询参与全局事务的各节点的数据都是一致性的
方案:
1> DTP模型
是一套分布式事务标准,定义实现了分布式事务的规范和API。有3个核心组件:AP、TM、RM。
AP:应用程序。
RM:资源管理器。数据库管理系统或消息服务管理系统。
TM:事务管理器。协调和管理DTP模型中的事务,同时管理资源管理器
2> 2PC模型
两阶段提交协议模型。Prepare阶段、Commit阶段;
缺点:
同步阻塞。等待事务提交和执行结果都是同步等待
单点故障。事务管理器自身的存在故障时影响资源管理器操作
3> 3PC模型
三阶段提交模型。存在事务管理器和资源管理器
事务执行成功流程:
阶段一:事务管理器向资源管理器发送CanCommit消息;资源管理器向事务管理器返回Yes
阶段二:事务管理器向资源管理器发送PreCommit消息,资源管理器开始执行事务操作,将Undo和Redo写入事务日志;并返回Ack消息
阶段三:事务管理器向资源管理器发送PreCommit消息,资源管理器执行事务提交操作,返回已提交状态
事务执行失败流程:
阶段一:事务管理器向资源管理器发送CanCommit消息;异常资源管理器向事务管理器返回No
阶段二:事务管理器向资源管理器发送Abort消息,资源管理器中断事务操作
阶段三:事务管理器向资源管理器发送doRollBack消息,资源管理器利用Undo log回滚事务
3PC模型解决了2PC模型中的阻塞问题,资源管理器接收不到事务管理器的消息(超时)会自动提交事务,但是会导致数据不一致,提交的事务数据不能再回滚
2、最终一致性
并不要求参与事务的各节点的数据时刻保持一致,允许其存在中间状态,只要一段时间后,能够达到数据的最终一致状态
服务模式:
a、可查询操作
服务操作具有可标识性,业务服务需要提供根据唯一标识查询信息的接口
b、幂等操作
重试调用执行多少次都能输出相同的结果
c、TCC操作
三阶段:1、Try阶段;2、Confirm阶段;3、Cancel阶段
d、可补偿操作
如果数据处于不正常状态,可通过某种方式进行业务补偿,使数据达到一致性
2.1、解决方案
2.1.1、TCC
在应用层将一个完整的事务分为三个阶段:Try、Confirm、Cancel。
实现的模式:可查询操作、幂等操作、TCC操作、可补偿操作
TCC需要注意的问题:
1、空回滚问题。调用Try之前服务网络问题,网络回复后回滚执行Cancel方法时,就会出现空回滚;解决办法:使用事务全局ID和分支事务ID记录,执行Try时往分支记录表中插入一条记录,执行Cancel时判断是否存在调用再回滚
2、幂等问题。调用超时或者网络等原因,未知分支事务的执行情况,TCC会有重试机制,重试时容易导致数据不一致问题,需要保证Commit和Cancel阶段的幂等。解决方案:分支事务记录表上需要记录事务的执行状态,执行Commit和Cancel前先判断状态
3、悬挂问题。
解决办法:
执行Try时判断分支记录表中是否存在全局是武侠Confirm和Cancel的记录操作,有则不执行Try的方法
2.1.2、可靠消息最终一致性
概念:
事务的发起方执行完本地事务之后发出一条消息,事务参与方一定能接收到这条消息并处理成功。消息数据能独立存储,解决系统之间的耦合
基于本地消息表和消息队列中间件两种方式实现。
实现的模式:可查询操作、幂等操作
执行过程:
最重要的是消息确认服务和消息恢复服务。
需要注意的问题:
a、事务发送方本地执行和发送消息的原子性
b、事务参与方接收消息的可靠性问题
c、事务参与方接收到消息的幂等性问题
2.2.3、最大努力通知型
消息发送方服务,接收方服务,允许消息丢失;事务被动方处理结果不影响主动方的处理结果。
解决方式:
a、主动方设置时间阶梯型通知规则,消息丢失后重试
b、被动方应做到幂等,避免重复消息导致数据不一致
c、达到重试次数后。主动方提供事务状态查询的回查接口供被动方未接收到消息时的查询功能
实现的模式:可查询模式、幂等模式
五、分布式事务原理
1、XA强一致性分布式
2、TCC分布式
一个完整的分布式事务包含:主业务服务(事务发起方)、从业务服务(事务操作方)、TCC管理器
核心原理:
需要将原本的一个事务接口改造成三个不同的事务逻辑。提供Try、Commit、Cancel的操作
三个阶段不同的处理内容:
在业务系统中设计者三个阶段的时候需要考虑许多问题:
a、发生异常时如何处理?记录一些分布式事务的日志
TCC应用于订单支付的案例:用户下单、产生一笔订单、扣库存、加积分,生成出货单。
Try阶段:
a、订单更新为支付中
b、库存服务。商品的冻结字段中+此次订单商品个数,商品库存数量减去当前订单商品个数
c、积分服务中,订单产生的积分记录到用户积分表中预增加字段
d、仓储服务。出货单状态标记为“未知”
Confirm阶段:
a、订单更新为支付成功
b、库存服务。商品库存数-订单的商品个数,预扣减字段-订单商品个数
c、积分服务中,用户积分表中增加积分
d、仓储服务。出货单状态标记为“已创建”
Cancel阶段:
a、订单更新为支付失败
b、库存服务。预扣减字段-商品个数;商品库存数+订单的商品个数
c、积分服务中,预增加字段-积分数
d、仓储服务。出货单状态标记为“已取消“
TCC分布式事务框架:Hmily。关键技术:
1、AOP切面
2、反射技术。实现Confirm、Cancel阶段的调用
3、持久化技术
4、序列化技术。多服务之间通信
5、定时任务
6、动态代理
3、可靠消息最终一致性
原理:事务发起方执行本地事务成功后发出一条消息,事务参与方接收到事务发起方发送过来的消息,并成功执行本地事务。主要有两点:
1> 事务发起方一定能够将消息发送出去
2> 事务参与方一定能够成功接收到消息
如何保证事务发起方本地事务执行和发送消息的一致性?
1》本地消息表。存放消息的本地消息表和业务表放在一个库里,通过事务操作可实现一致性。但是存储和编码成本高,不可取
2》独立消息服务。消息处理部分独立部署服务,实现分布式事务需要有几个核心服务:可靠消息服务、消息确认服务、消息恢复服务、消息中间件。
3》RocketMQ事务消息。Producer端的本地事务与消息发送形成一个完整的原子操作
提供了本地事务监听接口:RocketMQLocalTransactionListener实现了两个方法:
executeLocalTransaction() // MQ回调改方法,返回事务状态
checkLocalTransaction() // 回查本地事务状态
问题:美团的MQ怎么实现的分布式事务消息?
4》如何保证消息发送的一致性?
消息发送方本地事务和消息发送整体的一致性。需要手动实现消息的发送和确认机制。
5》消息接收一致性
消息中间件向事务接收方投递消息一直失败时,根据指定规则重试,重试扔失败,存储到死信队列中,人工干预
保证消息接收的一致性需:
限制消息重复投递次数;
事务接收方接口幂等;
事务接收方与消息中间件的确认机制;
重试失败消息进入死信队列
6》 消息的可靠性
发送可靠性:发送方部署多份,集群模式;
消息存储可靠性:消息存储的多副本机制
消息消费可靠性:消费方集群模式,幂等机制