[Java]微服务之分布式事务
介绍
下单业务,前端请求首先进入订单服务,创建订单并写入数据库。然后订单服务调用购物车服务和库存服务:
- 购物车服务负责清理购物车信息
- 库存服务负责扣减商品库存
问题分析:
- 下单过程中, 订单服务创建订单, 插入自己的数据库, 执行成功
- 购物车服务, 清理数据库中用户的购物车数据, 执行成功
- 库存服务扣减库存时, 商品库存不够, 那么库存服务就会抛出异常
- 订单创建成功了, 购物车页清理了, 但是库存却没有扣减
- 这就出现了事务的不一致
在分布式系统中,如果一个业务需要多个服务合作完成,而且每一个服务都有事务,多个事务必须同时成功或失败,这样的事务就是分布式事务。
- 其中的每个服务的事务就是一个分支事务。
- 整个业务称为全局事务。
认识Seata
Seata是 2019年1月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。致力于提供高性能和简单易用的分布式R事务服务,为用户打造一站式的分布式解决方案。
官网地址:Apache Seata,其中的文档、播客中提供了大量的使用说明、源码分析。
分布式事务总体解决思路
解决分布式事务,各个子事务之间必须能感知到彼此的事务状态,才能保证状态一致。
- 需要引入事务协调者(全局事务)进行事务控制,
- 所有的子事务都要向全局事务汇报状态
- 如果子事务全部成功, 全局事务告知子事务, 提交数据库操作
- 如果存在失败的子事务, 全局事务告知子事务, 回滚数据库操作
Seata解决分布式事务的思考会更加完善
Seata事务管理中有三个重要的角色:
- TC(Transaction Coordinator)-事务协调者: 维护全局和分支事务的状态,协调全局事务提交或回滚
- TM (Transaction Manager)-事务管理器: 定义全局事务的范围、开始全局事务、提交或回滚全局事务
- RM(Resource Manager)-资源管理器: 管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态
部署TC服务
Seata支持多种存储模式,但考虑到持久化的需要,我们一般选择基于数据库存储。
执行课前资料提供的《seata-tc.sql》
,导入数据库表:
课前资料准备了一个seata目录,其中包含了seata运行时需要的所有配置文件:
- 其中yaml文件中的配置是我们需要关心的, 代码中有注释, 可以阅读, 需要时可以修改
- 我们将整个seata文件夹拷贝到虚拟机的
/root
目录:
Docker部署
- 服务之间都是采用容器名连接, 所欲要确保nacos、mysql都在hm-net网络中(同一个网络)。
- 查看网络列表, 看网络是否存在
- 查看容器所在的网络
- 如果某个容器不再hm-net网络,可以参考下面的命令将某容器加入指定网络:
docker network connect [网络名] [容器名]
- 在虚拟机的
/root
目录执行下面的命令:
docker run --name seata \
-p 8099:8099 \
-p 7099:7099 \
-e SEATA_IP=192.168.1.97 \
-v ./seata:/seata-server/resources \
--privileged=true \
--network hm-net \
-d \
seataio/seata-server:1.5.2
4.1 参数说明
- --name: 指定容器名
- -p 8099:8099: 微服务跟seata服务连接时的端口
- -p 7099:7099: seata控制台访问端口
- -e SEATA_IP: seata的IP地址, 要改成自己的
- -v ./seata: 数据卷挂载, 让配置文件生效
- --privileged=true: 本地文件授权
- --network: 容器的网络
- -d: 容器后台运行
- seataio/seata-server:1.5.2: sreata镜像包和版本
4.2 执行说明
- 如果镜像下载困难,也可以把课前资料提供的镜像上传到虚拟机并加载
- 查看服务
http://192.168.1.97:8848/nacos
http://192.168.1.97:7099
账密: admin/admin
集成Seate
参与分布式事务的每一个微服务都需要集成Seata,我们以trade-service
为例。
- 引入依赖
为了方便各个微服务集成seata,我们需要把seata配置共享到nacos,因此不仅仅要引入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>
提示: cart-service服务/item-service服务/trade-service服务都需要改造
- 改造配置
首先在nacos上添加一个共享的seata配置,命名为shared-seata.yaml
:
- 配置项的说明
seata:
registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
type: nacos # 注册中心类型 nacos
nacos:
server-addr: 192.168.1.97: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"
- 添加共享配置文件
bootstrap.yaml
spring:
application:
name: trade-service # 服务名称
profiles:
active: dev
cloud:
nacos:
server-addr: 192.168.1.97:8848 # 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配置
- 改造SpringMvc配置文件
server:
port: 8085 #每个微服务运行在不同端口
hm:
swagger:
title: "交易服务接口文档"
package: com.hmall.trade.controller
db:
database: hm-trade
- 重启服务
JDK非11版本启动服务配置中加虚拟机选型, 不然服务启动报错
XA模式
XA规范 是X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA规范 描述了全局的TM与局部的RM之间的接口,几乎所有主流的关系型数据库都对XA规范 提供了支持。
seata的XA模式如下:
一阶段的工作:
- RM注册分支事务到TC
- RM执行分支业务sql但不提交
- RM报告执行状态到TC
二阶段的工作:
- TC检测各分支事务执行状态
-
- a. 如果都成功,通知所有RM提交事务
- b. 如果有失败,通知所有RM回滚事务
- RM接收TC指令,提交或回滚事务
XA模式的优点是什么?
- 事务的强一致性,满足ACID原则。
- 常用数据库都支持,实现简单,并且没有代码侵入
XA模式的缺点是什么?
- 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
- 依赖关系型数据库实现事务
Seata的starter已经完成了XA模式的自动装配,实现非常简单
- 修改application.yml文件(每个参与事务的微服务),开启XA模式:
seata:
data-source-proxy-mode: XA
- 给发起全局事务的入口方法添加@GlobalTransactional注解
- 其他的分支事务, 因为有可能要订单回滚, 所以加一下事务注解
- 重启服务并测试: 提交订单, 看下单失败后, 购物车的数据能否回滚
- 二阶段分支执行力回滚
AT模式
Seata主推的是AT模式,AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模型中资源锁定周期过长的缺陷
- 阶段一RM的工作:
- 注册分支事务
- 记录undo-log(数据快照)
- 执行业务sql并提交
- 报告事务状态
- 阶段二提交时RM的工作:
- 删除undo-log即可
- 阶段二回滚时RM的工作:
- 根据undo-log恢复数据到更新前
简述AT模式与XA模式最大的区别是什么?
- XA模式一阶段不提交事务,锁定资源;
- AT模式一阶段直接提交,不锁定资源。
- XA模式依赖数据库机制实现回滚;
- AT模式利用数据快照实现数据回滚。
- XA模式强一致;AT模式最终一致
实现AT模式
- 首先,添加资料中的seata-at.sql到微服务对应的数据库中
- 然后,修改application.yml文件,将事务模式修改为AT模式:
- 重新测试
- 选择购物车的商品
- 进行结算
- 把商品数量改成0
- 提交订单失败
- 购物车数据回滚