一、简介

在近几年流行的微服务架构中,由于对服务和数据库进行了拆分,原来的一个单进程本地事务变成多个进程的本地事务,这时要保证数据的一致性,就需要用到分布式事务了。分布式事务的解决方案有很多,其中国内比较主流的框架就是Seata了。

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

这里推荐使用AT模式,该模式具备代码侵入性小,性能高等优点,前提是:

  1. 基于支持本地 ACID 事务的关系型数据库

  2. Java 应用,通过 JDBC 访问数据库。

二、环境搭建

版本清单:

名称 版本
Nacos 1.3.2
Seata 1.4.2
spring-boot 2.2.6.RELEASE
spring-cloud-starter-alibaba-nacos-discovery 2.2.6.RELEASE
spring-cloud-starter-openfeign 2.2.6.RELEASE
seata-spring-boot-starter 1.4.2
spring-cloud-starter-alibaba-seata 2.2.1.RELEASE

2.1 Nacos安装

本地搭建nacos比较简单,首先通过github下载nacos(我的是1.3.2版本,下载地址),然后解压缩进入bin目录,打开命令行工具运行如下命令即可启动。

startup.sh -m standalone

启动后访问http://localhost:8848/nacos/index.html即可,默认账号密码是nacos/nacos。

2.2 部署seata-server

  • 1.下载解压

    进入seata的发行页面,选择需要的版本下载,然后解压。

  • 2.修改配置文件

    进入conf目录(如/Users/ship/program/seata/seata-server-1.4.2/conf),可以看到有file.conf和registry.conf两个配置文件。

    首先打开file.conf文件,修改配置如下

    ## transaction log store, only used in seata-server
    store {
    ## store mode: file、db、redis
    mode = "file" // 改为db
    ## rsa decryption public key
    publicKey = ""
    ## file store property
    file {
    ## store location dir
    dir = "sessionStore"
    # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
    maxBranchSessionSize = 16384
    # globe session size , if exceeded throws exceptions
    maxGlobalSessionSize = 512
    # file buffer size , if exceeded allocate new buffer
    fileWriteBufferCacheSize = 16384
    # when recover batch read size
    sessionReloadReadSize = 100
    # async, sync
    flushDiskMode = async
    } ## database store property
    db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
    datasource = "druid"
    ## mysql/oracle/postgresql/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"
    ## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
    url = "jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true"// 改成自己的数据库地址
    user = "mysql" // 改成自己的数据库用户名
    password = "mysql"// 改成自己的数据库密码
    minConn = 5
    maxConn = 100
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
    } ...
    }

    这个数据库会在下面的第四步再创建。

    然后打开registry.conf文件,修改注册中心为nacos

    registry {
    # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
    type = "file" // 改为nacos nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = "" // 默认名称空间为public
    cluster = "default"
    username = "" // 默认是不需要密码的,如果开启了安全验证则要填写
    password = ""
    }
    eureka {
    serviceUrl = "http://localhost:8761/eureka"
    application = "default"
    weight = "1"
    }
    ...
    } config {
    # file、nacos 、apollo、zk、consul、etcd3
    type = "file" nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = ""
    password = ""
    dataId = "seataServer.properties"
    }
    ...
    }
  • 3.同步配置到nacos

    低版本的seata需要在项目的resource目录下创建file.conf和registry.conf文件,高版本的只需要将配置信息同步到nacos,然后读取即可。

    首先需要在conf的同级目录(如/Users/ship/program/seata/seata-server-1.4.2)下创建config.txt(下载地址)文件,然后修改数据库配置信息。

    store.db.datasource=druid
    store.db.dbType=mysql
    store.db.driverClassName=com.mysql.jdbc.Driver
    store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true
    store.db.user=seata // 改为自己的账号
    store.db.password=nova2020 // 改为自己的密码
    store.db.minConn=5
    store.db.maxConn=30

    最后进入conf目录(如/Users/ship/program/seata/seata-server-1.4.2/conf),下载nacos-config.sh(下载地址)并使用nacos-config.sh文件同步上传配置到Nacos,命令如下:

    sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t 023933c2-2825-46b2-a05a-4a407b479877

    命令参数介绍:

    -h : 指定nacos的ip地址。

    -p: 指定nacos的端口号。

    -g: 指定分组

    -t: 指定nacos的名称空间,建议为seata单独创建一个名称空间。

    -u: nacos的用户名,开启认证才需要。

    -w: nacos的密码,开启认证才需要。

    打开nacos即可在对应的名称空间下看到那些配置信息,如图:

    不得不吐槽一下,像config.text和nacos-config.sh这些文件在0.9.0版本的seata都是和安装包放一起的,高版本的还需要自己找真的坑。

  • 4.创建数据库

    为seata-server创建seata库并执行db_store.sql ,下载地址

    为每个业务库执行db_undo_log.sql以添加回滚日志的表,下载地址

  • 5.启动

进入bin目录,输入sh seata-server.sh即可启动,部分启动日志如下:

SLF4J: A number (18) of logging calls during the initialization phase have been intercepted and are
SLF4J: now being replayed. These are subject to the filtering rules of the underlying logging system.
SLF4J: See also http://www.slf4j.org/codes.html#replay
16:45:02.688 INFO --- [ main] io.seata.config.FileConfiguration : The file name of the operation is registry
16:45:02.693 INFO --- [ main] io.seata.config.FileConfiguration : The configuration file used is /Users/ship/program/seata/seata-server-1.4.2/conf/registry.conf
16:45:02.785 INFO --- [ main] io.seata.config.FileConfiguration : The file name of the operation is file.conf
16:45:02.785 INFO --- [ main] io.seata.config.FileConfiguration : The configuration file used is /Users/ship/program/seata/seata-server-1.4.2/conf/file.conf
16:45:03.411 INFO --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
16:45:03.843 INFO --- [ main] i.s.core.rpc.netty.NettyServerBootstrap : Server started, listen port: 8091

三、实战

前面环境已经搭好了,现在通过一个示例来验证分布式事务的AT模式。

场景是创建订单时会根据商品的金额来扣减用户余额,分别对应order服务和account服务。

表结构设计如下:

DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT 0,
`money` int(11) DEFAULT 0,
PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`money` int(11) DEFAULT 0,
PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3.1order服务

创建一个order项目并添加nacos、fegin和seata的依赖,pom.xml部分如下:

 <dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.6.RELEASE</version>
</dependency> <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.6.RELEASE</version>
</dependency> <dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2.2.1.RELEASE</version>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>

这里为了引入最新版本的seata-spring-boot-starter,就在spring-cloud-starter-alibaba-seata作了排除,也是官方推荐的方式。如果发现你的项目启动不起来或者有其他问题,可能是版本依赖有问题。

启动类OrderApplication.java添加需要的注解

@EnableAutoDataSourceProxy //这个一定要加,如果不加通过配置文件开启也可以
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class OrderApplication { public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
} }

核心部分OrderService

/**
* @Author: Ship
* @Description:
* @Date: Created in 2021/8/23
*/
@Service
public class OrderService { @Autowired
private OrderDao orderDao; @Autowired
private AccountClient accountClient; @GlobalTransactional
@Transactional(rollbackFor = Exception.class)
public OrderVO create(OrderDTO orderDTO) {
// 创建订单
Order order = new Order();
BeanUtils.copyProperties(orderDTO,order);
orderDao.insert(order);
// 扣除账户余额
AccountDeductDTO accountDeductDTO = new AccountDeductDTO();
Integer total = orderDTO.getMoney() * orderDTO.getCount();
accountDeductDTO.setMoney(total);
accountDeductDTO.setUserId(orderDTO.getUserId());
accountClient.deduct(accountDeductDTO);
return new OrderVO(order.getId());
}
}

只需一个@GlobalTransactional注解即可,用在分布式事务开启的方法上。

修改配置文件bootstrap.yml

spring:
application:
name: order
datasource:
username: root
password: 1234
url: jdbc:mysql://127.0.0.1:3306/seata_order?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
type: com.alibaba.druid.pool.DruidDataSource server:
port: 9900
seata:
registry:
type: nacos
nacos:
application: seata-server # 不配置名称空间,默认public
server-addr: 127.0.0.1:8848
group: "SEATA_GROUP"
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: "SEATA_GROUP"
namespace: 023933c2-2825-46b2-a05a-4a407b479877 # 配置名称空间为seata
enabled: true
tx-service-group: my_test_tx_group # 要与nacos上的配置一致
service:
vgroup-mapping:
my_test_tx_group: default # 要与nacos上的配置一致
disable-global-transaction: false

注意:seata.tx-service-group属性值要和seata-server的config.txt文件中的service.vgroupMapping.${分组名}=default分组名一一对应,这里我用的默认配置my_test_tx_group。关于事务分组还有很多种玩法,可以参考这里https://seata.io/zh-cn/docs/user/txgroup/transaction-group.html。

3.2 account服务

account服务项目结构基本与order服务一致,只是service代码不同。

/**
* @Author: Ship
* @Description:
* @Date: Created in 2021/8/23
*/
@Service
public class AccountService { @Autowired
private AccountDao accountDao; @Transactional(rollbackFor = Exception.class)
public void deduct(AccountDeductDTO accountDeductDTO) {
QueryWrapper<Account> wrapper = new QueryWrapper();
wrapper.lambda().eq(Account::getUserId, accountDeductDTO.getUserId());
Account account = accountDao.selectOne(wrapper);
Integer money = account.getMoney() - accountDeductDTO.getMoney();
account.setMoney(money); // 更新余额
accountDao.updateById(account); // int i = 1 / 0;
}
}

3.3 测试

  1. 启动seata-server

  2. 启动order服务和account服务,并能成功在nacos上看到注册实例。

  1. 首先给用户1111的账户初始100元的余额,sql如下

    insert into account_tbl(user_id,money) VALUES(1111,100);
  2. 请求下单接口http://localhost:9900/order/create,POST body参数如下:

    {
    "userId":1111,
    "commodityCode":"code",
    "count":2,
    "money":10
    }

    查询数据库可以发现,order_tbl表已经有一条订单数据了,并且用户的1111的余额变成了80,说明事务提交成功

  1. 这时将account服务的AccountService中的int i = 1 / 0;这行代码取消注释,并在重启account服务之后再次请求下单接口。

  2. 查看控制台日志发现抛异常了,再次查询order_tbl表发现还是一条订单数据,account_tbl表的用户余额也还是80,说明发生了全局事务回滚。 通过order服务的日志也可以看出,account服务扣减余额接口异常导致了回滚。

    2021-09-04 21:18:28.323  INFO 72668 --- [h_RMROLE_1_1_16] i.s.c.r.p.c.RmBranchCommitProcessor      : rm client handle branch commit process:xid=192.168.3.253:8091:3900290643103043585,branchId=3900290643103043591,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/seata_order,applicationData=null
    2021-09-04 21:18:28.326 INFO 72668 --- [h_RMROLE_1_1_16] io.seata.rm.AbstractRMHandler : Branch committing: 192.168.3.253:8091:3900290643103043585 3900290643103043591 jdbc:mysql://127.0.0.1:3306/seata_order null
    2021-09-04 21:18:28.327 INFO 72668 --- [h_RMROLE_1_1_16] io.seata.rm.AbstractRMHandler : Branch commit result: PhaseTwo_Committed
    2021-09-04 21:36:17.280 INFO 72668 --- [nio-9900-exec-3] i.seata.tm.api.DefaultGlobalTransaction : Begin new global transaction [192.168.3.253:8091:3900290643103043595]
    2021-09-04 21:36:17.282 ERROR 72668 --- [nio-9900-exec-3] c.a.druid.pool.DruidAbstractDataSource : discard long time none received connection. , jdbcUrl : jdbc:mysql://127.0.0.1:3306/seata_order?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true, jdbcUrl : jdbc:mysql://127.0.0.1:3306/seata_order?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true, lastPacketReceivedIdleMillis : 1068019
    2021-09-04 21:36:17.782 INFO 72668 --- [nio-9900-exec-3] i.seata.tm.api.DefaultGlobalTransaction : Suspending current transaction, xid = 192.168.3.253:8091:3900290643103043595
    2021-09-04 21:36:17.782 INFO 72668 --- [nio-9900-exec-3] i.seata.tm.api.DefaultGlobalTransaction : [192.168.3.253:8091:3900290643103043595] rollback status: Rollbacked //回滚

    至此说明我们的分布式事务控制生效了,示例代码包括脚本都已经提交到我的github上,需要的请点击

四、总结

Seata框架上手不难,重点还是理解其实现原理和做到灵活使用,比如事务分组的设计就很巧妙,其AT模式比起之前用过的TCC框架好太多,阿里出品还是厉害啊。

参考资料:

seata官方文档,https://seata.io/zh-cn/docs/overview/what-is-seata.html

https://github.com/seata/seata-samples

分布式事务框架seata入门的更多相关文章

  1. 分布式事务框架 Seata 入门案例

    1.  Seata Server 部署 Seata分TC.TM和RM三个角色,TC(Server端)为单独服务端部署,TM和RM(Client端)由业务系统集成. 首先,下载最新的安装包 也可以下载源 ...

  2. 分布式事务框架Seata及EasyTransaction架构的比对思考

    本文将会对比Seata与EasyTransaction两个分布式事务的一些高层设计,相信大家会有收获. Seata的概述 Seata(曾用名Fescar,开源版本GTS)是阿里的开源分布式事务框架,其 ...

  3. 快速了解阿里微服务热门开源分布式事务框架——Seata

    一.Seata 概述 Seata 是 Simple Extensible Autonomous Transaction Architecture 的简写,由 feascar 改名而来. Seata 是 ...

  4. 分布式事务框架-seata初识

    一.事务与分布式事务 事务,在数据库中指的是操作数据库的最小单位,往大了看,事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消. 那为什么会有分布式事务呢 ...

  5. 开源的分布式事务框架 springcloud Alibaba Seata 的搭建使用 一次把坑踩完。。。

    seata的使用 1. Seata 概述 Seata 是 Simple Extensible Autonomous Transaction Architecture 的简写,由 feascar 改名而 ...

  6. Spring Cloud Alibaba | 微服务分布式事务之Seata

    Spring Cloud Alibaba | 微服务分布式事务之Seata 本篇实战所使用Spring有关版本: SpringBoot:2.1.7.RELEASE Spring Cloud:Green ...

  7. Spring Cloud Alibaba分布式事务组件 seata 详解(小白都能看懂)

    一,什么是事务(本地事务)? 指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行. 简单的说,事务就是并发控制的单位,是用户定义的一个操作序列.      而一个逻辑工作单元要成 ...

  8. SpringCloudAlibaba分布式事务解决方案Seata实战与源码分析-中

    事务模式 概述 在当前的技术发展阶段,不存一个分布式事务处理机制可以完美满足所有场景的需求.一致性.可靠性.易用性.性能等诸多方面的系统设计约束,需要用不同的事务处理机制去满足. 目前使用的流行度情况 ...

  9. 基于Dubbo的分布式事务框架(LCN)

    原文地址:http://原文地址:https://github.com/1991wangliang/transaction 基于Dubbo的分布式事务框架(LCN) 该框架依赖Redis/dubbo/ ...

  10. tcc分布式事务框架解析

    前言碎语 楼主之前推荐过2pc的分布式事务框架LCN.今天来详细聊聊TCC事务协议. 2pc实现:https://github.com/codingapi/tx-lcn tcc实现:https://g ...

随机推荐

  1. es6 Array.form将类数组或者对象转化为数组

    Array.from()方法就是将一个[类数组对象][或者可遍历对象]转换成一个[真正的数组] 那么什么是类数组对象呢?所谓类数组对象,最基本的要求就是具有length属性的对象. let array ...

  2. 原生js拖拽元素(onmouseup不能够触发的原因)

    我们经常会遇见拖拽某一个元素的场景,拖拽也是很常用的: 这次拖拽遇见一个问题,有时在拖拽的时候吗,鼠标松开,元素仍然可以拖拽: 经过查阅资料,发现: 会触发H5原生的拖拽事件.并且不会监听到onmou ...

  3. windows应用程序icon缓存、查看图标、icon制作方法

    windows程序图标缓存 在vs中替换c++程序的图标后,需要重新编译,但是很多情况下都不会刷新,还是看到老的图标,只能重启电脑才能看到新的图标. 通过ChatGPT得到相关的回答如下: 如果在 W ...

  4. Python Selenium 库使用技巧

    Selenium 是一个用于Web应用程序测试的工具.Selenium测试直接运行在浏览器中,就像真正的用户在操作一样.支持的浏览器包括IE,Mozilla Firefox,Safari,Google ...

  5. C/C++ ShellCode 常用加密方式

    异或加密ShellCode: #include <stdio.h> #include <Windows.h> unsigned char buf[] = "\xba\ ...

  6. Bootstrap Table 动态修改行的颜色

    Bootstrap Table 官网地址 https://bootstrap-table.com/百度搜了大量资料 还是找不 动态改变行的颜色,一般搜索到的都是 初始化的时候  使用 rowStyle ...

  7. CF452F Permutation 与 P2757 [国家集训队] 等差子序列 题解

    两道基本一样的题: 题目链接: P2757 [国家集训队] 等差子序列 Permutation 链接:CF 或者 洛谷 等差子序列那题其实就是长度不小于 \(3\) 的等差数列是否存在,我们考虑等于 ...

  8. P10033 题解

    不喜欢特判?不喜欢分讨?不喜欢被卡 corner?不喜欢证明?不喜欢动脑子? 那就看这篇题解! 感性思路 首先感性地感受一下题目宽泛的限制条件题解区各种花式的构造方法就不难想出,符合条件的序列实在很多 ...

  9. java 如何计算两个汉字的相似度?如何获得一个汉字的相似汉字?

    计算汉字相似度 情景 有时候我们希望计算两个汉字的相似度,比如文本的 OCR 等场景.用于识别纠正. 实现 引入 maven <dependency> <groupId>com ...

  10. Java设计模式-适配器模式Adapter

    介绍 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本 因接口不匹配不能一起工作的两个类可以协同工作.其别名为包装器(Wrapper ...