分布式事务的核心挑战
微服务架构下,一个业务操作可能横跨多个服务,每个服务维护独立数据库。传统的本地事务无法保证跨服务的数据一致性。例如电商下单流程涉及订单服务创建订单、库存服务扣减库存、支付服务处理支付,任何一个环节失败都需要回滚所有操作。
Seata是阿里巴巴开源的分布式事务解决方案,提供AT、TCC、SAGA、XA四种事务模式。AT模式(Automatic Transaction)对业务代码侵入最小,通过自动生成补偿SQL实现回滚,适合快速接入已有项目。TCC模式需要业务层实现Try、Confirm、Cancel三个接口,性能更高但开发成本也更高。
Seata Server部署与配置
Seata架构包含三个角色:TC(Transaction Coordinator)事务协调器、TM(Transaction Manager)事务管理器、RM(Resource Manager)资源管理器。TC作为独立服务部署,TM和RM以SDK形式集成在应用中。
# docker-compose部署Seata Server
# docker-compose.yml
version: '3'
services:
seata-server:
image: seataio/seata-server:1.7.0
ports:
- "8091:8091"
- "7091:7091"
environment:
- SEATA_IP=192.168.1.100
- SEATA_PORT=8091
- STORE_MODE=db
- SEATA_CONFIG_NAME=file:/root/seata-config/registry
volumes:
- ./seata-config:/root/seata-config
- ./seata-logs:/root/logs
Seata Server使用数据库存储事务日志,配置文件如下:
# registry.conf
registry {
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "192.168.1.50:8848"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
}
}
config {
type = "file"
file {
name = "file:/root/seata-config/file.conf"
}
}
# file.conf
store {
mode = "db"
db {
datasource = "druid"
dbType = "mysql"
driverClassName = "com.mysql.cj.jdbc.Driver"
url = "jdbc:mysql://192.168.1.60:3306/seata?useUnicode=true&rewriteBatchedStatements=true"
user = "seata"
password = "seata_password"
minConn = 5
maxConn = 30
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
}
}
service {
vgroupMapping.eCommerce_tx_group = "default"
default.grouplist = "192.168.1.100:8091"
enableDegrade = false
disableGlobalTransaction = false
}
Spring Boot集成Seata AT模式
AT模式的工作原理分两个阶段。一阶段:拦截业务SQL,生成前后镜像数据写入undo_log表,本地事务提交,释放数据库锁。二阶段:如果全局事务提交,异步删除undo_log;如果全局事务回滚,根据undo_log生成反向SQL补偿回滚数据。
<!-- pom.xml 依赖配置 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2022.0.0.0-RC2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.20</version>
</dependency>
# application.yml
seata:
enabled: true
application-id: order-service
tx-service-group: eCommerce_tx_group
service:
vgroup-mapping:
eCommerce_tx_group: default
grouplist:
default: 192.168.1.100:8091
registry:
type: nacos
nacos:
server-addr: 192.168.1.50:8848
group: SEATA_GROUP
config:
type: file
data-source-proxy-mode: AT
client:
rm:
report-success-enable: true
table-meta-check-enable: true
tm:
commit-retry-count: 3
rollback-retry-count: 3
default-global-transaction-timeout: 60000
业务代码中通过@GlobalTransactional注解开启全局事务:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@DubboReference
private InventoryService inventoryService;
@DubboReference
private PaymentService paymentService;
@GlobalTransactional(name = "createOrder", rollbackFor = Exception.class)
public Order createOrder(OrderDTO dto) {
// 1. 创建订单
Order order = new Order();
order.setOrderNo(generateOrderNo());
order.setUserId(dto.getUserId());
order.setProductId(dto.getProductId());
order.setQuantity(dto.getQuantity());
order.setAmount(dto.getAmount());
order.setStatus("CREATED");
orderMapper.insert(order);
// 2. 扣减库存(远程调用库存服务)
inventoryService.deduct(dto.getProductId(), dto.getQuantity());
// 3. 处理支付(远程调用支付服务)
paymentService.pay(order.getOrderNo(), dto.getAmount());
// 4. 更新订单状态
order.setStatus("PAID");
orderMapper.updateStatus(order);
return order;
}
}
库存服务的扣减方法同样需要注解为分支事务。Seata通过SeataDataSourceProxy代理数据源,自动拦截SQL执行,生成undo_log:
@Service
public class InventoryServiceImpl implements InventoryService {
@Autowired
private InventoryMapper inventoryMapper;
@Override
public boolean deduct(Long productId, Integer quantity) {
Inventory inventory = inventoryMapper.selectByProductId(productId);
if (inventory.getStock() < quantity) {
throw new BusinessException("库存不足");
}
inventory.setStock(inventory.getStock() - quantity);
inventoryMapper.updateStock(inventory);
return true;
}
}
每个参与全局事务的数据库都需要创建undo_log表:
CREATE TABLE `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 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事务回滚日志表';
事务回滚机制与问题诊断
当全局事务回滚时,TC向各分支发送回滚指令,RM根据undo_log中的beforeImage生成反向SQL执行回滚。回滚过程中需要校验当前数据是否与afterImage一致,如果不一致说明数据已被其他事务修改,需要人工介入。
常见问题排查方法:通过Seata控制台查看全局事务状态(Begin/Committing/RollingBack/RollbackFailed),检查branch_table中各分支事务的执行状态。undo_log表中的rollback_info字段以JSON格式存储了变更前后的数据快照,可用于分析回滚失败的原因。
// undo_log中的rollback_info结构示例
{
"@class": "io.seata.rm.datasource.undo.BranchUndoLog",
"xid": "192.168.1.100:8091:1234567890",
"branchId": 1234567891,
"sqlUndoLogs": [{
"sqlType": "UPDATE",
"tableName": "inventory",
"beforeImage": {
"rows": [{"fields": [
{"name": "id", "value": 1},
{"name": "product_id", "value": 100},
{"name": "stock", "value": 50}
]}]
},
"afterImage": {
"rows": [{"fields": [
{"name": "id", "value": 1},
{"name": "product_id", "value": 100},
{"name": "stock", "value": 45}
]}]
}
}]
}
全局事务超时配置default-global-transaction-timeout设置为60秒。如果业务逻辑执行时间超过此阈值,TC会自动发起回滚。对于耗时较长的业务流程,需要适当调大超时时间,或将长流程拆分为多个短事务使用SAGA模式处理。
AT模式在高并发场景下需要注意脏写问题。Seata通过全局锁机制保证分支事务的写隔离——一阶段提交前需要获取全局锁,如果目标记录已被其他全局事务锁定,当前事务需要等待或回滚。全局锁的竞争在高并发写入场景下可能成为性能瓶颈,此时应考虑使用TCC模式,将锁粒度控制到业务层面。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot-wei-fu-wu-fen-bu-shi-shi-wu-jie-jue-fang-an/