目录
一、相关概念回顾
二、分布式事务
三、分布式事务解决方案
1、基于XA协议的两段式提交(2PC) - 强一致性
2、代码补偿事务(TCC) - 最终一致性
3、本地消息表(异步确保)- 最终一致性
4、MQ 事务消息
5、Seata介绍
全局锁、本地锁,二段式提交
四、Seata案例初始化
五、Seata 原理
什么是事物:提供一种“要么什么都不做,要么做全套(All or Nothing)”机制。保证数据一致性
(1)原子性(Automicity)
事务中的操作要么都发生,要么都不发生。关系型事物才有,非关系型数据库缺点:没有强一致性
(2)一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态。(3)隔离性(Isolation)
事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。事物间相互不影响
(4)持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响
脏读:一个事务读取了另一个事务未提交数据;
不可重复读:同一个事务中前后两次读取同一条记录不一样。因为被其他事务修改了并且提交了。(同一事务中查询的数据应保持一致性)
幻读:一个事务读取了另一个事务新增、删除的记录情况,记录数不一样,像是出现幻觉。
mysql默认的事务隔离级别为repeatable-read,mysql中使用了MVCC多版本控制技术,在这个级别也可以避免幻读。
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read-uncommitted) | √ | √ | √ |
读已提交(read-committed) | × | √ | √ |
可重复读(repeatable-read) | × | × | √ |
串行化(serializable) | × | × | × |
名称 | 含义 |
---|---|
REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是默认值。 |
REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
NESTED | 如当前存在事务,则在嵌套事务内执行。如当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作 |
本地事务也称为 数据库事务 或 传统事务 (相对于分布式事务而言)。
特征:只针对本地事务,一个数据库。事务的执行结果保证ACID。会用到数据库锁。
单体应用的数据一致性由本地事务来保证。而当单体应用被拆分为微服务,每个独立的服务分别使用独立的数据源:每一个服务内部的数据一致性仍由本地事务来保证,整个业务层面的全局数据一致性要如何保障呢?
当内部无法解决。引入外部协调者,解决内部事物 (全局事务ID?)
本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
分布式系统:部署在不同节点上的系统通过网络交互来完成协同工作的系统。
分布式事务应用在哪些场景
- 电商系统中的下单扣库存
电商系统中,订单系统和库存系统是两个系统,一次下单的操作由两个系统协同完成
- 金融系统中的银行卡充值
在金融系统中通过银行卡向平台充值需要通过银行系统和金融系统协同完成。
- 教育系统中下单选课业务
在线教育系统中,用户购买课程,下单支付成功后学生选课成功,此事务由订单系统和选课系统协同完成。
- SNS系统的消息发送
在社交系统中发送站内消息同时发送手机短信,一次消息发送由站内消息系统和手机通信系统协同完成。
跨多服务多数据库的分布式事务
1.XA两段提交(低效率)-分布式事务解决方案
2.TCC三段提交(2段,高效率[不推荐(补偿代码)])
3.本地消息(MQ+Table) (电商选择)
4.事务消息(RocketMQ[alibaba])
5.Seata(alibaba) 两段式提交的优化
X/Open 组织(即现在的 Open Group )定义了分布式事务处理模型。
当一个事务跨多个节点时,为了保持事务的原子性与一致性,需要引入一个 协调者(Coordinator)来统一掌控所有 参与者(Participant) 的操作结果,并指示它们是否要把操作结果进行真正的提交(commit)或者回滚(rollback)
二阶段提交2PC(Two phase Commit)是指,在分布式系统里,为了保证所有节点在进行事务提交时保持一致性的一种算法。
2PC顾名思义分为两个阶段,其实施思路可概括为:
(1)投票阶段(voting phase):参与者将操作结果通知协调者 (举例开会发邮件,需要回复)
(2)提交阶段(commit phase):收到参与者的通知后,协调者再向参与者发出通知,根据反馈情况决定各参与者是否要提交还是回滚;
缺陷:一个模块出现问题(没有反馈),所有模块都会阻塞。2PC遵循强一致性,效率很低。
XA协议:XA是一个分布式事务协议。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2(IBM)这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。
XA是资源层面的分布式事务,强一致性。TCC通过代码补偿的方式,保证最终一致性。三段式事物,本质还是两段式。
具体流程:(飞机票)
TCC是Try ( 尝试 ) — Confirm(确认) — Cancel ( 取消 ) 的简称:
操作方法 | 含义 |
---|---|
Try | 完成所有业务检查(一致性),预留业务资源(准隔离性) 回顾上面航班预定案例的阶段1,机票就是业务资源,所有的资源提供者(航空公司)预留都成功,try阶段才算成功 |
Confirm | 确认执行业务操作,不做任何业务检查, 只使用Try阶段预留的业务资源。回顾上面航班预定案例的阶段2,美团APP确认两个航空公司机票都预留成功,因此向两个航空公司分别发送确认购买的请求。 |
Cancel | 取消Try阶段预留的业务资源。回顾上面航班预定 |
以航班预定的案例,从合肥 –> 昆明 –> 大理。
有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。
RocketMQ提供了类似X/Open XA的分布事务功能,通过MQ的事务消息能达到分布式事务的最终一致。
半消息
总体而言RocketMQ事务消息分为两条主线
定时任务发送流程:发送half message(半消息),执行本地事务,发送事务执行结果
定时任务回查流程:MQ服务器回查本地事务,发送事务执行结果
优点: 实现了最终一致性,不需要依赖本地数据库事务。
缺点: 目前主流MQ中只有RocketMQ支持事务消息(需要买阿里的服务)。
Seata 是阿里开源的一个分布式事务框架,能够让大家在操作分布式事务时,像操作本地事务一样简单。一个注解搞定分布式事务。
官网:Seata
AT模式(Automatic (Branch) Transaction Mode)
- Transaction Coordinator (TC):事务协调器,维护全局事务的运行状态,负责协调并决定全局事务的提交或回滚。(Seata)
- Transaction Manager(TM): 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。(全局入口)
- Resource Manager (RM):资源管理器,负责本地事务的注册,本地事务状态的汇报(投票),并且负责本地事务的提交和回滚。(分布式服务)
- XID:一个全局事务的唯一标识
其中,TM是一个分布式事务的发起者和终结者,TC负责维护分布式事务的运行状态,而RM则负责本地事务的运行。
①整体机制
两阶段提交协议的演变:
②写隔离
两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。
tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。
③读隔离
在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted)。
如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE(排他锁) 语句的代理 读取数据时,不允许其他线程对此条数据进行修改。
想用Seata,分布式事务中所有模块所对应的的数据库,都应该有一张UNDO_LOG表。用于存储相关操作,用于补偿式回滚。
版本声明:
nacos-server-1.4.2 + seata-server-1.4.2 + MySQL5.7 + Hoxton.SR9 + Alibaba2.2.6.RELEASE + SpringBoot2.3.2.RELEASE
1、复制项目,下载依赖,修改配置mysql地址
2、创建表
3、启动nacos -1.4.2 startup.cmd -m standalone
修改nacos mysql持久化
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&serverTimezone=Asia/Shanghai
db.user=root
db.password=******
测试,出现分布式事务问题。
用户模块报错。订单产生,库存减少,但是金钱没有减少。
4、在配置中心增加配置:seataServer.properties、common.yml,详情参考课件(注意分组)
创建 seata142 数据库,导入表:global_table、branch_table、lock_table,参考脚本
5、新建dataId为seataServer.properties的配置,内容如下:
6、搭建TC服务器
注意:dataId = "seataServer.properties",从nacos配置中心拉取配置。
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
dataId = "seataServer.properties"
}
}
7、引入seata依赖
在fescar-api项目中引入依赖,排除低版本依赖,重新引入1.4.2;传递给其他微服务项目使用。
com.alibaba.cloud
spring-cloud-starter-alibaba-seata
io.seata
seata-spring-boot-starter
io.seata
seata-spring-boot-starter
1.4.2
8、fescar-api 工程下面新建配置类
@Configuration
public class DataSourceProxyConfig {
/**
* 普通数据源
* @return
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return new DruidDataSource();
}
/**
* 代理数据源绑定DataSourceProxy ---> undo_log的操作
* @param dataSource
* @return
*/
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
/**
* mybatis--->手动指定sqlSessionFactory所使用的代理数据源
* @param dataSourceProxy
* @return
* @throws Exception
*/
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
// 换成代理数据源
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
return sqlSessionFactoryBean.getObject();
}
}
9、入口方法上添加@GlobalTransactional开启全局事务
测试:http://localhost:18081/business/addorder
记录了log日志,事务完成了回滚
再看TC/TM/RM三大组件
第一阶段加载
二阶段提交
二阶段回滚
二阶段如果是回滚的话,Seata就需要回滚一阶段执行的“业务SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先校验脏写,对比“数据库当前业务数据”和“after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。