微服务系列六:分布式事务与seata

目录

实验环境说明

前言

一、分布式事务问题与策略

1.1 分布式事务介绍

1.2 分布式事务解决策略分析

二、分布式事务解决方案 Seata

2.1 认识Seata

2.2 Seata的工作原理

2.3 部署Seata微服务

2.3.1 准备数据库表

2.3.2 准备配置文件

2.3.3 docker部署

2.4 微服务集成Seata

2.4.1 引入Seata依赖

2.4.2 添加共享配置 

2.4.3 添加引导配置

2.4.4 导入数据库备份表AT

2.5 测试Seata的效果

2.5.1 修改事务注解@GlobalTransactional

2.5.2 重启测试Seata分布式事务

三、Seata解决分布式事务问题的策略模式介绍

3.1 Seata的四种策略

3.2 (拓展)XA规范

四、XA策略模式 : 先阻塞后一并提交

4.1 XA事务模型

4.2 XA执行流程

4.3 XA优缺点分析

4.4 XA模式实现步骤

五、AT策略模式: 先提交备份后修复

4.1 AT事务模型

4.2 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的工作架构图

  • TMRM:可以理解为Seata的客户端部分,引入到参与事务的微服务依赖中即可。会协助微服务,实现本地分支事务与TC之间交互,实现事务的提交或回滚。
  • TC: 事务协调中心(SEATA),是一个独立的微服务,需要单独部署。

2.3 部署Seata微服务

2.3.1 准备数据库表

TC服务在运行过程中需要管理事务相关,像事务从哪里开始到哪里结束等。因此我们需要配置相应的数据库连接。

目前我们只需要导入TC表即可

2.3.2 准备配置文件

准备资料里有个seata目录,都要上传到服务器/root目录下

查看applictaion.yml

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目录执行下面的命令:

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依赖

<!--统一配置管理-->
  <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

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共享配置引导:

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四种模式的特点介绍

  1. XA模式

    • 强一致性:XA模式是四种模式中唯一支持强一致性的。在XA模式下,当本地事务提交而全局事务未提交时,直接操作数据库也无法看到本地事务提交的数据。
    • 可靠性高但性能较低:由于其强一致性的保证,XA模式相对其他模式来说更加可靠,但这也导致了其性能相对较低。
  2. TCC模式

    • 不依赖RM支持:TCC(Try-Confirm-Cancel)模式不依赖于资源管理器(RM)对分布式事务的支持,而是通过对业务逻辑的分解来实现分布式事务。
    • 手动实现三个方法:在TCC模式下,所有事务都需要手动实现Try、Confirm、Cancel三个方法。这增加了业务代码的复杂性,但提供了更高的灵活性。
    • 二阶段提交:TCC模式也采用二阶段提交机制,但与AT模式不同的是,TCC对业务代码的侵入性很强。
  3. AT模式

    • 无入侵式:AT模式是一种对业务无入侵式的分布式事务解决方案。用户只需关注自己的业务SQL,Seata框架会自动生成事务的二阶段提交和回滚操作。
    • 两阶段提交机制:AT模式采用两阶段提交机制。在一阶段,业务数据和回滚日志记录在同一个本地事务中提交;在二阶段,根据全局事务的状态决定是提交还是回滚。
    • 快速提交与异步回滚:一阶段的提交是异步化的,非常快速;而回滚则是通过一阶段的回滚日志进行反向补偿。
  4. 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一阶段的工作:

  1. 注册分支事务到TC

  2. 执行分支业务sql但不提交

  3. 报告执行状态到TC

TC二阶段的工作:

  TC检测各分支事务执行状态

  1. 如果都成功,通知所有RM提交事务

  2. 如果有失败,通知所有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模式中生成数据库备份,并且后续删除的过程

给扣减库存方法打断点,验证数据库快照的生成

放行断点后,数据库快照删除

观察控制台

六、分布式微服务知识追问巩固

1. 什么是分布式事务?分布式事务问题是如何发生的?

2. 单体项目中如何解决数据不一致问题?

3. 微服务如何解决数据不一致问题?你能想到有哪些方案?

4. 事务管理中有哪些角色?它们分别的作用是什么?

5. 你了解过Seata么?它在事务管理中充当了一个什么样的角色?

6. 请你谈谈Seata的工作原理是什么?

7. 分布式事务中,Seata是如何标记分布式事务的入口与出口?

8. 介绍一下什么是XA事务解决策略模式?它的优缺点是什么?

9. 介绍一下什么是AT事务解决策略模式?它与XA模式的不同点?

上一篇:K8S网络插件故障处理


下一篇:【刷题17】最小栈、栈的压入弹出、逆波兰表达式-二、栈的压入弹出