微服务-seata分布式事务

1.简述

1.1.什么是分布式事务

  • 事务:是应用程序中一系列严密的操作,所有操作必须成功完成,要么全部失败,ACID 特性。
  • 本地事务:关系型数据库中,由一组SQL组成的一个执行单元,该单元要么整体成功,要么整体失败;它有一个缺点:仅支持单库事务,并不支持跨库事务。
  • 分布式事务:指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。一个应用某个功能需要操作多个库,不同的库中存储不同的业务数据。是指一个业务需要同时操作多个数据库的情况下,而且必须保持ACID的特性。一般应用于微服务的多服务处理。

1.2.为什么需要分布式事务

一个网站的访问量越来越大,按照商品、订单、用户、店铺等业务为单位进行数据库拆分,以及按照业务为单位提供服务接口。为了完成一个简单的业务功能,比如:购买商品后扣款,有可能需要横跨多个服务,涉及用户订单、商品库存、支付等多个数据库,而这些操作又需要在同一个事务中完,这就涉及到到了分布式事务。

分布式事务就是为了保证不同资源服务器的数据一致性。

关于具体的分布式事务具体理论知识,请自行移步相关文章学习。

2.分布式事务

我们模拟一个业务流程:

由于订单、购物车、商品分别在三个不同的微服务,而每个微服务都有自己独立的数据库,因此下单过程中就会跨多个数据库完成业务。而每个微服务都会执行自己的本地事务:

  • 交易服务:下单事务

  • 购物车服务:清理购物车事务

  • 库存服务:扣减库存事务

整个业务中,各个本地事务是有关联的。因此每个微服务的本地事务,也可以称为分支事务 。多个有关联的分支事务一起就组成了全局事务。我们必须保证整个全局事务同时成功或失败。

我们知道每一个分支事务就是传统的单体事务,都可以满足ACID特性,但全局事务跨越多个服务、多个数据库,是否还能满足呢?

我们模拟一次业务操作,微服务项目中,在不使用分布式事务管理时:

  1. 进入购物车页面
  2. 然结算下单,进入订单结算页面:
  3. 将购物车中某个商品的库存修改为0
  4. 提交订单,最终因库存不足导致下单失败
  5. 去查看购物车列表,发现购物车数据依然被清空了,并未回滚

事务并未遵循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依赖:

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.2.2.改造配置

首先在nacos控制台上添加一个共享的seata配置,命名为share-seata.yaml

内容如下:

XML 复制代码
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文件中添加内容:

XML 复制代码
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。分别将文件导入每个微服务所在的数据库中:

表具体内容如下:

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.业务测试

上面回滚失败的业务:

  1. 进入购物车页面
  2. 然结算下单,进入订单结算页面:
  3. 将购物车中某个商品的库存修改为0
  4. 提交订单,最终因库存不足导致下单失败
  5. 去查看购物车列表,发现购物车数据依然被清空了,并未回滚

我们在订单服务中,用户点击结算提交订单的业务方法上加上**@GlobalTransactional注解,**

当库存再次不足时,业务会进行回滚,购物车就不会被错误的清空。

@GlobalTransactional注解就是在标记事务的起点,将来TM就会基于这个方法判断全局事务范围,初始化全局事务。

至此,分布式事务seata框架的介绍结束!

相关推荐
ac-er88881 小时前
如何在Go语言开发中实现高性能的分布式日志收集
开发语言·分布式·golang
前端掘金者H1 小时前
Vue 项目接入Google第三方登录的详细流程🚀🚀
前端·google·架构
HsuYang5 小时前
Vite源码学习(六)——DEV流程探究起步
前端·javascript·架构
40岁的系统架构师5 小时前
7 分布式定时任务调度框架
分布式
guihong0046 小时前
深入解析 ZooKeeper:分布式协调服务的原理与应用
分布式·zookeeper·云原生
guihong0046 小时前
深入探秘 ZooKeeper:架构、设计、角色与 ZNode 全解析 前言
分布式·zookeeper·架构
wangbing11256 小时前
开发指南090-使用python做微服务
微服务·云原生·架构
dengjiayue6 小时前
单体 vs 微服务 怎么选?
微服务·云原生·架构
binqian7 小时前
【harbor】离线安装2.9.0-arm64架构服务制作和升级部署
运维·架构