目录
[1.1 分布式事务介绍](#1.1 分布式事务介绍)
[1.2 分布式事务解决策略分析](#1.2 分布式事务解决策略分析)
[二、分布式事务解决方案 Seata](#二、分布式事务解决方案 Seata)
[2.1 认识Seata](#2.1 认识Seata)
[2.2 Seata的工作原理](#2.2 Seata的工作原理)
[2.3 部署Seata微服务](#2.3 部署Seata微服务)
[2.3.1 准备数据库表](#2.3.1 准备数据库表)
[2.3.2 准备配置文件](#2.3.2 准备配置文件)
[2.3.3 docker部署](#2.3.3 docker部署)
[2.4 微服务集成Seata](#2.4 微服务集成Seata)
[2.4.1 引入Seata依赖](#2.4.1 引入Seata依赖)
[2.4.2 添加共享配置](#2.4.2 添加共享配置)
[2.4.3 添加引导配置](#2.4.3 添加引导配置)
[2.4.4 导入数据库备份表AT](#2.4.4 导入数据库备份表AT)
[2.5 测试Seata的效果](#2.5 测试Seata的效果)
[2.5.1 修改事务注解@GlobalTransactional](#2.5.1 修改事务注解@GlobalTransactional)
[2.5.2 重启测试Seata分布式事务](#2.5.2 重启测试Seata分布式事务)
[3.1 Seata的四种策略](#3.1 Seata的四种策略)
[3.2 (拓展)XA规范](#3.2 (拓展)XA规范)
[四、XA策略模式 : 先阻塞后一并提交](#四、XA策略模式 : 先阻塞后一并提交)
[4.1 XA事务模型](#4.1 XA事务模型)
[4.2 XA执行流程](#4.2 XA执行流程)
[4.3 XA优缺点分析](#4.3 XA优缺点分析)
[4.4 XA模式实现步骤](#4.4 XA模式实现步骤)
[五、AT策略模式: 先提交备份后修复](#五、AT策略模式: 先提交备份后修复)
[4.1 AT事务模型](#4.1 AT事务模型)
[4.2 AT执行流程](#4.2 AT执行流程)
[4.3 AT模式的实现步骤](#4.3 AT模式的实现步骤)
[4.3 AT模式测试](#4.3 AT模式测试)
实验环境说明
本文有部分地方需要实验进行。首先对于看过黑马微服务的同学应该会比较熟悉。如果没有你也可以参考我实验的环境搭个简单的测试环境,用于实验。实验的目的也是为了更好的理解业务、理解知识点。
本地环境部分:
|---------------|---------------|------------|
| 服务名 | 端口号 | 备注 |
| nginx | 18080 / 18081 | 前端运行环境 |
| sentinel | 8090 | 服务保护监控 |
| hm-gateway | 8080 | 后端项目网关模块 |
| item-service | 8081 | 后端商品服务模块 |
| cart-service | 8082 | 后端购物车服务模块 |
| item-service2 | 8083 | 后端商品服务模块 |
| item-service3 | 8084 | 后端商品服务模块 |
| user-service | 8085 | 后端用户管理模块 |
| trade-service | 8086 | 后端交易服务模块 |
| pay-service | 8087 | 后端支付服务模块 |
远程服务器环境部分:
|-----------|-------------|-----------------------|
| 服务名 | 端口号 | 备注 |
| nacos | 8848 | 注册中心及配置中心,非容器镜像 |
| nginx | 18080/18081 | 线上前端运行环境,容器镜像 |
| mysql | 3306 | 线上数据库,容器镜像 |
| docker-hm | 8080 | 线上后端运行环境,容器镜像 |
| seata | 7099 | 分布式事务解决方案模块,非容器镜像 |
注意事项:
- 远程环境中非容器镜像指nacos、seata是后单独配置的容器,而mysql、nginx、docker-hm是使用compose统一部署的。
- 在本节实验中,线上环境的nginx和docker-hm我们不会使用到,而是使用本地的nginx和后端项目
- 请你确保在配置线上环境时,mysql必须比nacos 和 seata先启动。如果nacos、seata先启动将无法连接数据库。此时你需要停止nacos/seata容器,先启动mysql。
- 部署seata服务的数据库表等相关资料可以在黑马Springcloud课程中获得。或者后期有需要也会整理一份。
前言
本篇是微服务入门系列的最后一篇,先前我们逐步学习了微服务的远程调用、服务治理、请求路由、身份认证、配置管理、服务保护等知识。
微服务作为分布式的系统项目,难免会遇到分布式事务等相关问题。本篇将会从分布式事务照成的数据不一致问题出发,去探讨一个相对完善的分布式事务解决方案。
一、分布式事务问题与策略
1.1 分布式事务介绍
以购物车下单业务为例,该业务目前是否满足ACID特性呢?
下单业务,前端请求首先进入订单服务,创建订单并写入数据库。然后订单服务调用购物车服务和库存服务:
- 购物车服务负责清理购物车信息
- 库存服务负责扣减商品库存
如图所示,当前两部分请求成功后都直接提交了,可是商品库存不足导致扣减库存部分失败了。这就导致了订单创建了、购物车清空了,但是库存是不足的这种矛盾的产生。这就是数据一致性问题。
所谓的分布式事务就是为了解决这种问题。我们希望创建订单、清空购物车、扣减库存这几个动作一起成功,一起失败。在单体项目中很好解决,我们只需要添加@Transactional注解即可。不过在微服务架构中,这种事务原子性就比较复杂了。
【分布式事务实验】将商品库存改成0,然后尝试下单,查看购物车清空清空和商品库存接口的矛盾性。
1.2 分布式事务解决策略分析
那么,如何做到分布式事务一起成功/一起失败呢?
其实方法思维还是很简单的,参考单体项目的@Transactional。我们只需要判断所有的事务全部成功后再一并提交 ,这就是一种解决策略。(先阻塞后一并提交)
除此之外,还可以这么做。仍然是每个动作执行成功后提交,但是在提交之前保存一份备份。 我们只需要判断是否所有的事务执行成功,如果不是的话,那就恢复备份。这样也是一种解决策略。(先各自提交,后回滚修复)
但是,我们又如何知道分布式事务从哪里开始到哪里结束呢?
这个过程无疑是最难判断的。好在我们市面上有许多成熟的工具,可以帮助我们完成这一点。本篇之中,我们会使用seata分布式事务解决方案工具。
【总结】
|-------------------------------|
| 分布式事务方案 |
| 先阻塞等待,等完全成功后一并提交事务 |
| 先各自提交做好备份,如果有事务失败了,大家一起恢复备份状态 |
二、分布式事务解决方案 Seata
2.1 认识Seata
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
Seata 是什么? | Apache Seatahttps://seata.apache.org/zh-cn/docs/overview/what-is-seata/我们前面讲过了,分布式事务系统中,最关键的就是确定事务从哪里开始,到哪里结束。确定好这一部分,分布式事务就比较好解决了。而seata就是一个统一的事务协调者,与多个分支事务通信,检测每个分支事务的执行状态,保证全局事务下的每一个分支事务同时成功或失败。
2.2 Seata的工作原理
在Seata的事务管理中有三个重要的角色:
-
TC ( Transaction Coordinator ) - **事务协调者:**维护全局和分支事务的状态,协调全局事务提交或回滚。
-
TM (Transaction Manager) - **事务管理器:**定义全局事务的范围、开始全局事务、提交或回滚全局事务。
-
RM (Resource Manager) - **资源管理器:**管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
Seata的工作架构图
- TM 、RM: 可以理解为Seata的客户端部分,引入到参与事务的微服务依赖中即可。会协助微服务,实现本地分支事务与TC之间交互,实现事务的提交或回滚。
- **TC:**事务协调中心(SEATA),是一个独立的微服务,需要单独部署。
2.3 部署Seata微服务
2.3.1 准备数据库表
TC服务在运行过程中需要管理事务相关,像事务从哪里开始到哪里结束等。因此我们需要配置相应的数据库连接。
目前我们只需要导入TC表即可
2.3.2 准备配置文件
准备资料里有个seata目录,都要上传到服务器/root目录下
查看applictaion.yml
bash
server:
port: 7099
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
# extend:
# logstash-appender:
# destination: 127.0.0.1:4560
# kafka-appender:
# bootstrap-servers: 127.0.0.1:9092
# topic: logback_to_logstash
console:
user:
username: admin
password: admin
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: file
# nacos:
# server-addr: ca355066792d:8848
# group : "DEFAULT_GROUP"
# namespace: ""
# dataId: "seataServer.properties"
# username: "nacos"
# password: "nacos"
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
nacos:
application: seata-server
server-addr: 192.168.186.140:8848
group : "DEFAULT_GROUP"
namespace: ""
username: "nacos"
password: "nacos"
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
max-commit-retry-timeout: -1
max-rollback-retry-timeout: -1
rollback-retry-timeout-unlock-enable: false
enable-check-auth: true
enable-parallel-request-handle: true
retry-dead-threshold: 130000
xaer-nota-retry-timeout: 60000
enableParallelRequestHandle: true
recovery:
committing-retry-period: 1000
async-committing-retry-period: 1000
rollbacking-retry-period: 1000
timeout-retry-period: 1000
undo:
log-save-days: 7
log-delete-period: 86400000
session:
branch-async-queue-size: 5000 #branch async remove queue size
enable-branch-async-remove: false #enable to asynchronous remove branchSession
store:
# support: file 、 db 、 redis
mode: db
session:
mode: db
lock:
mode: db
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://mysql:3306/seata?rewriteBatchedStatements=true&serverTimezone=UTC
user: root
password: 123
min-conn: 10
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 1000
max-wait: 5000
# redis:
# mode: single
# database: 0
# min-conn: 10
# max-conn: 100
# password:
# max-total: 100
# query-limit: 1000
# single:
# host: 192.168.150.101
# port: 6379
metrics:
enabled: false
registry-type: compact
exporter-list: prometheus
exporter-prometheus-port: 9898
transport:
rpc-tc-request-timeout: 15000
enable-tc-server-batch-send-response: false
shutdown:
wait: 3
thread-factory:
boss-thread-prefix: NettyBoss
worker-thread-prefix: NettyServerNIOWorker
boss-thread-size: 1
- server.port :7099 是seata服务的运行端口
- spring.application.name :seata-server 是seata的服务名称
- seara.config 是seata的配置部分
- type: file 表示配置文件默认写在本文件下
- registry:表示将seata微服务注册到哪个注册中心
- type:nacos 本次实验选取的注册中心是nacos
- nacos:这里配置的是nacos相关参数,需要注意
- server-addr:192.168.186.140:8848 这里指定访问地址,我们等会部署到云端,可以通过容器名进行访问,抱歉这里我的容器名不是nacos,所以我直接使用IP地址进行访问
- group:"DEFAULT_GROUP" 这个分组一定要指定,和其他微服务要一致
- namespace: 命名空间,不填就是默认default
- username: nacos 登录账号
- password: nacos 登录密码
- console:
- user:配置seata控制台的登录账号密码
- username: admin
- password: admin
- store: 配置seata的存储相关
- mode: db 存储到db文件
- db: 配置数据库的连接信息参数
2.3.3 docker部署
【查看网络段信息】
注意,一定要确保seata部署的网络段要和nacos和mysql一致!不清楚的可以先查询网络段信息。
我们网络段为 : zhicong2
【创建容器】
在虚拟机的/root
目录执行下面的命令:
bash
docker run --name seata \
-p 8099:8099 \
-p 7099:7099 \
-e SEATA_IP=192.168.186.140 \
-v ./seata:/seata-server/resources \
--privileged=true \
--network zhicong2 \
-d \
seataio/seata-server:1.5.2
【查看容器】
【查看控制台】
2.4 微服务集成Seata
参与分布式事务的每一个微服务都需要集成Seata,我们以交易微服务trade-service
为例。
2.4.1 引入Seata依赖
为了方便各个微服务集成seata,我们后面会把把seata的配置注册到nacos中,所以trade-service模块不仅要引入seata依赖,也要引入nacos依赖
XML
<!--统一配置管理-->
<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.4.2 添加共享配置
在nacos上添加一个共享的seata配置,命名为shared-seata.yaml
bash
seata:
registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
type: nacos
nacos:
server-addr: 192.168.186.140:8848
namespace: "" # namespace,默认为空
group: DEFAULT_GROUP
application: seata-server
username: nacos
password: nacos
tx-service-group: hmall
service:
vgroup-mapping: # 事务组与tc集群的映射关系
hmall: "default"
2.4.3 添加引导配置
在trade-service模块的bootstrap.yaml添加seata共享配置引导:
bash
spring:
application:
name: trade-service # 服务名称
profiles:
active: dev
cloud:
nacos:
server-addr: 192.168.186.140: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配置
2.4.4 导入数据库备份表AT
seata的客户端在解决分布式事务的时候需要记录一些中间数据,保存在数据库中。因此我们要先准备一个这样的表。
将seata-at.sql分别文件导入项目模块的数据库中:
其他模块也是一样的,去试试吧!
2.5 测试Seata的效果
其实到这里Seata已经可以正常使用了。接下来就让我们实验一下如何实验Seata吧
2.5.1 修改事务注解@GlobalTransactional
找到trade-service
模块下的com.hmall.trade.service.impl.OrderServiceImpl
类中的createOrder
方法,也就是下单业务方法。将其上的@Transactional
注解改为Seata提供的@GlobalTransactional
:
@GlobalTransactional注解的作用:
这个注解是seata提供的,用于标记分布式事务的入口的注解,
将来TM就会基于这个方法判断全局事务范围,初始化全局事务。
2.5.2 重启测试Seata分布式事务
【重启项目】
【查看购物车】下单小白鞋为例
【修改购物车库存为0】
【点击下单】
【查看购物车是否删减】没有删减
【查看控制台信息】
交易微服务下单报错、商品微服务更新接口报错、购物车也不再执行删除的接口
三、Seata解决分布式事务问题的策略模式介绍
3.1 Seata的四种策略
Seata支持四种不同的分布式事务解决方案:XA、TCC、TA、SAGA
其中XA和TA正是我们开篇思考出来的两者策略,因此本篇文章会着重讲解这两者策略是如何使用的,Seata又是如何帮我们实现的。
【Seata四种模式的特点介绍】
XA模式:
- 强一致性:XA模式是四种模式中唯一支持强一致性的。在XA模式下,当本地事务提交而全局事务未提交时,直接操作数据库也无法看到本地事务提交的数据。
- 可靠性高但性能较低:由于其强一致性的保证,XA模式相对其他模式来说更加可靠,但这也导致了其性能相对较低。
TCC模式:
- 不依赖RM支持:TCC(Try-Confirm-Cancel)模式不依赖于资源管理器(RM)对分布式事务的支持,而是通过对业务逻辑的分解来实现分布式事务。
- 手动实现三个方法:在TCC模式下,所有事务都需要手动实现Try、Confirm、Cancel三个方法。这增加了业务代码的复杂性,但提供了更高的灵活性。
- 二阶段提交:TCC模式也采用二阶段提交机制,但与AT模式不同的是,TCC对业务代码的侵入性很强。
AT模式:
- 无入侵式:AT模式是一种对业务无入侵式的分布式事务解决方案。用户只需关注自己的业务SQL,Seata框架会自动生成事务的二阶段提交和回滚操作。
- 两阶段提交机制:AT模式采用两阶段提交机制。在一阶段,业务数据和回滚日志记录在同一个本地事务中提交;在二阶段,根据全局事务的状态决定是提交还是回滚。
- 快速提交与异步回滚:一阶段的提交是异步化的,非常快速;而回滚则是通过一阶段的回滚日志进行反向补偿。
SAGA模式:
- 补偿协议:Saga是一种补偿协议,在Saga模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正补偿服务。
- 正向与逆向操作:用户需要根据业务场景实现每个参与者的正向操作和逆向回滚操作。如果所有正向操作都执行成功,则分布式事务提交;如果任何一个正向操作失败,则分布式事务会执行前面各参与者的逆向回滚操作。
- 灵活性高:Saga模式提供了很高的灵活性,可以根据业务场景进行定制化的实现。
3.2 (拓展)XA规范
XA
规范 是 X/Open
组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA 规范 描述了全局的TM
与局部的RM
之间的接口,几乎所有主流的数据库都对 XA 规范 提供了支持。
简单理解,XA规范就是把事务分成了两阶段工作,以下是各个阶段进行的工作:
一阶段:
事务协调者通知每个事务参与者执行本地事务
本地事务执行完成后报告事务执行状态给事务协调者,此时事务不提交,继续持有数据库锁
二阶段:
事务协调者基于一阶段的报告来判断下一步操作
如果一阶段都成功,则通知所有事务参与者,提交事务
如果一阶段任意一个参与者失败,则通知所有事务参与者回滚事务
四、XA策略模式 : 先阻塞后一并提交
4.1 XA事务模型
Seata对原始的XA模式做了简单的封装和改造,以适应自己的事务模型,基本架构如图:
4.2 XA执行流程
RM
一阶段的工作:
-
注册分支事务到
TC
-
执行分支业务sql但不提交
-
报告执行状态到
TC
TC
二阶段的工作:
TC
检测各分支事务执行状态
-
如果都成功,通知所有RM提交事务
-
如果有失败,通知所有RM回滚事务
RM
二阶段的工作:
- 接收
TC
指令,提交或回滚事务
4.3 XA优缺点分析
-
事务的强一致性,满足ACID原则
-
常用数据库都支持,实现简单,并且没有代码侵入
-
因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
-
依赖关系型数据库实现事务
4.4 XA模式实现步骤
【调整配置文件】在nacos中,修改shared-seata.yaml配置文件,指定XA模式
seata:
data-source-proxy-mode: XA
【标记分布式事务入口 】在需要用到分布式事务的方法上,依旧是添加@GlobalTransactional注解
五、AT策略模式:先提交备份后修复
AT
模式同样是分阶段提交的事务模型,不过缺弥补了XA
模型中资源锁定周期过长的缺陷。是典型的空间换时间策略。
4.1 AT事务模型
事务模型可以看出,AT策略模式不会锁死请求,而是形成数据库备份,如果后续出现了事务异常,全部执行回滚恢复。利用备份解决了XA模式中锁定数据库资源的弊端。因此也是Seata默认的分布式事务策略。
4.2 AT执行流程
阶段一RM
的工作:
-
注册分支事务
-
记录undo-log(数据快照)
-
执行业务sql并提交
-
报告事务状态
阶段二提交时RM
的工作:
- 删除undo-log即可
阶段二回滚时RM
的工作:
- 根据undo-log恢复数据到更新前
4.3 AT模式的实现步骤
【导入备份表】先前已经导入过了
【**调整配置文件】**修改shared-seata.yaml文件,指定AT模式(其实不配置默认就是AT模式)
seata:
data-source-proxy-mode: AT
【标记分布式事务入口 】在需要用到分布式事务的方法上,依旧是添加@GlobalTransactional注解
4.3 AT模式测试
我们测试一下AT模式中生成数据库备份,并且后续删除的过程
给扣减库存方法打断点,验证数据库快照的生成
放行断点后,数据库快照删除
观察控制台
六、分布式微服务知识追问巩固
-
什么是分布式事务?分布式事务问题是如何发生的?
-
单体项目中如何解决数据不一致问题?
-
微服务如何解决数据不一致问题?你能想到有哪些方案?
-
事务管理中有哪些角色?它们分别的作用是什么?
-
你了解过Seata么?它在事务管理中充当了一个什么样的角色?
-
请你谈谈Seata的工作原理是什么?
-
分布式事务中,Seata是如何标记分布式事务的入口与出口?
-
介绍一下什么是XA事务解决策略模式?它的优缺点是什么?
-
介绍一下什么是AT事务解决策略模式?它与XA模式的不同点?