微服务架构下为什么必然面对分布式事务
单体应用中一个本地事务就能保证数据一致性,微服务拆分后,一个业务操作跨越多个服务,每个服务有独立数据库。下单扣库存、转账扣余额、订单支付回调——这些场景都需要跨服务事务保证。这篇文章对比当前主流的四种分布式事务方案,结合Spring Boot项目给出选型建议和实战代码。
方案一:2PC两阶段提交——XA协议
XA协议是数据库层面的分布式事务标准,通过事务协调器统一控制所有参与者的提交或回滚。
Spring Boot + Atomikos配置:
// pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
@Configuration
public class XADataSourceConfig {
@Bean(name = "orderDataSource")
public DataSource orderDataSource() {
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
ds.setXaDataSourceClassName("com.mysql.cj.jdbc.MysqlXADataSource");
ds.setUniqueResourceName("orderDB");
ds.setXaProperties(Map.of(
"url", "jdbc:mysql://localhost:3306/order_db",
"user", "root",
"password", "123456"
));
ds.setPoolSize(5);
return ds;
}
@Bean(name = "inventoryDataSource")
public DataSource inventoryDataSource() {
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
ds.setXaDataSourceClassName("com.mysql.cj.jdbc.MysqlXADataSource");
ds.setUniqueResourceName("inventoryDB");
ds.setXaProperties(Map.of(
"url", "jdbc:mysql://localhost:3306/inventory_db",
"user", "root",
"password", "123456"
));
ds.setPoolSize(5);
return ds;
}
}
XA方案的局限性:数据库必须支持XA协议,协调器单点故障风险,长时间锁定资源影响吞吐。适合对一致性要求极高、并发不高的场景。
方案二:TCC(Try-Confirm-Cancel)模式
TCC是业务层面的分布式事务,把一个事务拆成三个阶段:Try预留资源、Confirm确认提交、Cancel取消释放。
以扣库存为例:
@Service
public class InventoryTccService {
@Autowired
private InventoryMapper inventoryMapper;
// Try:冻结库存
@Transactional
public boolean tryDeduct(String orderId, String sku, int quantity) {
int rows = inventoryMapper.freezeStock(sku, quantity);
if (rows == 0) return false; // 库存不足
// 记录事务日志
transactionLogMapper.insert(new TransactionLog(
orderId, sku, quantity, "TRYING"
));
return true;
}
// Confirm:确认扣减
@Transactional
public void confirm(String orderId) {
TransactionLog log = transactionLogMapper.selectByOrderId(orderId);
inventoryMapper.confirmDeduct(log.getSku(), log.getQuantity());
transactionLogMapper.updateStatus(orderId, "CONFIRMED");
}
// Cancel:释放冻结库存
@Transactional
public void cancel(String orderId) {
TransactionLog log = transactionLogMapper.selectByOrderId(orderId);
inventoryMapper.unfreezeStock(log.getSku(), log.getQuantity());
transactionLogMapper.updateStatus(orderId, "CANCELLED");
}
}
TCC的优势:不依赖数据库XA支持,资源锁定时间短。代价是业务代码侵入性强,每个操作都要写三个方法。
方案三:Seata框架AT模式
Seata的AT模式是当前微服务架构中最流行的分布式事务方案,自动生成回滚日志,对业务代码零侵入。
Spring Boot集成:
// pom.xml
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.8.0</version>
</dependency>
# application.yml
seata:
tx-service-group: order-tx-group
service:
vgroup-mapping:
order-tx-group: default
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: seata
业务代码只需加一个注解:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryClient inventoryClient;
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public void createOrder(OrderDTO dto) {
// 1. 创建订单
orderMapper.insert(new Order(dto));
// 2. 远程扣库存(Feign调用)
Result result = inventoryClient.deduct(dto.getSku(), dto.getQuantity());
if (!result.isSuccess()) {
throw new BusinessException("库存扣减失败");
// Seata自动回滚订单数据
}
}
}
Seata AT模式的核心是undo_log表,记录修改前后的数据快照,回滚时反向补偿。需要每个业务库创建这张表:
CREATE TABLE undo_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
branch_id BIGINT NOT NULL,
xid VARCHAR(100) NOT NULL,
context VARCHAR(128) NULL,
rollback_info LONGBLOB NOT NULL,
log_status INT NOT NULL,
log_created DATETIME NOT NULL,
log_modified DATETIME NOT NULL,
UNIQUE KEY ux_undo_log (xid, branch_id)
);
方案四:基于消息中间件的最终一致性
对一致性要求不绝对严格的场景,用消息队列实现最终一致性是最轻量的方案。
RocketMQ事务消息实现:
@Service
public class OrderMessageService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
public void createOrderWithMessage(OrderDTO dto) {
// 发送事务消息:本地事务+消息发送原子化
rocketMQTemplate.sendMessageInTransaction(
"order-topic",
MessageBuilder.withPayload(dto).build(),
dto
);
}
// 本地事务执行
@Transactional
public void executeLocalTransaction(Message msg, OrderDTO dto) {
orderMapper.insert(new Order(dto));
}
// 事务回查
public void checkLocalTransaction(Message msg) {
String orderId = msg.getHeaders().get("orderId", String.class);
Order order = orderMapper.selectById(orderId);
if (order != null) return CommitMessage.COMMIT;
return CommitMessage.ROLLBACK;
}
}
库存服务消费消息执行扣减,消费失败会自动重试,最终达到一致。
四种方案选型对比
– XA:强一致,低并发,数据库原生支持,适合金融核心交易
– TCC:强一致,业务侵入高,适合资源明确可预留的业务(库存、余额)
– Seata AT:准一致,零侵入,适合快速落地的通用微服务场景
– 消息最终一致:弱一致,最轻量,适合异步业务(积分、通知、日志)
没有万能方案,根据业务的一致性要求和并发规模选择。同一个系统中不同业务可以用不同方案——核心链路用Seata AT保障,旁路逻辑用消息队列异步处理。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot-wei-fu-wu-jia-gou-zhong-fen-bu-shi-shi-wu-de-si/