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

目录

实验环境说明

前言

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

[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分布式事务)

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

  • TMRM: 可以理解为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四种模式的特点介绍

  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模式的不同点?

相关推荐
MonkeyKing_sunyuhua1 小时前
ubuntu22.04 docker-compose安装postgresql数据库
数据库·docker·postgresql
掘金-我是哪吒2 小时前
微服务mysql,redis,elasticsearch, kibana,cassandra,mongodb, kafka
redis·mysql·mongodb·elasticsearch·微服务
追风林2 小时前
mac m1 docker本地部署canal 监听mysql的binglog日志
java·docker·mac
不能再留遗憾了3 小时前
RabbitMQ 高级特性——消息分发
分布式·rabbitmq·ruby
€☞扫地僧☜€3 小时前
docker 拉取MySQL8.0镜像以及安装
运维·数据库·docker·容器
全能全知者4 小时前
docker快速安装与配置mongoDB
mongodb·docker·容器
材料苦逼不会梦到计算机白富美6 小时前
golang分布式缓存项目 Day 1
分布式·缓存·golang
想进大厂的小王6 小时前
项目架构介绍以及Spring cloud、redis、mq 等组件的基本认识
redis·分布式·后端·spring cloud·微服务·架构
Java 第一深情6 小时前
高性能分布式缓存Redis-数据管理与性能提升之道
redis·分布式·缓存