本文目录导读:

我为您整理一个完整的Spring Cloud Alibaba Seata分布式事务实战案例,涵盖环境搭建、代码实现和核心原理。
案例场景设计
业务场景:电商下单服务
- 订单服务:创建订单
- 库存服务:扣减库存
- 账户服务:扣减余额
三个服务需要通过分布式事务保证数据一致性。
环境准备
技术栈版本
Spring Boot: 2.3.12.RELEASE
Spring Cloud: Hoxton.SR12
Spring Cloud Alibaba: 2.2.5.RELEASE
Seata: 1.4.2
MySQL: 8.0
Nacos: 1.4.1
数据库初始化
-- 订单库
CREATE DATABASE seata_order;
USE seata_order;
CREATE TABLE t_order (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT,
product_id BIGINT,
count INT,
money DECIMAL(10,2),
status INT DEFAULT 0
);
-- 库存库
CREATE DATABASE seata_storage;
USE seata_storage;
CREATE TABLE t_storage (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
product_id BIGINT,
total INT,
used INT,
residue INT
);
-- 账户库
CREATE DATABASE seata_account;
USE seata_account;
CREATE TABLE t_account (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT,
total DECIMAL(10,2),
used DECIMAL(10,2),
residue DECIMAL(10,2)
);
-- 每个业务库都需要创建undo_log表
CREATE TABLE undo_log (
id BIGINT NOT NULL AUTO_INCREMENT,
branch_id BIGINT NOT NULL,
xid VARCHAR(100) NOT NULL,
context VARCHAR(128) 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),
UNIQUE KEY ux_undo_log (xid, branch_id)
);
核心代码实现
父POM配置
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
订单服务(Order Service)
pom.xml
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
application.yml
server:
port: 8001
spring:
application:
name: seata-order-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
alibaba:
seata:
tx-service-group: my_test_tx_group
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
username: root
password: root
logging:
level:
io.seata: debug
OrderController.java
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/order/create")
public String createOrder(@RequestParam("userId") Long userId,
@RequestParam("productId") Long productId,
@RequestParam("count") Integer count) {
orderService.createOrder(userId, productId, count);
return "订单创建成功";
}
}
OrderService.java
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private StorageFeignClient storageFeignClient;
@Autowired
private AccountFeignClient accountFeignClient;
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public void createOrder(Long userId, Long productId, Integer count) {
// 1. 创建订单
BigDecimal money = new BigDecimal("10").multiply(new BigDecimal(count));
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
order.setCount(count);
order.setMoney(money);
order.setStatus(0);
orderDao.createOrder(order);
// 2. 扣减库存(远程调用)
storageFeignClient.decreaseStock(productId, count);
// 3. 扣减账户余额(远程调用)
accountFeignClient.decreaseBalance(userId, money);
// 4. 模拟异常测试回滚
// 取消下面注释测试分布式事务回滚
// int i = 1 / 0;
// 5. 修改订单状态为已完成
orderDao.updateOrderStatus(order.getId(), 1);
}
}
StorageFeignClient.java
@FeignClient(name = "seata-storage-service", path = "/storage")
public interface StorageFeignClient {
@GetMapping("/decrease")
String decreaseStock(@RequestParam("productId") Long productId,
@RequestParam("count") Integer count);
}
AccountFeignClient.java
@FeignClient(name = "seata-account-service", path = "/account")
public interface AccountFeignClient {
@GetMapping("/decrease")
String decreaseBalance(@RequestParam("userId") Long userId,
@RequestParam("money") BigDecimal money);
}
库存服务(Storage Service)
StorageService.java
@Service
public class StorageService {
@Autowired
private StorageDao storageDao;
public void decreaseStock(Long productId, Integer count) {
// 检查库存并扣减
Storage storage = storageDao.findByProductId(productId);
if (storage == null) {
throw new RuntimeException("商品不存在");
}
if (storage.getResidue() < count) {
throw new RuntimeException("库存不足");
}
storageDao.decreaseStock(productId, count);
}
}
StorageController.java
@RestController
@RequestMapping("/storage")
public class StorageController {
@Autowired
private StorageService storageService;
@GetMapping("/decrease")
public String decreaseStock(@RequestParam("productId") Long productId,
@RequestParam("count") Integer count) {
storageService.decreaseStock(productId, count);
return "扣减库存成功";
}
}
账户服务(Account Service)
AccountService.java
@Service
public class AccountService {
@Autowired
private AccountDao accountDao;
public void decreaseBalance(Long userId, BigDecimal money) {
// 检查余额并扣减
Account account = accountDao.findByUserId(userId);
if (account == null) {
throw new RuntimeException("用户不存在");
}
if (account.getResidue().compareTo(money) < 0) {
throw new RuntimeException("余额不足");
}
accountDao.decreaseBalance(userId, money);
}
}
AccountController.java
@RestController
@RequestMapping("/account")
public class AccountController {
@Autowired
private AccountService accountService;
@GetMapping("/decrease")
public String decreaseBalance(@RequestParam("userId") Long userId,
@RequestParam("money") BigDecimal money) {
accountService.decreaseBalance(userId, money);
return "扣减余额成功";
}
}
Seata Server配置
registry.conf
registry {
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "localhost:8848"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
type = "nacos"
nacos {
serverAddr = "localhost:8848"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
}
}
file.conf
store {
mode = "db"
db {
datasource = "druid"
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://localhost:3306/seata_server?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC"
user = "root"
password = "root"
minConn = 5
maxConn = 20
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
}
}
事务配置
每个服务添加配置类
@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return new DruidDataSource();
}
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:mapper/*.xml"));
return sqlSessionFactoryBean.getObject();
}
}
Seata配置(每个服务的bootstrap.yml)
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: my_test_tx_group
config:
type: nacos
nacos:
serverAddr: localhost:8848
group: SEATA_GROUP
registry:
type: nacos
nacos:
serverAddr: localhost:8848
group: SEATA_GROUP
启动与测试
启动顺序
启动Nacos
2. 启动Seata Server(执行seata-server.sh)
3. 启动数据库服务
4. 启动三个业务服务
5. 初始化测试数据
测试数据初始化
-- 库存表 INSERT INTO t_storage VALUES (1, 1, 100, 0, 100); -- 账户表 INSERT INTO t_account VALUES (1, 1, 1000.00, 0, 1000.00);
测试接口
# 正常下单 GET http://localhost:8001/order/create?userId=1&productId=1&count=2 # 异常场景测试(在OrderService中取消注释异常代码) # 观察库存和余额是否回滚
核心原理说明
AT模式工作原理
一阶段:
- 业务SQL执行前获取全局锁
- 执行业务SQL
- 生成undo_log快照
2. 二阶段提交:
- 删除undo_log
- 释放全局锁
3. 二阶段回滚:
- 根据undo_log回滚数据
- 释放全局锁
事务传播机制
@GlobalTransactional开启全局事务- 所有参与方通过同一XID关联
- Seata通过拦截器自动处理分支事务
常见问题及解决方案
数据源代理问题
// 必须使用DataSourceProxy包装
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
事务超时设置
seata:
service:
vgroup-mapping:
my_test_tx_group: default
enable-degrade: false
disable-global-transaction: false
client:
tm:
commit-retry-count: 5
rollback-retry-count: 5
性能优化建议
seata:
client:
rm:
report-success-enable: false
table-meta-check-enable: true
report-retry-count: 5
async-commit-buffer-limit: 10000
这个案例完整展示了如何使用Seata实现分布式事务,需要注意的是,在实际生产环境中,还需要考虑性能优化、高可用部署、监控告警等方面的问题。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播