在微服务架构中,业务功能被拆分到多个独立的服务中,每个服务拥有自己的数据库。这种架构带来了诸多好处,但也引入了分布式事务的挑战。如何在保证数据一致性的同时保持系统的高可用和性能,是每个微服务开发者都需要面对的问题。
分布式事务的挑战
ACID vs BASE
传统单体应用中,我们可以依赖数据库的 ACID 特性来保证事务的一致性。但在分布式环境中,每个服务独立运行,无法通过数据库事务来保证跨服务的操作一致性。
分布式系统面临以下核心挑战:
网络不可靠:服务之间通过网络通信,可能存在延迟、丢包、分区等网络问题,导致操作无法完成。
部分失败:多个服务的操作中,可能部分成功、部分失败,如何处理这种不一致状态是一个难题。
数据一致性:在分布式环境下,强一致性会带来性能代价,弱一致性又会影响业务正确性,需要在两者之间找到平衡。
两阶段提交(2PC)
两阶段提交是最经典的分布式事务协议,由一个协调者和多个参与者组成。
工作原理
准备阶段:协调者向所有参与者发送准备请求,参与者执行事务操作但暂不提交,如果可以提交则返回"准备就绪",否则返回"准备失败"。
提交阶段:如果所有参与者都返回准备就绪,协调者发送提交请求,参与者提交事务;如果有任何参与者返回失败,协调者发送回滚请求,参与者回滚事务。
优缺点
优点:能够保证强一致性,理论成熟。
缺点:
- 协调者是单点故障
- 阻塞协议,在故障情况下会长时间占用资源
- 性能较差,需要多轮网络通信
- 不适合互联网大规模场景
三阶段提交(3PC)
三阶段提交是对 2PC 的改进,引入了超时机制,减少阻塞时间。
工作原理
CanCommit 阶段:协调者询问参与者是否可以提交,参与者基于本地条件返回是或否。
PreCommit 阶段:如果可以提交,参与者预执行事务并记录日志。
DoCommit 阶段:协调者根据预执行结果决定最终提交或回滚。
优缺点
优点:减少了阻塞时间,解决了协调者单点导致的长时间阻塞问题。
缺点:仍然无法解决网络分区时的数据不一致问题,性能提升有限,实际应用较少。
本地消息表
本地消息表是一种最终一致性方案,通过将消息与本地事务绑定,保证消息的可靠投递。
实现方案
服务 A:在本地事务中,同时执行业务操作和插入消息到消息表,保证原子性。
消息投递:定时任务扫描消息表,将未发送的消息投递到消息队列。
服务 B:从消息队列消费消息,执行业务操作,保证幂等性。
消息确认:服务 A 收到确认后,标记消息为已处理。
代码示例
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
// 1. 执行业务操作
orderMapper.insert(order);
// 2. 插入消息到本地消息表
Message message = new Message();
message.setTopic("order.created");
message.setContent(JSON.toJSONString(order));
message.setStatus(MessageStatus.PENDING);
messageMapper.insert(message);
}
}
// 定时任务投递消息
@Scheduled(fixedDelay = 5000)
public void sendPendingMessages() {
List<Message> messages = messageMapper.selectPendingMessages();
for (Message message : messages) {
try {
messageQueue.send(message);
messageMapper.markAsSent(message.getId());
} catch (Exception e) {
log.error("消息投递失败", e);
}
}
}
优缺点
优点:
- 实现简单,不需要第三方分布式事务框架
- 保证消息的可靠投递
- 最终一致性
缺点:
- 需要额外的消息表
- 需要实现定时任务
- 无法保证实时一致性
TCC 补偿机制
TCC(Try-Confirm-Cancel)是一种应用层的补偿型事务协议,通过三个阶段保证最终一致性。
工作原理
Try 阶段:尝试执行,完成资源的检查和预留。
Confirm 阶段:确认执行,使用预留的资源完成业务操作。
Cancel 阶段:取消操作,释放预留的资源。
代码示例
public interface PaymentService {
// Try 阶段:冻结金额
@Transactional
boolean tryPayment(String userId, BigDecimal amount);
// Confirm 阶段:扣款
@Transactional
boolean confirmPayment(String userId, BigDecimal amount);
// Cancel 阶段:释放冻结金额
@Transactional
boolean cancelPayment(String userId, BigDecimal amount);
}
@Service
public class PaymentServiceImpl implements PaymentService {
@Override
public boolean tryPayment(String userId, BigDecimal amount) {
// 检查余额是否充足
UserAccount account = accountMapper.selectByUserId(userId);
if (account.getBalance().compareTo(amount) < 0) {
return false;
}
// 冻结金额
account.setFrozenAmount(account.getFrozenAmount().add(amount));
accountMapper.update(account);
return true;
}
@Override
public boolean confirmPayment(String userId, BigDecimal amount) {
// 扣款
UserAccount account = accountMapper.selectByUserId(userId);
account.setBalance(account.getBalance().subtract(amount));
account.setFrozenAmount(account.getFrozenAmount().subtract(amount));
accountMapper.update(account);
return true;
}
@Override
public boolean cancelPayment(String userId, BigDecimal amount) {
// 释放冻结金额
UserAccount account = accountMapper.selectByUserId(userId);
account.setFrozenAmount(account.getFrozenAmount().subtract(amount));
accountMapper.update(account);
return true;
}
}
优缺点
优点:
- 性能较好,不依赖数据库锁
- 适用范围广,可以处理复杂的业务场景
缺点:
- 代码侵入性强,每个业务方法都需要编写三个方法
- 容易出现悬挂、空回滚、幂等问题
- 开发和调试复杂
Saga 模式
Saga 模式将长事务拆分为多个本地事务,每个本地事务都有对应的补偿操作。
协同式 Saga
由协调者协调各个服务的执行顺序和补偿操作。
public class OrderSagaOrchestrator {
public void executeOrder(Order order) {
try {
// 步骤1:创建订单
orderService.createOrder(order);
// 步骤2:扣减库存
inventoryService.deductStock(order.getProductId(), order.getQuantity());
// 步骤3:支付
paymentService.charge(order.getUserId(), order.getAmount());
} catch (Exception e) {
// 补偿操作:逆序执行补偿
try {
paymentService.refund(order.getUserId(), order.getAmount());
} catch (Exception ex) {
log.error("支付补偿失败", ex);
}
try {
inventoryService.restoreStock(order.getProductId(), order.getQuantity());
} catch (Exception ex) {
log.error("库存补偿失败", ex);
}
try {
orderService.cancelOrder(order.getId());
} catch (Exception ex) {
log.error("订单补偿失败", ex);
}
throw e;
}
}
}
编排式 Saga
每个服务通过事件驱动的方式,自行决定下一步操作和补偿逻辑。
@Service
public class OrderService {
@EventHandler
public void handlePaymentCompleted(PaymentCompletedEvent event) {
// 支付成功,更新订单状态
orderMapper.updateStatus(event.getOrderId(), OrderStatus.PAID);
// 发布事件触发下一步
eventPublisher.publish(new OrderPaidEvent(event.getOrderId()));
}
@EventHandler
public void handlePaymentFailed(PaymentFailedEvent event) {
// 支付失败,取消订单
orderMapper.updateStatus(event.getOrderId(), OrderStatus.CANCELLED);
// 发布事件触发补偿
eventPublisher.publish(new OrderCancelledEvent(event.getOrderId()));
}
}
优缺点
优点:
- 灵活性高,可以处理复杂的业务流程
- 性能较好,不阻塞业务
- 适合长事务场景
缺点:
- 补偿逻辑复杂,难以保证正确性
- 可能出现中间状态,需要额外的状态管理
- 缺乏标准化的实现框架
Seata 框架实践
Seata 是阿里巴巴开源的分布式事务框架,支持多种模式。
AT 模式
AT 模式是 Seata 的默认模式,通过自动记录 SQL 的执行日志,实现自动回滚。
# application.yml
seata:
enabled: true
application-id: order-service
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_tx_group: default
grouplist:
default: 127.0.0.1:8091
@Service
public class OrderService {
@GlobalTransactional
public void createOrder(Order order) {
// 业务操作
orderMapper.insert(order);
inventoryService.deductStock(order.getProductId(), order.getQuantity());
paymentService.charge(order.getUserId(), order.getAmount());
}
}
TCC 模式
Seata 也支持 TCC 模式,需要业务方实现三个方法。
@LocalTCC
public interface PaymentTccAction {
@TwoPhaseBusinessAction(name = "paymentTccAction", commitMethod = "commit", rollbackMethod = "rollback")
boolean prepare(BusinessActionContext context, @BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "amount") BigDecimal amount);
boolean commit(BusinessActionContext context);
boolean rollback(BusinessActionContext context);
}
优缺点
优点:
- 开发简单,对业务代码侵入少
- 支持多种模式,灵活度高
- 社区活跃,文档完善
缺点:
- AT 模式依赖数据库连接池,有一定性能开销
- 需要部署 TC 服务器
- 版本迭代较快,稳定性需要验证
最佳实践建议
1. 避免分布式事务
最好的分布式事务是不需要分布式事务。在系统设计时,尽可能将相关功能聚合到同一个服务中,减少跨服务调用。
2. 选择合适的方案
根据业务场景选择合适的一致性方案:
强一致性场景:如银行转账,考虑 TCC 或 Seata AT 模式
最终一致性场景:如电商订单,考虑本地消息表或 Saga 模式
高并发场景:如秒杀,考虑异步化+重试机制
3. 幂等性设计
任何分布式事务方案都必须保证幂等性。通过唯一 ID、状态机、数据库唯一索引等方式确保重复操作不会产生副作用。
public interface IdempotentService {
@Idempotent(key = "#id", expire = 600)
Result execute(String id, Request request);
}
4. 监控和告警
建立完善的监控体系,监控事务的成功率、延迟、失败率等指标,及时发现和处理问题。
# Prometheus 配置
monitor_rules:
- name: transaction_success_rate
query: rate(seata_transaction_total{status="success"}[5m])
alert:
when: below 0.95
message: "事务成功率低于95%"
5. 幂等性实现
使用 Redis 或数据库实现分布式锁,防止重复执行:
public class IdempotentHelper {
private RedisTemplate<String, String> redisTemplate;
public boolean tryLock(String key, long expireSeconds) {
return Boolean.TRUE.equals(
redisTemplate.opsForValue()
.setIfAbsent(key, "1", expireSeconds, TimeUnit.SECONDS)
);
}
public void unlock(String key) {
redisTemplate.delete(key);
}
}
总结
分布式事务是微服务架构中的核心挑战之一。不同的场景需要选择不同的方案:
2PC/3PC:理论完善,但性能差,不适合大规模互联网场景
本地消息表:实现简单,保证最终一致性,适合消息驱动的业务
TCC:性能好,但开发复杂,适合对性能要求高的核心业务
Saga:灵活性高,适合长事务和复杂业务流程
Seata:对业务侵入少,快速上手,适合大多数中小型项目
在实际项目中,建议从业务需求出发,综合考虑一致性要求、性能指标、开发成本等因素,选择最合适的方案。同时,无论选择哪种方案,都要做好幂等性设计、监控告警和异常处理,确保系统的稳定性和可靠性。
参考资料
- Seata 官方文档: https://seata.io/zh-cn/
- “Microservices Patterns” by Chris Richardson
- “Designing Data-Intensive Applications” by Martin Kleppmann
- 分布式事务最佳实践: https://martinfowler.com/articles/patterns-of-distributed-systems/