微服务拆分后的事务难题
单体应用中一个本地事务就能保证数据一致性,微服务架构下订单创建涉及订单服务、库存服务、账户服务三个独立数据库,本地事务无法覆盖跨服务的数据操作。库存扣减成功但账户扣款失败时,库存不会自动回滚,造成数据不一致。
Seata的AT模式通过拦截SQL、自动生成回滚日志,在业务层实现分布式事务的自动补偿。与TCC模式相比,AT模式对业务代码侵入最小——只需加一个@GlobalTransactional注解。
Seata Server部署
Seata AT模式需要TC(Transaction Coordinator)协调器独立运行。
Docker部署Seata Server
# 拉取镜像
docker pull seataio/seata-server:2.0.0
# 创建配置目录
mkdir -p /opt/seata/config
# 编写application.yml
cat > /opt/seata/config/application.yml <<'EOF'
server:
port: 7091
seata:
config:
type: nacos
nacos:
server-addr: nacos:8848
namespace: seata
group: SEATA_GROUP
registry:
type: nacos
nacos:
application: seata-server
server-addr: nacos:8848
namespace: seata
group: SEATA_GROUP
store:
mode: db
datasource: druid
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://mysql:3306/seata?rewriteBatchedStatements=true
user: seata
password: seata_pwd
min-conn: 10
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
EOF
# 启动Seata Server
docker run -d --name seata-server \
-p 8091:8091 -p 7091:7091 \
-v /opt/seata/config:/seata-server/resources \
seataio/seata-server:2.0.0
创建Seata元数据表
在Seata专用数据库中执行建表SQL:
-- global_table: 存储全局事务状态
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_name` VARCHAR(128),
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`begin_time` BIGINT,
PRIMARY KEY (`xid`)
) ENGINE = InnoDB;
-- branch_table: 存储分支事务状态
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
PRIMARY KEY (`branch_id`)
) ENGINE = InnoDB;
-- lock_table: 存储全局锁
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(128),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
PRIMARY KEY (`row_key`)
) ENGINE = InnoDB;
业务数据库Undo Log表
AT模式的核心是undo_log——Seata在执行业务SQL前自动生成回滚快照,异常时根据快照逆补偿。每个参与分布式事务的微服务数据库都需要建这张表:
-- 每个业务库都要建
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT '分支事务ID',
`xid` VARCHAR(128) NOT NULL COMMENT '全局事务ID',
`context` VARCHAR(128) NOT NULL COMMENT '上下文',
`rollback_info` LONGBLOB NOT NULL COMMENT '回滚数据',
`log_status` INT(11) NOT NULL COMMENT '日志状态',
`log_created` DATETIME(6) NOT NULL COMMENT '创建时间',
`log_modified` DATETIME(6) NOT NULL COMMENT '修改时间',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB COMMENT ='AT transaction mode undo table';
Spring Boot微服务集成Seata
以订单服务为例,展示完整的集成流程。
Maven依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2023.0.1.0</version>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
application.yml配置
seata:
enabled: true
application-id: order-service
tx-service-group: yunthe-tx-group
service:
vgroup-mapping:
yunthe-tx-group: default
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: seata
group: SEATA_GROUP
业务代码中的事务注解
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockClient stockClient;
@Autowired
private AccountClient accountClient;
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public OrderDTO createOrder(OrderRequest request) {
// 1. 创建订单
Order order = new Order();
order.setUserId(request.getUserId());
order.setProductId(request.getProductId());
order.setQuantity(request.getQuantity());
order.setAmount(request.getAmount());
order.setStatus("CREATING");
orderMapper.insert(order);
// 2. 扣减库存(远程调用库存服务)
stockClient.deduct(request.getProductId(), request.getQuantity());
// 3. 扣减账户余额(远程调用账户服务)
accountClient.debit(request.getUserId(), request.getAmount());
// 4. 更新订单状态
order.setStatus("COMPLETED");
orderMapper.updateById(order);
return OrderDTO.fromEntity(order);
}
}
只需一个@GlobalTransactional注解,Seata自动协调三个服务的本地事务。任一步骤抛出异常,所有已执行的分支事务自动回滚。
数据源代理配置
AT模式通过拦截DataSource的SQL执行来生成undo_log。Spring Boot自动注入的数据源需要被Seata代理:
@Configuration
public class SeataDataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public DataSource dataSource() {
return new HikariDataSource();
}
@Bean
@Primary
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
}
Spring Cloud Alibaba Seata Starter 2023版本已自动完成代理,无需手动配置。如果日志中出现”dataSourceProxy not found”,需要确认Starter版本和自动配置是否生效。
分布式事务排障实战
全局事务超时回滚
Seata默认全局超时60秒。库存服务处理慢导致超时,全局事务被强制回滚:
# 解决方案:调整超时时间
@GlobalTransactional(timeoutMills = 300000) # 5分钟
全局锁冲突
两个全局事务同时操作同一行数据,后到的事务获取全局锁失败:
# 日志特征
# Could not acquire global lock on table:stock, pk:1001
# 方案1:设置重试次数和间隔
seata:
client:
lock:
retry-times: 30
retry-interval: 1000
# 方案2:业务层做乐观锁,避免长时间持有全局锁
脏回滚问题
undo_log中保存的是SQL执行前的数据快照。如果在全局事务执行期间,非Seata管理的程序直接修改了数据库,回滚时会覆盖这些修改,造成数据丢失。避免方案:所有数据修改必须经过Seata代理的数据源。
Seata AT模式在业务侵入性和可靠性之间取得了平衡。通过SQL拦截自动生成回滚日志,开发者只需一个注解就能获得分布式事务保障,代价是undo_log的存储开销和全局锁的性能影响。在高并发写场景下需要评估全局锁对吞吐量的影响,必要时切换为TCC模式。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot-ji-cheng-seataat-mo-shi-fen-bu-shi-shi-wu-shi/