微服务拆分后的事务一致性难题
单体应用中,一个数据库事务(BEGIN到COMMIT)就能保证数据一致性。微服务拆分后,一个业务操作跨越多个服务和多个数据库,本地事务无法覆盖全局。订单创建涉及订单服务、库存服务、支付服务,任何一个环节失败都需要回滚全部操作,这就是分布式事务的核心挑战。
分布式事务没有银弹方案。CAP定理决定了在网络分区时,一致性和可用性只能选一个。实际工程中采用BASE理论——基本可用、软状态、最终一致性,在业务可接受的时间窗口内达成一致。
Seata AT模式:对业务代码侵入最小的方案
Seata是阿里开源的分布式事务框架,AT模式是其最易用的方案。AT模式的核心机制是”两阶段提交+全局锁”:一阶段执行业务SQL并记录回滚日志(undo_log),二阶段根据全局事务结果决定提交(删除undo_log)或回滚(执行反向SQL)。
工程集成步骤:
<!-- 1. 引入Seata依赖 -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.8.0</version>
</dependency>
<!-- 2. 每个参与方数据库创建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_modified` DATETIME NOT NULL,
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE=InnoDB;
// 3. 业务代码加@GlobalTransactional注解
@GlobalTransactional(timeoutMills = 60000, name = "create-order")
public OrderResult createOrder(OrderRequest request) {
orderService.create(request);
inventoryService.deduct(request.getSkuId(), request.getQuantity());
paymentService.freeze(request.getUserId(), request.getAmount());
return OrderResult.success();
}
AT模式的优势是对业务代码几乎零侵入(只需加注解),劣势是全局锁机制在高并发场景下性能下降明显。全局锁会锁定参与方的数据行直到全局事务完成,如果事务耗时较长(比如支付服务响应慢),库存行被长时间锁定,其他事务只能等待。实测数据:单表1000 TPS时,全局锁争用导致TPS下降到300-400。
Saga模式:长事务场景下的替代方案
Saga模式将长事务拆分为多个本地事务,每个本地事务提交后执行下一步,任一环节失败则反向执行补偿操作。没有全局锁,可用性更高,但只有最终一致性。
Seata的Saga模式通过状态机定义事务流程:
{
"Name": "createOrderSaga",
"Comment": "订单创建Saga流程",
"StartState": "CreateOrder",
"States": {
"CreateOrder": {
"Type": "ServiceTask",
"ServiceName": "orderService",
"ServiceMethod": "create",
"CompensateState": "CancelOrder",
"Next": "DeductInventory",
"Input": ["$.request"],
"Output": { "orderId": "$.createResult.id" }
},
"DeductInventory": {
"Type": "ServiceTask",
"ServiceName": "inventoryService",
"ServiceMethod": "deduct",
"CompensateState": "RestoreInventory",
"Next": "FreezePayment",
"Input": ["$.request.skuId", "$.request.quantity"]
},
"FreezePayment": {
"Type": "ServiceTask",
"ServiceName": "paymentService",
"ServiceMethod": "freeze",
"CompensateState": "UnfreezePayment",
"Input": ["$.request.userId", "$.request.amount"],
"IsEnd": true
},
"CancelOrder": { "Type": "ServiceTask", "ServiceName": "orderService", "ServiceMethod": "cancel" },
"RestoreInventory": { "Type": "ServiceTask", "ServiceName": "inventoryService", "ServiceMethod": "restore" },
"UnfreezePayment": { "Type": "ServiceTask", "ServiceName": "paymentService", "ServiceMethod": "unfreeze" }
}
}
Saga模式的优势是无需全局锁,各本地事务独立提交,性能好。劣势是补偿操作必须幂等(可能被执行多次),且业务侧需要为每个正向操作编写补偿逻辑,开发量约为AT模式的2倍。
本地消息表+最终一致性:最可靠的兜底方案
本地消息表方案的核心思路是:业务操作和消息写入在同一个本地事务中完成,保证”业务执行成功则消息一定存在”,再由异步任务保证消息投递到下游。
// 1. 本地消息表结构
CREATE TABLE `outbox_message` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`topic` VARCHAR(64) NOT NULL,
`key` VARCHAR(128) NOT NULL,
`body` TEXT NOT NULL,
`status` TINYINT DEFAULT 0,
`retry_count` INT DEFAULT 0,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX `idx_status_created` (`status`, `created_at`)
) ENGINE=InnoDB;
// 2. 业务方法:同一事务中写入业务数据和消息
@Transactional
public void createOrderWithMessage(OrderRequest request) {
Order order = orderMapper.insert(request);
OutboxMessage msg = new OutboxMessage();
msg.setTopic("order-created");
msg.setKey(String.valueOf(order.getId()));
msg.setBody(JSON.toJSONString(order));
outboxMapper.insert(msg);
}
// 3. 定时任务扫描未发送消息并投递
@Scheduled(fixedDelay = 1000)
public void sendOutboxMessages() {
List<OutboxMessage> messages = outboxMapper.selectPending(100);
for (OutboxMessage msg : messages) {
try {
mqProducer.send(msg.getTopic(), msg.getKey(), msg.getBody());
outboxMapper.updateStatus(msg.getId(), 1);
} catch (Exception e) {
outboxMapper.incrementRetry(msg.getId());
if (msg.getRetryCount() >= 5) {
outboxMapper.updateStatus(msg.getId(), 2);
}
}
}
}
本地消息表方案没有全局锁,没有事务协调器单点,最坏情况下消息延迟但不会丢失。适合对实时性要求不高但可靠性要求极高的场景,如金融交易、库存扣减。
三种方案的选型决策矩阵
根据业务特征选择方案:
Seata AT模式:适合短事务(小于5秒)、强一致性要求高、并发量中等(小于500 TPS)的场景。典型应用:订单+库存扣减、转账交易。优点是代码侵入低,缺点是全局锁限制并发。
Saga模式:适合长事务(大于5秒)、允许最终一致性、流程步骤多的场景。典型应用:旅游订单(机票+酒店+租车)、供应链审批流。优点是性能好无全局锁,缺点是补偿逻辑开发量大。
本地消息表:适合跨系统异步通知、对可靠性要求极高但对实时性要求不高的场景。典型应用:支付结果通知、库存同步到搜索引擎。优点是最可靠,缺点是延迟高(秒级到分钟级)。
实际项目中,三种方案经常组合使用:核心交易链路用Seata AT保证强一致,审批流用Saga,跨系统通知用本地消息表。分布式事务方案的选择不是技术偏好问题,而是业务特征的匹配问题。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/wei-fu-wu-jia-gou-xia-fen-bu-shi-shi-wu-jie-jue-fang-an-shi/