1、分布式事务概念说明
分布式系统会把一个应用系统拆分为可独立部署的多个服务,因此需要服务与服务之间远程协作才能完成事务操作,这种分布式系统环境下由不同的服务之间通过网络远程协作完成事务称之为分布式事务,例如:用户注册送积分事务、创建订单减库存事务,银行转账事务等都是分布式事务,必须要保证不同服务状态结果的一致性。本地事务依赖数据库本身提供的事务特性来实现,因此以下逻辑可以控制本地事务
2、分布式事务产生的情景
1.跨JVM进程产生分布式事务
典型的场景就是微服务架构:微服务之间通过 远程调用完成事务操作。比如:订单微服务和库存微服务,下单的同时订单微服务请求库存微服务减少库存
2.跨数据库实例产生分布式事务
单体系统访问多个数据库实例:当单体系统需要访问多个数据库(实例)时就会产生分布式事务。比如:用户信息和订单信息分别在两个MySQL实例存储,用户管理系统删除用户信息,需要分别删除用户信息及用户的订单信息,由于数据分布在不同的数据实例,需要通过不同的数据库链接去操作数据,此时产生分布式事务
3.多服务访问同一个数据库实例
订单微服务和库存微服务即使访问同一个数据库也会产生分布式事务,原因就是 跨JVM进程,两个微服务持有了不同的数据库链接进行数据库操作,此时产生分布式事务
3、分布式事务问题由来
1、单体项目
一个模块对应一个数据库中的多张表
采用本地**@Transactiona**即可解决
2、分布式项目1
多个模块对应一个数据库中的多张表
采用消息中间件即可解决
3、分布式项目2
多个模块对应多个数据库中的多张表
采用消息中间件 和Seata都可,偷懒使用Seata,自己实现可以使用消息中间件
4、Seate
1、简介
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
@GlobalTransactional:一个注解搞定一切
2、Seata的三大角色
在 Seata 的架构中,一共有三个角色:
- TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。 - TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。 - RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
其中,TC 为单独部署的 Server 服务端,TM 和 RM 为嵌入到应用中的 Client 客户端。
案例场景
用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:
- 仓储服务:对给定的商品扣除仓储数量。
- 订单服务:根据采购需求创建订单。
- 帐户服务:从用户帐户中扣除余额。
5、环境搭建
1、安装(seata 1.4.2)
官网地址:https://github.com/seata/seata/releases
2、修改配置文件
1、file.conf
2、registry.conf
准备seataServer.properties配置信息
properties
service.vgroupMapping.my_test_tx_group=default #此处必须要和配置文件中一致 配置文件在script\config-center\config.txt中
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true&serverTimezone=UTC
store.db.user=root
store.db.password=123456
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
在下载Source code(zip)源代码中复制script到bin同目录中
在script中找到config.txt(seata-server-1.4.2\seata\seata-server-1.4.2\script\config-center)
最终目录效果
进入conf双击直接运行或者命令运行需要准备git bash环境
命令运行
java
// 运行指令 ,通过 Git Bash Here
sh nacos‐config.sh ‐h localhost ‐p 8848 -u nacos -w nacos
成功结果如下
3、进入bin目录双击运行seata-server.bat
成功如下图
seata注册成功如图所示
6、案例实现
分别创建三个项目订单 *商品*支付
项目结构
数据库结构
准备数据库
mysql
/*
Navicat Premium Data Transfer
Source Server : mysql
Source Server Type : MySQL
Source Server Version : 80036 (8.0.36)
Source Host : localhost:3306
Source Schema : seata
Target Server Type : MySQL
Target Server Version : 80036 (8.0.36)
File Encoding : 65001
Date: 08/07/2024 14:53:40
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for branch_table
-- ----------------------------
DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table` (
`branch_id` bigint NOT NULL,
`xid` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`transaction_id` bigint NULL DEFAULT NULL,
`resource_group_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`resource_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`branch_type` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`status` tinyint NULL DEFAULT NULL,
`client_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`application_data` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`gmt_create` datetime(6) NULL DEFAULT NULL,
`gmt_modified` datetime(6) NULL DEFAULT NULL,
PRIMARY KEY (`branch_id`) USING BTREE,
INDEX `idx_xid`(`xid` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of branch_table
-- ----------------------------
-- ----------------------------
-- Table structure for distributed_lock
-- ----------------------------
DROP TABLE IF EXISTS `distributed_lock`;
CREATE TABLE `distributed_lock` (
`lock_key` char(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`lock_value` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`expire` bigint NULL DEFAULT NULL,
PRIMARY KEY (`lock_key`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of distributed_lock
-- ----------------------------
INSERT INTO `distributed_lock` VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` VALUES ('TxTimeoutCheck', ' ', 0);
-- ----------------------------
-- Table structure for global_table
-- ----------------------------
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table` (
`xid` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`transaction_id` bigint NULL DEFAULT NULL,
`status` tinyint NOT NULL,
`application_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`transaction_service_group` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`transaction_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`timeout` int NULL DEFAULT NULL,
`begin_time` bigint NULL DEFAULT NULL,
`application_data` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`gmt_create` datetime NULL DEFAULT NULL,
`gmt_modified` datetime NULL DEFAULT NULL,
PRIMARY KEY (`xid`) USING BTREE,
INDEX `idx_status_gmt_modified`(`status` ASC, `gmt_modified` ASC) USING BTREE,
INDEX `idx_transaction_id`(`transaction_id` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of global_table
-- ----------------------------
-- ----------------------------
-- Table structure for lock_table
-- ----------------------------
DROP TABLE IF EXISTS `lock_table`;
CREATE TABLE `lock_table` (
`row_key` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`xid` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`transaction_id` bigint NULL DEFAULT NULL,
`branch_id` bigint NOT NULL,
`resource_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`table_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`pk` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`status` tinyint NOT NULL DEFAULT 0 COMMENT '0:locked ,1:rollbacking',
`gmt_create` datetime NULL DEFAULT NULL,
`gmt_modified` datetime NULL DEFAULT NULL,
PRIMARY KEY (`row_key`) USING BTREE,
INDEX `idx_status`(`status` ASC) USING BTREE,
INDEX `idx_branch_id`(`branch_id` ASC) USING BTREE,
INDEX `idx_xid`(`xid` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of lock_table
-- ----------------------------
-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`branch_id` bigint NOT NULL,
`xid` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`context` varchar(128) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ux_undo_log`(`xid` ASC, `branch_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of undo_log
-- ----------------------------
SET FOREIGN_KEY_CHECKS = 1;
1、依赖
1、父依赖
xml
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<dubbo.version>3.2.0-beta.4</dubbo.version>
<spring-boot.version>2.6.11</spring-boot.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--springcloudalibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.0.4.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.4.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-bom</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2、子模块依赖
xml
<dependencies>
<!-- Api接口 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<!-- spring boot starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!--mybatis-plus-->
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!--实体类-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>bean</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<!-- 要与seata服务端版本一直,所以把自带的替换掉 -->
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
</dependencies>
2、配置文件
1、bootstrap.yaml
yaml
spring:
application:
name: alibaba-seata-provider #服务名称
cloud:
nacos:
discovery:
server-addr: localhost:8848 #将nacos作为注册中心
# config:
# server-addr: localhost:8848 #将nacos作为配置中心
# file-extension: yaml #指定yaml格式的配置
2、application.yaml
yaml
#dubbo配置
dubbo:
application:
name: Dubbo-provider-9002 #Dubbo服务名称
qos-enable: false
protocol:
name: dubbo #协议名称
port: -1 #端口号,-1表示自动分配
registry:
address: nacos://localhost:8848 #注册中心地址
consumer:
timeout: 10000 #消费者调用超时时间设置
retries: 0 #消费者重试次数
server:
port: 9002
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
username: root
password: 123456
mybatis-plus:
type-aliases-package: com.ruoyi.pojo
mapper-locations: classpath:/mybatis/mapper/*.xml
#seata配置
seata:
enabled: true
enable-auto-data-source-proxy: true
tx-service-group: my_test_tx_group #事务分组需要和服务端配置文件中一致
registry:
type: nacos
nacos:
application : seata-server
serverAddr : 127.0.0.1:8848
group : SEATA_GROUP
cluster : default
username : nacos
password : nacos
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
username: nacos
password: nacos
data-id: seataServer.properties
每个子模块微服务都要加上配置文件
3、service
java
/**
* 用户余额接口服务
*/
@DubboReference()
private BalanceService balanceService;
/**
* 商品接口服务
*/
@DubboReference()
private GoodsService goodsServiceImpl;
@GlobalTransactional
@Override
public void inserOrder(Order order) {
//创建订单信息
orderMapper.insert(order);
//扣减用户余额信息 这里我是使用dubbo进行调用的 也可以用OpenFeign或者RestTemplate进行RPC调用
balanceService.updateBalance(new Balance(1,1,new BigDecimal(100),new BigDecimal(1)));
//扣减商品库存信息
goodsServiceImpl.updateGoods(new Goods(1,1));
}
此处在商品服务中模拟异常
java
@Override
public int updateGoods(Goods goods) {
//根据商品id获取商品信息
Goods goods1 = goodsMapper.selectById(goods.getId());
int i = 10/0;
System.err.println(i);
//更新商品库存信息
goods1.setGoodsCount(goods1.getGoodsSum() - goods.getGoodsCount());
int update = goodsMapper.updateById(goods1);
return update;
}
4、启动类
java
@SpringBootApplication
@EnableDiscoveryClient //nacos注册中心
@EnableDubbo //开启dubbo
@EnableAutoDataSourceProxy //开启seata
public class ProviderApplication_9003 {
public static void main(String[] args) {
System.setProperty("spring.cloud.bootstrap.enabled", "true");
SpringApplication.run(ProviderApplication_9003.class,args);
}
}
注意:每个子模块微服务启动类都要加上@EnableAutoDataSourceProxy
测试
查看seata日志已经回滚
项目源码:https://gitee.com/peng-pengjun/spring-cloud-alibaba-seata