分布式事务解决方案——Seata

发布时间 2023-08-25 10:55:48作者: shine-rainbow

分布式事务解决方案——Seata

Seata是什么?

Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

Seata三大角色

在Seata架构中,共涉及到三个角色:

  • TC(Transaction Conrdinator)-事务协调者

​ 维护全局和分支事务的状态,驱动全局事务提交或回滚。

  • TM(Transaction Manager)- 事务管理器

​ 定义全局事务的范围:开始全局事务,提交或回滚全局事务

  • RM(Resource Manager)- 资源管理器

​ 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

其中TC为单独部署的Server服务端,TM和RM为嵌入到应用的Client客户端。

AT模式

前提

  • 基于本地ACID事务的关系型数据库
  • Java应用,通过JDBC访问数据库

AT模式是一种无侵入的分布式事务解决方案,该模式下,用户只需关注自己的业务SQL,用户的业务SQL作为一阶段,Seata框架会自动生成事务的二阶段提交和回滚操作。

image.png

  • 一阶段:

    • Seata会连接业务代码,解析SQL语义。
    • 执行业务代码要更新的业务数据,在业务数据被更新前,将其保存为“before image
    • 执行业务SQL,更新业务数据
    • 查询更新后的数据,将其保存成"after image"
    • 生成行锁

    以上操作全部在一个数据库事务内完成,保证了一阶段操作的原子性

图片3.png

  • 二阶段(提交)

    • 因为业务SQL在一阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快照数据和行锁删除,完成数据清理即可。

    图片4.png

  • 三阶段(回滚)

    • 校验脏写

      • 首先要校验脏读,对比“数据库当前业务数据”和“after image”。
      • 如果两份数据完全一致就说明没有脏读,可以还原业务数据。
      • 如果不一致,说明有脏写,出现脏写就需要转人工处理。
    • 还原数据

      • 用"before image"还原业务数据
    • 删除快照数据和行锁

    图片5.png

设计亮点

相比较其他分布式事务框架,Seata架构的亮点如下:

  1. 应用层基于SQL解析实现了自动补偿,从而最大程度的降低业务侵入性。
  2. 将分布式事务中TC(事务协调者)独立部署,负责事务的注册、回滚。
  3. 通过全局锁实现了写隔离和读隔离。

Seata 快速开始

Seata 安装包下载

链接:https://github.com/seata

注意:选择的Seata版本最好与Spring Cloud版本匹配对应,版本说明文档地址

image-20230824102250798

本文使用Mac环境下seata-server1.3.0进行部署说明

https://github.com/seata/seata/releases/tag/v1.3.0

![image-20230824102726876](/Users/zhangshao/Library/Application Support/typora-user-images/image-20230824102726876.png)

事务信息存储配置

server端存储模式(store.model)支持如下三种方式:

  • File

​ 单机模式(默认为此模式),全局事务会话信息存储在内存中,读写并持久化至本地文件root.data(bin/sessionStore/root.data)中,性能较高

~/java_tools/seata/bin/sessionStore/root.data

  • db

​ 高可用模式(MySQL5.7+),全局事务会话信息通过db共享,性能较差一些。

  • redis

Seata-server 1.3及以上版本支持,性能较高,但存在事务信息丢失风险,需配合redis持久化配置策略。

本次使用File单机模式进行启动配置,db模式在补充部分介绍。

进入bin目录,执行./seata-server.sh

Seata Client代码实现

业务场景:

用户下单,整个业务逻辑由两个服务系统组成。

订单服务:根据采购需要创建订单

库存系统:对给定的商品扣减库存数量。

img

配置微服务整合Seata

  • 添加pom依赖
<!-- 添加 seata-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2.2.3.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Hoxton.SR8</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
  • 在各服务对应的数据库中添加undo_log表
CREATE TABLE `undo_log` (
                            `id` bigint(20) NOT NULL AUTO_INCREMENT,
                            `branch_id` bigint(20) NOT NULL,
                            `xid` varchar(100) NOT NULL,
                            `context` varchar(128) NOT NULL,
                            `rollback_info` longblob NOT NULL,
                            `log_status` int(11) NOT NULL,
                            `log_created` datetime NOT NULL,
                            `log_modified` datetime NOT NULL,
                            PRIMARY KEY (`id`),
                            UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
  • 添加事务分组
  • 配置seata的注册中心

具体 yaml配置如下

server:
  port: 8086

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db_product
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root123456

  cloud:
    alibaba:
      seata:
        # 这里需要与seata.service.vGroup-mapping保持一致,
        tx-service-group: seata-product 
  application:
    name: seata-product

seata:
  service:
    vgroup-mapping:
      seata-product: default

    grouplist:
      default: 127.0.0.1:8091

在方法上添加@GlobalTranscational即可。

@GlobalTransactional
	public Boolean create(Integer count){
		String url = "http://localhost:8086/deduct?productId=10001&count="+count;

		Boolean result = restTemplate.getForObject(url, Boolean.class);
		if(result!=null && result){
			if(5 == count){
				throw new RuntimeException("order 发生异常");
			}
			log.info("数据库开始创建订单");
			return true;
		}
		return false;
	}

补充

Seata Server端信息配置db模式

1. 打开`conf/file.conf`文件
2. 修改mode = 'db'
3. 修改数据库连接信息
4. 新建数据库seata及表结构:可以去seata提供的资源信息中下载https://github.com/seata/seata/tree/1.3.0,其sql位置在/script/server/db/mysql.sql;

image-20230824110444935

配置Nacos注册中心,负责与TC通信

将Seata Server注册到nacos,修改config目录下的registry.conf配置

registry {
  type = "nacos"
  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = "nacos"
    password = "nacos"
  }
}

配置Nacos作为配置中心

  1. Seata-Server端配置nacos配置中心,在registry.conf中加入配置使用nacos作为配置中心
config {
  type = "nacos"

  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
  }
}

其中的含义为:

  • Registry:服务器要注册到nacos的位置(命名空间,集群地址)
  • Config:Seata服务器配置,这里需要去nacos配置中心上去获取配置,所以需要推上去。

在nacos 主目录,创建config.txt,内容拷贝git上的配置https://github.com/seata/seata/blob/1.3.0/script/config-center/config.txt

主要修改的内容如下

# 其中my_test_tx_group是可以自定义的且之后要与seata客户端中相匹配
service.vgroupMapping.my_test_tx_group=default

 
# 修改为db,下面修改到对应数据库的信息
store.mode=file

store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true
store.db.user=username
store.db.password=password

同在nacos主目录,创建nacos-config.sh,内容拷贝git上的配置https://github.com/seata/seata/blob/1.3.0/script/config-center/nacos/nacos-config.sh

主要修改的内容如下

# 需要修改nacos-config.sh文件第97行代码,用以获取config.txt文件

image-20230825103513920

修改nacos-config的权限

chmod 777 ./nacos-config.sh

整个目录解构如下

image-20230825103702226

运行nacos-config.sh

./nacos-config.sh -h 127.0.0.1 -p 8848 -u nacos -w nacos -g SEATA_GROUP
参数说明
-h  host 默认值 localhsot
-p  post 默认值 8848
-u  nacos用户名
-w  nacos密码
-g  配置分组 默认值为 ‘SEATA_GROUP’
-t  租户信息 对应Nacos的命名空间id字段,默认为空

image-20230825104138280

随后登录nacos-server查看配置信息

image-20230825104236162