事务
事务的四个特性
原子性
把整个事务看作一个不可再分 原子,也就是整个事务的操作要么全部执行,要么全部不执行。一致性
事务开始前和结束后,数据库原有的物理约束和逻辑约束保持一致。比如说一个方法需要同时往两个表中插入数据,第一次插入操作成功之后,第二次插入操作失败,这时候就不符合数据库的一致性,所以这时候就需要事务来进行回滚保证一致性。隔离性
多个事务并发执行时,一个事务的执行不影响其它事务的执行。持久性
一个事务一旦提交,对数据库的修改应该永久保存在数据库中。
隔离级别
- 读未提交
- 读已提交
- 可重复读
- 串行化
MySQL 默认的隔离级别是可重复读,能有效避免脏读和不可重复读(虚读),但是有可能出现幻读
事务传播行为
事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。
spring定义了七种传播行为,spring默认是第一种事务行为。
| 事务行为 | 说明 |
|---|---|
| PROPAGATION_REQUIRED | 支持当前事务,假设当前没有事务。就新建一个事务 |
| PROPAGATION_SUPPORTS | 支持当前事务,假设当前没有事务,就以非事务方式运行 |
| PROPAGATION_MANDATORY | 支持当前事务,假设当前没有事务,就抛出异常 |
| PROPAGATION_REQUIRES_NEW | 新建事务,假设当前存在事务。把当前事务挂起 |
| PROPAGATION_NOT_SUPPORTED | 以非事务方式运行操作。假设当前存在事务,就把当前事务挂起 |
| PROPAGATION_NEVER | 以非事务方式运行,假设当前存在事务,则抛出异常 |
| PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
分布式事务
本地事务 @Transactional 只能保证一个 connection 的情况下,事务传播才有效,如果跨服务调用,不是一个 connection 的情况下,不然在遇到异常时,每个本地事务只能保证该 connection 执行的 sql 语句回滚,这时候就需要分布式事务。
CAP理论
- 一致性(Consistency)即更新操作成功并返回客户端后,所有节点在同一时间的数据完全一致
- 可用性(Availability)即服务一直可用,而且是正常响应时间,最新的数据,任何时候任何应用程序都能读取写入数据
- 分区容错性(Partition tolerance)系统部分节点出现故障后,连接正常节点还可以使用系统提供的服务
分布式系统最多满足其中的两个特性,无法同时保持 CAP,分布式系统P是前提,必须保证,如果保证数据库 1 和数据库 2 的一致性,就必须同时锁定数据库 2 的读写操作,无法保证可用性
BASE理论
-
基本可用(Basically Available)是指分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用
-
软状态(Soft State)是状态可以有一段时间不同步,存在异步的情况,而该状态不会影响系统整体可用性。
-
最终一致性(Eventually Consistent)是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态
既是无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)
柔性事务和刚性事务
柔性事务满足 BASE 理论(基本可用,最终一致),刚性事务满足 ACID 理论
柔性事务分为
1)两阶段型
2)补偿型
3)异步确保型
4)最大努力通知型
分布式一致性协议
2PC
第一阶段:准备阶段(投票阶段)
第二阶段:提交阶段(执行阶段)
阶段1-协调者发送一个提议,分别询问各参与者是否接受,这时候参与者开始执行事务操作,返回执行成功与否。
阶段2-协调者根据参与者的反馈,提交或终止事务,提交事务成功,返回确认消息,协调方根据确认消息,决定本次事务是否成功。
2PC容易出现的问题
-
协调者正常,参与者宕机,会陷入阻塞状态,解决方法:引入超时机制
-
协调者宕机,参与者正常,特别在第二阶段,协调者无法向参与者发送提交请求,所有参与者都属于操作但是未提交的状态,会陷入阻塞情况
3PC
是 2PC 的改进版本,一是引入超时机制,二是在第一阶段和第二阶段中间插入准备阶段
那么引入了超时机制,参与者就不会傻等了,如果是等待提交命令超时,那么参与者就会提交事务了,因为都到了这一阶段了大概率是提交的,如果是等待预提交命令超时,那该干啥就干啥了,反正本来啥也没干。****
所以说 3PC 就是通过引入预提交阶段来使得参与者之间的状态得到统一,也就是留了一个阶段让大家同步一下。
但是超时机制可能造成数据不一致
1、CanCommit阶段
分为两步:
- 事务询问:协调者向参与者发送CanCommit请求,寻问是否可以执行事务提交操作,然后开始等待参与者的响应
- 响应反馈: 参与者接收到CanCommit请求后,根据自身情况返回Yes或No
2.PreCommit阶段
就跟2PC的第一阶段差不多,执行操作,但是不提交事务,然后返回信息给协调者,协调者和参与者都引入了超时机制
3.DoCommit阶段
和2PC的阶段二差不多
TX-LCN使用
1. 使用Tx-Manager、单独的项目
1.1 依赖jar
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tm</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
1.2 配置文件properties
spring.application.name=TransactionManager
server.port=7970
# JDBC 数据库配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/user?characterEncoding=UTF-8&serverTimeZone=UTC
spring.datasource.username=root
spring.datasource.password=654321
# 数据库方言
spring.jpa.database-platform=org.hibernate.dialect.MySQL57Dialect
# 为TM创建持久化数据库表
spring.jpa.hibernate.ddl-auto=update
# TM监听Socket端口. 默认为 ${server.port} - 100
tx-lcn.manager.port=8070
# TM后台登陆密码,默认值为codingapi
tx-lcn.manager.admin-key=qfjava
# 雪花算法的sequence位长度,默认为12位.
tx-lcn.manager.seq-len=12
# 异常回调开关。开启时请制定ex-url
tx-lcn.manager.ex-url-enabled=false
# 开启日志,默认为false
tx-lcn.logger.enabled=true
tx-lcn.logger.driver-class-name=${spring.datasource.driver-class-name}
tx-lcn.logger.jdbc-url=${spring.datasource.url}
tx-lcn.logger.username=${spring.datasource.username}
tx-lcn.logger.password=${spring.datasource.password}
# redis 的设置信息. 线上请用Redis Cluster
spring.redis.host=118.31.224.147
spring.redis.port=6379
spring.redis.password=154050
1.3 开关类上使用注解:
@EnableTransactionManagerServer //启用Tx-Manager 事务管理器
1.4 创建数据库
CREATE TABLE `t_tx_exception` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`group_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`unit_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`mod_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`transaction_state` tinyint(4) NULL DEFAULT NULL,
`registrar` tinyint(4) NULL DEFAULT NULL,
`remark` varchar(4096) NULL DEFAULT NULL,
`ex_state` tinyint(4) NULL DEFAULT NULL COMMENT '0 未解决 1已解决',
`create_time` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
1.5 运行访问
http://localhost:8801/admin/index.html
2.应用分布式事务:Tx-LCN在服务中使用
2.1 依赖jar
<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>
2.2 开关类添加注解
各个服务的开关上使用注解 @EnableDistributedTransaction 开启分布式事务
2.3 在要进行分布式事务的方法上使用以下注解,要带上本地注解@Transaction,使用的 注解要固定的一个,否则参数为null
@LcnTransaction //lcn模式
LCN模式是通过代理Connection的方式实现对本地事务的操作,然后在由TxManager统一协调控制事务。当本地 事务提交回滚或者关闭连接时将会执行假操作,该代理的连接将由LCN连接池管理。
@TxcTransaction //txc模式
TXC模式命名来源于淘宝,实现原理是在执行SQL之前,先查询SQL的影响数据,然后保存执行的SQL快走信息和创建 锁。当需要回滚的时候就采用这些记录数据回滚数据库,目前锁实现依赖redis分布式锁控制。
@TccTransaction //tcc模式
TCC事务机制相对于传统事务机制(X/Open XA Two-Phase-Commit),其特征在于它不依赖资源管理器(RM)对 XA的支持,而是通过对(由业务系统提供的)业务逻辑的调度来实现分布式事务。主要由三步操作,Try: 尝试执行 业务、 Confirm:确认执行业务、 Cancel: 取消执行业务。
例子
@LcnTransaction //lcn模式
// @TccTransaction //txc模式
// @TxcTransaction //tcc模式
@Transactional(rollbackFor = Exception.class)
public void insert(Test1 test1) {
try {
// 开启事务
testDao.insert(test1);
serviceTest.insert(test1);
System.out.println(1 / 0);
// 提交事务
} catch (Exception e) {
System.out.println("出现了异常");
DTXUserControls.rollbackCurrentGroup();
}
}
2.4 修改配置文件
添加tx-lcn管理器的地址
tx-lcn:
client:
manager-address: localhost:8070
分布式事务tx-lcn下lcn模式时spring手动事务回滚失效
1.改用 @TccTransaction来替换lcn模式
2.使用DTXUserControls.rollbackCurrentGroup();来替换TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();