微服务架构中的分布式事务处理:理论与实践

2026-02-19 09:00:00 · 3 minute read

在微服务架构中,业务功能被拆分到多个独立的服务中,每个服务拥有自己的数据库。这种架构带来了诸多好处,但也引入了分布式事务的挑战。如何在保证数据一致性的同时保持系统的高可用和性能,是每个微服务开发者都需要面对的问题。

分布式事务的挑战

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);
}

优缺点

优点

缺点

最佳实践建议

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:对业务侵入少,快速上手,适合大多数中小型项目

在实际项目中,建议从业务需求出发,综合考虑一致性要求、性能指标、开发成本等因素,选择最合适的方案。同时,无论选择哪种方案,都要做好幂等性设计、监控告警和异常处理,确保系统的稳定性和可靠性。

参考资料

已复制