一、基本介绍
1、什么是分布式事务
指一次大的操作由不同的小操作组成的,这些小的操作分布在不同的服务器上,分布式事务需要保证这些小操作要么全部成功,要么全部失败。从本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
2、为什么要使用分布式事务
在微服务独立数据源的思想,每一个微服务都有一个或者多个数据源,虽然单机单库事务已经非常成熟,但是由于网路延迟和不可靠的客观因素,分布式事务到现在也还没有成熟的方案,对于中大型网站,特别是涉及到交易的网站,一旦将服务拆分微服务,分布式事务一定是绕不开的一个组件,通常解决分布式事务问题。
3、seata 分布式事务
Seata
是阿里开源的一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。
Seata
目标打造一站式的分布事务的解决方案,最终会提供四种事务模式:
-
AT 模式:参见(《Seata AT 模式》 https://seata.io/zh-cn/docs/dev/mode/at-mode.html)文档
-
TCC 模式:参见(《Seata TCC 模式》 https://seata.io/zh-cn/docs/dev/mode/tcc-mode.html)文档
-
Saga 模式:参见(《SEATA Saga 模式》 https://seata.io/zh-cn/docs/dev/mode/at-mode.html)文档
-
XA 模式:正在开发中...
目前使用的流行度情况是:AT > TCC > Saga。因此,我们在学习Seata的时候,可以花更多精力在AT模式上,最好搞懂背后的实现原理,毕竟分布式事务涉及到数据的正确性,出问题需要快速排查定位并解决。
二、下载方式
-
Windows平台安装包下载
可以从https://github.com/seata/seata/releases
下载seata-server-$version.zip
包
Windows
下载解压后(.zip)
,直接点击bin/seata-server.bat
就可以了。(本文使用1.4.0版本)
三、如何使用
1、创建相关测试数据库和表
# 订单数据库信息 seata_order
DROP DATABASE IF EXISTS seata_order;
CREATE DATABASE seata_order;DROP TABLE IF EXISTS seata_order.p_order;
CREATE TABLE seata_order.p_order
(id INT(11) NOT NULL AUTO_INCREMENT,user_id INT(11) DEFAULT NULL,product_id INT(11) DEFAULT NULL,amount INT(11) DEFAULT NULL,total_price DOUBLE DEFAULT NULL,status VARCHAR(100) DEFAULT NULL,add_time DATETIME DEFAULT CURRENT_TIMESTAMP,last_update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (id)
) ENGINE = InnoDBAUTO_INCREMENT = 1DEFAULT CHARSET = utf8mb4;DROP TABLE IF EXISTS seata_order.undo_log;
CREATE TABLE seata_order.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 = InnoDBAUTO_INCREMENT = 1DEFAULT CHARSET = utf8mb4;# 产品数据库信息 seata_product
DROP DATABASE IF EXISTS seata_product;
CREATE DATABASE seata_product;DROP TABLE IF EXISTS seata_product.product;
CREATE TABLE seata_product.product
(id INT(11) NOT NULL AUTO_INCREMENT,price DOUBLE DEFAULT NULL,stock INT(11) DEFAULT NULL,last_update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (id)
) ENGINE = InnoDBAUTO_INCREMENT = 1DEFAULT CHARSET = utf8mb4;DROP TABLE IF EXISTS seata_product.undo_log;
CREATE TABLE seata_product.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 = InnoDBAUTO_INCREMENT = 1DEFAULT CHARSET = utf8mb4;INSERT INTO seata_product.product (id, price, stock)
VALUES (1, 10, 20);# 账户数据库信息 seata_account
DROP DATABASE IF EXISTS seata_account;
CREATE DATABASE seata_account;DROP TABLE IF EXISTS seata_account.account;
CREATE TABLE seata_account.account
(id INT(11) NOT NULL AUTO_INCREMENT,balance DOUBLE DEFAULT NULL,last_update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (id)
) ENGINE = InnoDBAUTO_INCREMENT = 1DEFAULT CHARSET = utf8mb4;DROP TABLE IF EXISTS seata_account.undo_log;
CREATE TABLE seata_account.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 = InnoDBAUTO_INCREMENT = 1DEFAULT CHARSET = utf8mb4;
INSERT INTO seata_account.account (id, balance)
VALUES (1, 50);
其中,每个库中的undo_log
表,是Seata AT
模式必须创建的表,主要用于分支事务的回滚。
另外,为了测试方便,插入了一条id = 1
的account
记录,和一条id = 1
的product
记录。
2、引入ruoyi-common-datasource依赖(包含seata配置)
<!-- ruoyi common datasource -->
<dependency><groupId>com.ruoyi</groupId><artifactId>ruoyi-common-datasource</artifactId>
</dependency>
3、服务配置文件
# spring配置
spring: redis:host: localhostport: 6379password: datasource:druid:stat-view-servlet:enabled: trueloginUsername: adminloginPassword: 123456dynamic:druid:initial-size: 5min-idle: 5maxActive: 20maxWait: 60000timeBetweenEvictionRunsMillis: 60000minEvictableIdleTimeMillis: 300000validationQuery: SELECT 1 FROM DUALtestWhileIdle: truetestOnBorrow: falsetestOnReturn: falsepoolPreparedStatements: truemaxPoolPreparedStatementPerConnectionSize: 20filters: stat,wall,slf4jconnectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000datasource:# 主库数据源master:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/ry-cloud?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8username: rootpassword: password# seata_order数据源order:username: rootpassword: passwordurl: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8driver-class-name: com.mysql.cj.jdbc.Driver# seata_account数据源account:username: rootpassword: passwordurl: jdbc:mysql://localhost:3306/seata_account?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8driver-class-name: com.mysql.cj.jdbc.Driver# seata_product数据源product:username: rootpassword: passwordurl: jdbc:mysql://localhost:3306/seata_product?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8driver-class-name: com.mysql.cj.jdbc.Driverseata: true #开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭# seata配置
seata:enabled: true# Seata 应用编号,默认为 ${spring.application.name}application-id: ${spring.application.name}# Seata 事务组编号,用于 TC 集群名tx-service-group: ${spring.application.name}-group# 关闭自动代理enable-auto-data-source-proxy: false# 服务配置项service:# 虚拟组和分组的映射vgroup-mapping:ruoyi-system-group: default# 分组和 Seata 服务的映射grouplist:default: 127.0.0.1:8091config:type: fileregistry:type: file# mybatis配置
mybatis:# 搜索指定包别名typeAliasesPackage: com.ruoyi.system# 配置mapper的扫描,找到所有的mapper.xml映射文件mapperLocations: classpath:mapper/**/*.xml# swagger配置
swagger:title: 系统模块接口文档license: Powered By ruoyilicenseUrl: https://ruoyi.vip
注意:
一定要设置spring.datasource.dynamic.seata
配置项为true
,开启对Seata
的集成,否则会导致Seata
全局事务回滚失败。