微服务架构下的事务困境
单体应用中,一个数据库事务就能保证数据一致性。微服务架构下,一个业务操作涉及多个服务、多个数据库,本地事务无法覆盖跨服务调用。库存扣减成功但订单创建失败,或者反过来——数据不一致在生产环境中是绝对不能接受的。
Seata是当前Java微服务生态中最成熟的分布式事务框架。AT模式(Auto Transaction)对业务代码侵入性最小,只需一个@GlobalTransactional注解,框架自动处理两阶段提交的回滚补偿。
Seata AT模式的工作原理
AT模式的核心逻辑分两阶段:
一阶段:拦截业务SQL,生成前镜像(before image)和后镜像(after image),写入undo_log表,然后执行业务SQL并提交本地事务。
二阶段:若全局事务提交,异步清理undo_log;若全局事务回滚,根据undo_log中的前镜像反向补偿(生成反向SQL),恢复数据到事务开始前的状态。
/* 一阶段执行流程(以UPDATE为例) */
-- 1. 查询前镜像
SELECT id, stock FROM product WHERE id = 1001;
-- before_image: {id: 1001, stock: 100}
-- 2. 执行业务SQL
UPDATE product SET stock = stock - 1 WHERE id = 1001;
-- 3. 查询后镜像
SELECT id, stock FROM product WHERE id = 1001;
-- after_image: {id: 1001, stock: 99}
-- 4. 写入undo_log
INSERT INTO undo_log (branch_id, xid, context, rollback_info)
VALUES (2001, 'tx-123456', 'AT',
'{"beforeImage":{"stock":100},"afterImage":{"stock":99}}');
-- 5. 提交本地事务
两阶段回滚时,Seata读取undo_log的前镜像,生成反向SQL恢复数据。
Seata Server部署与配置
Seata Server作为事务协调者(TC),独立部署:
# docker-compose部署Seata Server
version: '3.8'
services:
seata-server:
image: seataio/seata-server:2.0.0
container_name: seata-server
ports:
- "8091:8091"
- "7091:7091"
restart: unless-stopped
Seata Server配置(application.yml):
server:
port: 7091
seata:
config:
type: nacos
nacos:
server-addr: 192.168.1.50:8848
namespace: seata
group: SEATA_GROUP
registry:
type: nacos
nacos:
server-addr: 192.168.1.50:8848
namespace: seata
group: SEATA_GROUP
store:
mode: db
db:
datasource: druid
db-type: mysql
url: jdbc:mysql://192.168.1.50:3306/seata?useSSL=false
user: seata
password: seata_pwd
微服务接入Seata客户端
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
seata:
enabled: true
application-id: order-service
tx-service-group: my_test_tx_group
service:
vgroup-mapping:
my_test_tx_group: default
registry:
type: nacos
nacos:
server-addr: 192.168.1.50:8848
namespace: seata
每个参与方数据库创建undo_log表:
CREATE TABLE IF NOT EXISTS undo_log (
branch_id BIGINT NOT NULL,
xid VARCHAR(128) NOT NULL,
context VARCHAR(128) NOT NULL,
rollback_info LONGBLOB NOT NULL,
log_status INT NOT NULL,
log_created DATETIME NOT NULL,
log_expired DATETIME NOT NULL,
PRIMARY KEY (branch_id),
KEY idx_xid (xid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
业务代码:@GlobalTransactional注解实战
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockFeignClient stockClient;
@Autowired
private AccountFeignClient accountClient;
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public Order createOrder(OrderDTO orderDTO) {
Order order = new Order();
order.setUserId(orderDTO.getUserId());
order.setProductId(orderDTO.getProductId());
order.setAmount(orderDTO.getAmount());
order.setStatus("CREATED");
orderMapper.insert(order);
stockClient.deduct(orderDTO.getProductId(), orderDTO.getQuantity());
accountClient.debit(orderDTO.getUserId(), orderDTO.getAmount());
return order;
}
}
服务治理与高并发场景下的注意事项
Seata AT模式在正常场景下工作良好,但高并发写场景存在全局锁争用问题。全局锁保证跨服务数据的一致性,但代价是降低并发吞吐量。
seata:
client:
rm:
lock:
retry-interval: 10
retry-times: 30
retry-policy-branch-rollback-on-conflict: true
API接口规范建议:分布式事务只包裹必要的服务调用,不要在@GlobalTransactional内执行耗时操作(如文件上传、消息发送),这些会延长全局锁持有时间,加剧锁争用。对一致性要求不那么严格的场景,可用RocketMQ事务消息替代Seata。事务消息的吞吐量远高于Seata AT模式,但编程复杂度更高,需要实现本地事务状态的回查接口。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot-wei-fu-wu-fen-bu-shi-shi-wu-jie-jue-fang-an/