因为这里主要是讲解分布式事务,关于什么是事务,以及事务的特性,单个事务的使用方式,以及在Spring框架下,事务的传播方式,这里就不再赘述了。但是我这里要补充一点就是,一提到事务大家脑子里第一闪念就是传统意义上的事务,就是指数据库上的事务,实则在我们正常的开发中,更多的关注的是数据的一致性。
但是又因为我们的数据不仅仅是存储在数据库上,也可能是别的组件上,或者是别的不支持事务的数据库上。时至今日,我们的“事务”的概念已经逐渐泛化掉了,在很多场景下,如果只是讲我们的数据库的事务显得过于狭隘,终极目的就是为了保持数据的一致性。因为数据的事务比较有代表性,而且每一位开发的同学都是从这里接触的事务,所以我下面也主要以数据库来讲,但不仅仅是指数据库的事务。
数据库的事务的生效范围是数据库连接级别的。我们先说一下这里的连接是什么意思,无论是我们通过navicat或者别的方式,比如JDBC操作数据库,其实都是基于连接。我想大家一定要理解“事务基于连接而不是基于数据库”这句话的意思,就是说即便我操作的是同一个数据库,不同连接上的事务仍然是两个事务,而不是说我只有都对两个不同的数据库的操作才是两个事务。
- 当我们使用navicat的时候,我们连上数据库开的窗口那就是一个数据库连接
- JDBC中先获取数据库连接才能开启并操作事务
try {
Connection connection = DriverManager.getConnection(URL, USER, PASSWORD);
// 禁用自动提交,开始手动管理事务
connection.setAutoCommit(false);
// 执行DML
String deductSql = "UPDATE account SET balance = balance - ? WHERE account_id = ?";
deductStmt = connection.prepareStatement(deductSql);
deductStmt.setDouble(1, 100); // 扣减金额
deductStmt.setInt(2, 1); // 账户A的ID
deductStmt.executeUpdate();
// 提交事务
connection.commit();
} catch (Exception e) {
// 出现异常回滚
connection.rollback();
}
从上面大家已经知道事务是基于连接级别的。也就是说在一个连接上某一瞬间同时只能有一个事务存在。当你在两个不同的连接上同时操作两个事务,两个事务是相互独立的,但是效果都会体现在数据库上。如果是两个事务同时开启并操作相同的数据,这种冲突当前的数据库会根据设置的隔离级别来进行处理,由数据库本身保证。
上面已经说了我们的事务都是基于连接的。如果在一次请求进来,在处理这一个请求期间,我们多次执行数据库操作,如果每次操作数据库的时候每次都会重新获取新的连接(现在大家都会配置数据库连接池,所以存在多次获取同一个连接的可能性),无论获取的是不是同一个数据库连接,只要是设置的是自动提交,数据库连接默认都是自动提交,就没办法保证整个请求生命周期中的数据符合事务一致性。
想必大家已经明白事务是基于连接的意义了,而连接又是面向数据库的。如果我们在一次请求中操作了同一个数据库,其中的事务我们叫做集中式事务,这个是比较好保证的,只需要我们在整个请求过程中保持使用的是同一个数据库连接,如果全部执行成功就commit,所有的数据库操作全部生效。如果失败就rollback,所有的数据库操作全部失效。本质可以参考上面的jdbc demo,其实Spring框架的事务就是基于这个原理做的。详情可以看大白话讲解Spring对数据源和事务管理以及多数据源配置
众所周知(你真知道吗?评论区告诉我),Spring的事务是基于事务管理器 @Transactional(transactionManager = "primaryTransactionManager")
,事务管理器是基于数据源。
// 配置主数据源的事务管理器
@Primary
@Bean(name = "primaryTransactionManager")
public DataSourceTransactionManager primaryTransactionManager(
@Qualifier("primaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
Spring支持多个数据源的配置,也就支持多个事务管理器,也就支持多个不同数据源的事务,但是没办法保证不同数据源的事务保持一致,不同的数据源是在不同的事务上下文处理的,这种情况它们已经是分布式事务了。
如果是操作了不同的数据库,要么对所有数据库的操作全部成功,要么对所有数据库的操作全部回滚,这种场景的事务,我们叫做分布式事务。解释下,分布式事务是我们需要保证全局的一致性,无论是不是数据库,有可能我们把数据更新到Redis、Mongo或者别的保存数据状态的组件上。所以分布式事务只是一种对数据状态全局一致性的保证。
分布式事务,目前市场上比较成熟的阿里的开源的 SEATA分布式事务框架。
分布式事务的四种模式:
四种模式对比
模式 | 一致性 | 适用场景 | 性能 | 开发复杂度 | 数据库支持 |
---|---|---|---|---|---|
AT | 最终一致性 | 数据库为主的简单场景 | 高 | 低 | 关系型数据库 |
TCC | 强一致性 | 复杂业务逻辑 | 较高 | 高 | 多种存储类型 |
SAGA | 最终一致性 | 长事务、异步场景 | 较高 | 较高 | 不限 |
XA | 强一致性 | 金融类强一致性需求 | 较低 | 低 | 支持 XA 的数据库 |
AT模式
AT 模式是 Seata 中的核心模式,适用于关系型数据库的分布式事务处理。以下是一个使用 AT 模式的简单示例,演示如何在 Spring Boot 中集成 Seata 来处理分布式事务。假设有两个微服务 order-service
和 account-service
,它们分别负责订单处理和账户扣款。
1. 环境准备
- Spring Boot
- MySQL
- Seata Server
- Nacos(用于服务发现和配置管理)
2. 项目结构
假设我们有两个模块:
order-service
:负责生成订单account-service
:负责扣减账户余额
每个服务都使用 Spring Boot 和 MySQL,并且通过 Seata 来保证分布式事务的一致性。
seata-demo/
├── account-service
├── order-service
└── seata-server (单独运行的 Seata Server)
3. 依赖引入
每个微服务都需要引入以下依赖:
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
4. 配置 Seata
在 order-service
和 account-service
的 application.yml
中添加 Seata 和 Nacos 的配置。
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
datasource:
url: jdbc:mysql://localhost:3306/order_db
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
seata:
tx-service-group: my_test_tx_group
feign:
discovery:
enabled: true
server:
port: 8081
seata:
enabled: true
service:
vgroup-mapping:
my_test_tx_group: "default"
disable-global-transaction: false
5. 数据库表结构
order
表(订单服务中的订单表)
CREATE TABLE `order_tbl` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`user_id` VARCHAR(255) DEFAULT NULL,
`commodity_code` VARCHAR(255) DEFAULT NULL,
`count` INT(11) DEFAULT NULL,
`money` DECIMAL(11, 0) DEFAULT NULL,
PRIMARY KEY (`id`)
);
account
表(账户服务中的账户表)
CREATE TABLE `account_tbl` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`user_id` VARCHAR(255) DEFAULT NULL,
`money` DECIMAL(11, 0) DEFAULT NULL,
PRIMARY KEY (`id`)
);
6. Order Service 实现
在 order-service
中实现订单创建逻辑,并使用 Seata 的全局事务。
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/create")
public String create(@RequestParam("userId") String userId,
@RequestParam("commodityCode") String commodityCode,
@RequestParam("count") Integer count,
@RequestParam("money") BigDecimal money) {
orderService.createOrder(userId, commodityCode, count, money);
return "Order created successfully!";
}
}
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private AccountFeignClient accountFeignClient;
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public void createOrder(String userId, String commodityCode, Integer count, BigDecimal money