1.简述
1.1.什么是分布式事务
- 事务:是应用程序中一系列严密的操作,所有操作必须成功完成,要么全部失败,ACID 特性。
- 本地事务:关系型数据库中,由一组SQL组成的一个执行单元,该单元要么整体成功,要么整体失败;它有一个缺点:仅支持单库事务,并不支持跨库事务。
- 分布式事务:指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。一个应用某个功能需要操作多个库,不同的库中存储不同的业务数据。是指一个业务需要同时操作多个数据库的情况下,而且必须保持ACID的特性。一般应用于微服务的多服务处理。
1.2.为什么需要分布式事务
一个网站的访问量越来越大,按照商品、订单、用户、店铺等业务为单位进行数据库拆分,以及按照业务为单位提供服务接口。为了完成一个简单的业务功能,比如:购买商品后扣款,有可能需要横跨多个服务,涉及用户订单、商品库存、支付等多个数据库,而这些操作又需要在同一个事务中完,这就涉及到到了分布式事务。
分布式事务就是为了保证不同资源服务器的数据一致性。
关于具体的分布式事务具体理论知识,请自行移步相关文章学习。
2.分布式事务
我们模拟一个业务流程:
由于订单、购物车、商品分别在三个不同的微服务,而每个微服务都有自己独立的数据库,因此下单过程中就会跨多个数据库完成业务。而每个微服务都会执行自己的本地事务:
-
交易服务:下单事务
-
购物车服务:清理购物车事务
-
库存服务:扣减库存事务
整个业务中,各个本地事务是有关联的。因此每个微服务的本地事务,也可以称为分支事务。多个有关联的分支事务一起就组成了全局事务。我们必须保证整个全局事务同时成功或失败。
我们知道每一个分支事务就是传统的单体事务,都可以满足ACID特性,但全局事务跨越多个服务、多个数据库,是否还能满足呢?
我们模拟一次业务操作,微服务项目中,在不使用分布式事务管理时:
- 进入购物车页面
- 然结算下单,进入订单结算页面:
- 将购物车中某个商品的库存修改为
0
- 提交订单,最终因库存不足导致下单失败
- 去查看购物车列表,发现购物车数据依然被清空了,并未回滚
事务并未遵循ACID的原则,归其原因就是参与事务的多个子业务在不同的微服务,跨越了不同的数据库。虽然每个单独的业务都能在本地遵循ACID,但是它们互相之间没有感知,不知道有人失败了,无法保证最终结果的统一,也就无法遵循ACID的事务特性了。
这就是分布式事务问题,出现以下情况之一就可能产生分布式事务问题:
-
业务跨多个服务实现
-
业务跨多个数据源实现
接下来介绍如何解决分布式事务问题。
2.1.认识Seata
解决分布式事务的方案有很多,但实现起来都比较复杂,因此我们一般会使用开源的框架来解决分布式事务问题。在众多的开源分布式事务框架中,功能最完善、使用最多的就是阿里巴巴在2019年开源的Seata了。
Seata官方网址:Seata 是什么? | Apache Seata
其实分布式事务产生的一个重要原因,就是参与事务的多个分支事务互相无感知,不知道彼此的执行状态。因此解决分布式事务的思想非常简单:
就是找一个统一的事务协调者,与多个分支事务通信,检测每个分支事务的执行状态,保证全局事务下的每一个分支事务同时成功或失败即可。大多数的分布式事务框架都是基于这个理论来实现的。
2.1.1.部署TC服务
部署详情请移步该文章:docker部署seata-CSDN博客
2.2.微服务集成Seata
参与分布式事务的每一个微服务都需要集成Seata,我们以trade-service0(订单服务)
为例。
2.2.1.引入依赖
为了方便各个微服务集成seata,我们需要把seata配置共享到nacos,因此trade-service
模块不仅仅要引入seata依赖,还要引入nacos依赖:
<!--统一配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--读取bootstrap文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
2.2.2.改造配置
首先在nacos控制台上添加一个共享的seata配置,命名为share-seata.yaml
:
内容如下:
seata:
registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
type: nacos # 注册中心类型 nacos
nacos:
server-addr: 192.168.230.128:8848 # nacos地址
namespace: "" # namespace,默认为空
group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
application: seata-server # seata服务名称
username: nacos
password: nacos
tx-service-group: hmall # 事务组名称
service:
vgroup-mapping: # 事务组与tc集群的映射关系
hmall: "default"
改造trade-service服务,在
bootstrap.yaml文件中添加内容:
spring:
application:
name: trade-service # 服务名称
profiles:
active: dev
cloud:
nacos:
server-addr: 192.168.150.101 # nacos地址
config:
file-extension: yaml # 文件后缀名
shared-configs: # 共享配置
- dataId: shared-jdbc.yaml # 共享mybatis配置
- dataId: shared-log.yaml # 共享日志配置
- dataId: shared-swagger.yaml # 共享日志配置
- dataId: shared-seata.yaml # 共享seata配置
可以看到这里加载了共享的seata配置。
在你其他需要的服务中进行此类配置,这里不在一一阐述。
2.2.3.添加数据库表
seata的客户端在解决分布式事务的时候需要记录一些中间数据,保存在数据库中。因此我们要先准备一个这样的表seata-at.sql。分别将文件导入每个微服务所在的数据库中:
表具体内容如下:
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
运行成功后你的数据库活出多一张uodo_log的表。
至此为止,微服务整合的工作就完成了。
2.2.4.业务测试
上面回滚失败的业务:
- 进入购物车页面
- 然结算下单,进入订单结算页面:
- 将购物车中某个商品的库存修改为
0
- 提交订单,最终因库存不足导致下单失败
- 去查看购物车列表,发现购物车数据依然被清空了,并未回滚
我们在订单服务中,用户点击结算提交订单的业务方法上加上@GlobalTransactional注解,
当库存再次不足时,业务会进行回滚,购物车就不会被错误的清空。
@GlobalTransactional
注解就是在标记事务的起点,将来TM就会基于这个方法判断全局事务范围,初始化全局事务。
至此,分布式事务seata框架的介绍结束!