当前位置:   article > 正文

Day124.分布式事务:Seata、2PC两段式、代码补偿TCC、本地消息表、MQ事物消息_seata 类似

seata 类似

目录

一、相关概念回顾

二、分布式事务

三、分布式事务解决方案

1、基于XA协议的两段式提交(2PC)  - 强一致性

2、代码补偿事务(TCC) - 最终一致性

3、本地消息表(异步确保)- 最终一致性

4、MQ 事务消息

5、Seata介绍

全局锁、本地锁,二段式提交

四、Seata案例初始化 

五、Seata 原理


一、相关概念回顾

什么是事物:提供一种“要么什么都不做,要么做全套(All or Nothing)”机制。保证数据一致性

  • 事务的四大属性?(ACID属性) ★

(1)原子性(Automicity)
事务中的操作要么都发生,要么都不发生。 

关系型事物才有,非关系型数据库缺点:没有强一致性

(2)一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态。

(3)隔离性(Isolation)
事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

事物间相互不影响

(4)持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响
 

  • 事物(并发)操作中的问题?(针对隔离性) ★
  1. 脏读:一个事务读取了另一个事务未提交数据;

  2. 不可重复读:同一个事务中前后两次读取同一条记录不一样。因为被其他事务修改了并且提交了。(同一事务中查询的数据应保持一致性)

  3. 幻读:一个事务读取了另一个事务新增、删除的记录情况,记录数不一样,像是出现幻觉。

  • 事物的四种隔离级别是?(一致性和并发性:一致性越好,并发性越差) ★

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) 两段式提交的优化

1、基于XA协议的两段式提交(2PC)  - 强一致性

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接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。

2、代码补偿事务(TCC) - 最终一致性

XA是资源层面的分布式事务,强一致性。TCC通过代码补偿的方式保证最终一致性。三段式事物,本质还是两段式。

具体流程:(飞机票)

TCC是Try ( 尝试 ) — Confirm(确认) — Cancel ( 取消 ) 的简称: 

操作方法含义
Try完成所有业务检查(一致性),预留业务资源(准隔离性) 回顾上面航班预定案例的阶段1,机票就是业务资源,所有的资源提供者(航空公司)预留都成功,try阶段才算成功
Confirm确认执行业务操作,不做任何业务检查, 只使用Try阶段预留的业务资源。回顾上面航班预定案例的阶段2,美团APP确认两个航空公司机票都预留成功,因此向两个航空公司分别发送确认购买的请求。
Cancel取消Try阶段预留的业务资源。回顾上面航班预定

以航班预定的案例,从合肥 –> 昆明 –> 大理。

3、本地消息表(异步确保)- 最终一致性

  • 订单系统新增一条消息表,将新增订单和新增消息放到一个事务里完成,然后通过轮询的方式去查询消息表,将消息推送到 MQ,库存系统去消费 MQ。

  • 优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。
  • 缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。

4、MQ 事务消息

有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持

RocketMQ提供了类似X/Open XA的分布事务功能,通过MQ的事务消息能达到分布式事务的最终一致。

半消息

总体而言RocketMQ事务消息分为两条主线

定时任务发送流程:发送half message(半消息),执行本地事务,发送事务执行结果

定时任务回查流程:MQ服务器回查本地事务,发送事务执行结果

半消息的实现,在RabbitMQ的基础上增加了功能

优点 实现了最终一致性,不需要依赖本地数据库事务。

缺点 目前主流MQ中只有RocketMQ支持事务消息(需要买阿里的服务)。

5、Seata介绍

Seata 阿里开源的一个分布式事务框架,能够让大家在操作分布式事务时,像操作本地事务一样简单。一个注解搞定分布式事务。

  • 解决分布式事务问题,有两个设计初衷
    • 对业务无侵入:即减少技术架构上的微服务化所带来的分布式事务问题对业务的侵入
    • 高性能:减少分布式事务解决方案所带来的性能消耗
  • Seata中有两种分布式事务实现方案,AT 及 TCC
    • AT模式主要关注多 DB 访问的数据一致性,当然也包括多服务下的多 DB 数据访问一致性问题 2PC-改进
    • TCC 模式主要关注业务拆分,在按照业务横向扩展资源时,解决微服务间调用的一致性问题

官网: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案例初始化 

想用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服务器

  1. 下载地址:Seata 
  2. 解压seata-server-1.4.2.zip安装,修改D:\Server\seata-server-1.4.2\conf\registry.conf设置TC 服务对应的注册中心和配置中心。 这里选择Nacos,
  3. 注意:dataId = "seataServer.properties",从nacos配置中心拉取配置。

  1. registry {
  2. # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  3. type = "nacos"
  4. nacos {
  5. application = "seata-server"
  6. serverAddr = "127.0.0.1:8848"
  7. group = "SEATA_GROUP"
  8. namespace = ""
  9. cluster = "default"
  10. username = "nacos"
  11. password = "nacos"
  12. }
  13. }
  14. config {
  15. # file、nacos 、apollo、zk、consul、etcd3
  16. type = "nacos"
  17. nacos {
  18. serverAddr = "127.0.0.1:8848"
  19. namespace = ""
  20. group = "SEATA_GROUP"
  21. username = "nacos"
  22. password = "nacos"
  23. dataId = "seataServer.properties"
  24. }
  25. }
  • 进入到D:\Server\seata-server-1.4.2\bin目录运行seata-server.bat

7、引入seata依赖

在fescar-api项目中引入依赖,排除低版本依赖,重新引入1.4.2;传递给其他微服务项目使用。

  1. <!--Seata依赖-->
  2. <dependency>
  3. <groupId>com.alibaba.cloud</groupId>
  4. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  5. <exclusions>
  6. <exclusion>
  7. <groupId>io.seata</groupId>
  8. <artifactId>seata-spring-boot-starter</artifactId>
  9. </exclusion>
  10. </exclusions>
  11. </dependency>
  12. <dependency>
  13. <groupId>io.seata</groupId>
  14. <artifactId>seata-spring-boot-starter</artifactId>
  15. <version>1.4.2</version>
  16. </dependency>

8、fescar-api 工程下面新建配置类 

  1. @Configuration
  2. public class DataSourceProxyConfig {
  3. /**
  4. * 普通数据源
  5. * @return
  6. */
  7. @Bean
  8. @ConfigurationProperties(prefix = "spring.datasource")
  9. public DataSource dataSource() {
  10. return new DruidDataSource();
  11. }
  12. /**
  13. * 代理数据源绑定DataSourceProxy ---> undo_log的操作
  14. * @param dataSource
  15. * @return
  16. */
  17. @Bean
  18. public DataSourceProxy dataSourceProxy(DataSource dataSource) {
  19. return new DataSourceProxy(dataSource);
  20. }
  21. /**
  22. * mybatis--->手动指定sqlSessionFactory所使用的代理数据源
  23. * @param dataSourceProxy
  24. * @return
  25. * @throws Exception
  26. */
  27. @Bean
  28. public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
  29. SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
  30. // 换成代理数据源
  31. sqlSessionFactoryBean.setDataSource(dataSourceProxy);
  32. return sqlSessionFactoryBean.getObject();
  33. }
  34. }

9、入口方法上添加@GlobalTransactional开启全局事务

测试:http://localhost:18081/business/addorder

记录了log日志,事务完成了回滚

五、Seata 原理

再看TC/TM/RM三大组件

  • TM开启分布式事务(TM向TC注册全局事务记录)
  • 换业务场景,编排数据库,服务等事务内资源(RM向TC汇报资源准备状态)
  • TM结束分布式事务,事务一阶段结束(TM通知TC提交/回滚分布式事务)
  • TC汇总事务信息,决定分布式事务是提交还是回滚
  • TC通知所有RM提交/回滚资源,事务二阶段结束。

第一阶段加载

  • 在一阶段,Seata会拦截"业务SQL"
    • 1.解析SQL语义,找到“业务SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image
    • 2.执行"业务SQL"更新业务数据,在业务数据更新之后,
    • 3.其保存成“after image”,最后生成行锁。
  • 以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。

二阶段提交

  • 二阶段如果顺利提交的话,因为“业务SQL”在一阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

二阶段回滚

二阶段如果是回滚的话,Seata就需要回滚一阶段执行的“业务SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先校验脏写,对比“数据库当前业务数据”和“after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/678027
推荐阅读
相关标签
  

闽ICP备14008679号