1. 简介

本文将探讨在微服务架构中实现跨服务事务的可行方案,并分析分布式场景下事务处理的替代方案。

2. 避免跨微服务事务

分布式事务涉及众多协作组件,失败点极多。当这些组件部署在不同机器甚至数据中心时,事务提交过程可能变得漫长且不可靠。这会严重影响用户体验和系统吞吐量。因此解决分布式事务的最佳方式之一就是彻底避免它。

2.1. 需要事务的架构示例

良好的微服务设计应保持服务独立性和原子性,即每个服务都能独立完成特定的业务任务。如果系统按此原则拆分,大概率不需要实现跨服务事务。

以用户广播消息系统为例:

用户微服务 负责用户资料管理(创建用户、编辑资料等),核心领域模型如下:

@Entity
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Basic
    private String name;

    @Basic
    private String surname;

    @Basic
    private Instant lastMessageTime;
}

消息微服务 负责广播功能,封装了 Message 实体及相关逻辑:

@Entity
public class Message implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Basic
    private long userId;

    @Basic
    private String contents;

    @Basic
    private Instant messageTimestamp;

}

每个微服务拥有独立数据库。注意 Message 实体中不直接引用 User 实体(因为消息微服务无法访问用户类),而是通过 userId 关联。

但问题在于:User 实体包含 lastMessageTime 字段,用于在用户资料中展示最后活动时间。当添加新消息并更新该字段时,就需要跨微服务事务。

2.2. 无事务的替代方案

我们可以重构架构,从 User 实体中移除 lastMessageTime 字段。在用户资料页中,改为向消息微服务单独发起请求,获取该用户所有消息的最大时间戳。

⚠️ 代价:当消息微服务高负载或宕机时,用户资料页可能无法显示最后消息时间。但这远优于因用户微服务响应超时导致整个消息保存失败的分布式事务问题。

当然,某些复杂业务流程必须跨多微服务实现,且需要强一致性时,就需要其他方案。

3. 两阶段提交协议

两阶段提交协议(2PC)是实现跨组件事务的机制,适用于多个数据库、消息队列等场景。

3.1. 2PC 架构原理

分布式事务的核心是事务协调器,包含两个阶段:

  1. 准备阶段:所有参与者准备提交,并向协调器确认就绪状态
  2. 提交/回滚阶段:协调器向所有参与者下达提交或回滚指令

核心痛点:2PC 相比单服务操作极其缓慢。即使微服务在同一网络,协调开销也会显著拖慢系统性能,高负载场景基本不适用。

3.2. XA 标准

XA 标准是 2PC 分布式事务的规范,所有 JTA 兼容的应用服务器(JBoss、GlassFish 等)都原生支持。

典型场景:两个微服务的不同数据库参与分布式事务。但该机制要求所有资源部署在单一 JTA 平台,这与微服务架构的分布式特性相悖,可行性较低。

3.3. REST-AT 标准草案

REST-AT 是 RedHat 提出的草案标准,WildFly 应用服务器已支持。它允许应用服务器作为事务协调器,通过特定 REST API 创建和加入分布式事务。

但问题在于:要桥接分布式事务与微服务本地资源,仍需将资源部署到单一 JTA 平台,或自行开发复杂的桥接逻辑——这显然是踩坑行为。

4. 最终一致性与补偿机制

**目前最可行的跨微服务一致性方案是最终一致性**。它不强制跨微服务的 ACID 事务,而是通过机制保证系统在未来某个时间点达到一致状态。

4.1. 最终一致性适用场景

假设需要实现:

  1. 注册用户资料
  2. 执行后台自动校验(如检查用户是否被封禁)

校验可能耗时较长,适合拆分为独立微服务。让用户等待校验完成才确认注册显然不合理。

解决方案:消息驱动 + 补偿机制,架构如下:

  • 用户微服务:处理用户注册
  • 校验微服务:执行后台检查
  • 消息平台:支持持久化队列

消息平台确保消息可持久化,当接收方不可用时能延迟投递。

4.2. 正常流程

理想执行路径

  1. 用户微服务注册用户,保存到本地数据库
  2. 标记用户为"待校验"状态(限制部分功能)
  3. 向用户发送注册成功通知,提示部分功能暂不可用
  4. 发送校验请求到校验微服务
  5. 校验微服务执行检查后,向用户微服务返回结果:
    • 校验通过:解除用户功能限制
    • 校验失败:删除用户账户

最后一步删除无效账户的操作就是补偿阶段。系统经过这些步骤后达到一致状态,但期间用户数据会短暂处于不一致状态。

4.3. 故障场景

⚠️ 故障处理策略

  • 校验微服务不可用:消息平台通过持久化队列延迟投递
  • 消息平台故障:用户微服务通过定时任务重试(扫描待校验用户)
  • 校验微服务无法返回结果:校验微服务自身重试发送结果
  • 消息丢失:用户微服务定时任务扫描待校验用户,重新发起校验

即使消息重复发送,也不会影响数据库一致性。通过周密设计故障处理机制,可确保系统满足最终一致性,同时避免昂贵的分布式事务

但需注意:实现最终一致性是复杂任务,没有通用解决方案,需根据具体场景设计。

5. 总结

本文探讨了跨微服务事务的实现机制,并分析了替代方案。核心结论是:

  1. 优先避免跨服务事务(通过架构重构)
  2. 2PC 等传统方案性能差,不适用高负载场景
  3. 最终一致性 + 补偿机制是当前最可行的方案,但需精心设计故障处理逻辑

在微服务架构中,简单粗暴地追求强一致性往往得不偿失,最终一致性才是更务实的选择。


原始标题:Transactions Across Microservices