微服务架构下如何保证数据一致性

公司新产品供应链平台基于Saas的多租户模式设计,采用微服务架构。在前期技术架构选型、基础方案设计的过程中,我就一直在考虑如何保证在微服务架构下的数据一致性。

背景

数据一致性深受重视的原因主要是受老系统的影响。老系统采用单体架构设计,但作为Saas模式提供服务,一个服务集群为几十个仓库提供服务;经过多年持续迭代,内部业务逻辑耦合度极高,作为业务下游,不仅要为上游ERP提供方案兜底,还要为不同的客户提供定制化服务;在公司降本增效的前提下,服务器资源冗余度较低;当然还存在其他原因。在这种情况下,老系统经常出现数据状态不一致的问题。

新产品采用微服务架构设计,结合CAPBASE理论,微服务架构下更无法避免数据一致性的问题。因此,需要更加完整的方案解决这方面的问题。

强一致性和最终一致性

数据一致性有两种解决方案,强一致性最终一致性 。在传统的支持事务的数据库中,同一数据源,可利用事务解决;多个数据源,可利用XA 两阶段提交 达到强一致性,或重试的方式达到最终一致性。

常见解决方案

XA两阶段提交

XA协议

XA协议规范了DTP分布式事务的模型,该模型定义如下角色:

AP(Application Program):应用程序,可理解为调用分布式事务的应用程序。

TM(Resource Manager):事务管理器,负责协调和管理事务,事务管理器控制全局事务,管理事务生命周期。全局事务是指分布式事务处理环境中,需要操作多个数据库共同完成一个工作,这个工作即是一个全局事务。

RM(Transaction Manager):资源管理器,可以理解为事务的参与者,一般情况是指一个数据库实例,控制分支事务。

两阶段提交

两阶段提交是将整个事务(全局事务)分为两个阶段,准备阶段(Prepare phase)、提交阶段(commit phase)。

准备阶段

事务管理器给每个参与者发送Prepare消息,每个数据库参与者在本地执行事务,并写本地Undo/Redo日志,此时事务并未提交。

提交阶段

准备阶段全部成功,事务管理器给每个参与者发送提交(Commit)消息;如果有任一参与者执行失败,则发送(Rollback)消息。

注意:最后阶段才释放锁资源。

TCC模式

TCC(Try-Confirm-Cancel)是一种分布式事务解决方案,通过在业务逻辑中嵌入Try-Confirm-Cancel三个阶段,保证事务的一致性。

  • Try阶段:业务条件判断逻辑检查,预占资源。
  • Confirm阶段:业务逻辑确认执行结果,确认通过则提交操作,否则回滚操作。
  • Cancel阶段:业务逻辑撤销之前执行的操作,释放预占资源。

SAGA模式

Saga模式的一些关键特性:

  • 每个步骤都是原子性的本地事务,可以独立执行,也可以回滚。
  • 每个步骤都记录其状态以便于回滚或提交。
  • 每个步骤都需要明确的指定它的补偿操作,以便在出现故障时可以回滚之前的步骤。
  • 如果某个步骤失败,这需要启动相应的补偿操作以回滚之前的步骤。

综合分析

XA两阶段提交

  • 优点:使用简单,可以像使用本地事务一样使用基于XA的分布式事务,对业务侵入小。
  • 缺点:基于强一致性的同步阻塞协议,锁资源持有时间长,对并发和吞吐量影响较大。且在事务管理器或资源管理器出现连接中断或执行超时的情况下,仍可能导致数据不一致。

TCC模式

对业务侵入大,需要将正常的业务逻辑拆分为三个阶段;一次正常执行分为两个阶段执行,即预占资源和提交两个阶段(Confirm阶段用于异常补偿);业务逻辑中预占资源,对业务状态管理要求高。对开发成本、管理成本及性能均有较大影响。

SAGA模式

每个步骤都需要明确指定其补偿操作,管理成本增加。 相比于TCC模式,SAGA模式性能较好。

总结

XA两阶段提交 性能较差,无法接受;TCC模式 个人来看真的是在用创造问题的方式解决问题;SAGA模式性能损失较小,可接受。但仍存在几个问题:一是每个步骤需明确指定其补偿操作,开发成本和管理成本增加;二是从补偿操作(业务逻辑回滚)来看,其过程仍存在分布式环境下的数据一致性问题,即如果有多个步骤需要补偿,需要保证多个补偿步骤均能成功执行。

最终解决方案

综上来看,XA两阶段提交、TCC模式、SAGA模式在开发、管理、性能等方面均存在一定的问题。结合我司业务特性,最终采用的解决方案:通过重试达到最终一致性

优势

  • 无性能损失。
  • 将业务隔离为正向流程和逆向流程,正向流程为正常业务逻辑,逆向流程可视为补偿操作,例如取消流程。将正向流程和逆向流程隔离,业务逻辑清晰,降低开发和管理成本。
  • 业务逻辑保证幂等,执行过程异常可通过人工或系统重试,或执行逆向流程。
  • 在分布式环境中,接口执行超时重试是极其有效的解决方案,通过重试达到最终一致性与该设计目标是一致的。

注意:在分布式环境中应尽量保证接口幂等

异常处理

通过重试达到最终一致性,需要解决几个问题:一是异常处理;二是接口幂等。

异常处理

业务逻辑执行异常,需要通过人工或系统重试。对于业务系统来说,需要显式标识业务逻辑异常状态并引导人工重试。 接口调用场景如下:

场景1:

  1. 开启本地事务
  2. 本地业务逻辑执行
  3. 执行RPC命令
  4. 提交本地事务

场景2:

  1. 开启本地事务
  2. 执行RPC命令
  3. 本地业务逻辑执行
  4. 提交本地事务

场景3:

  1. 开启本地事务
  2. 本地业务逻辑执行
  3. 执行RPC命令1
  4. 执行RPC命令2
  5. ...
  6. 提交本地事务

场景4:

  1. 开启本地事务
  2. 本地业务中间状态更新
  3. 提交本地事务
  4. 执行RPC命令1
  5. 执行RPC命令2
  6. ...
  7. 开启本地事务
  8. 本地业务完成状态更新
  9. 提交本地事务

场景1:本地业务逻辑执行一般涉及状态变更,步骤2和步骤3执行异常触发事务回滚,一般情况下不会产生副作用(需要注意步骤3执行超时而最终执行成功的问题)

场景2:如果步骤3执行异常,步骤2已执行的命令无法回滚。

步骤3:如果步骤4执行异常,同样步骤3已的命令无法回滚。

步骤4:加入中间状态,重试幂等保证最终一致性,不产生副作用。同时可结合逆向流程设计。

综合上述场景,针对简单、非核心的业务场景,考虑到开发和管理成本,可采用场景1的执行方式;针对重要的、复杂的业务场景,可采用场景4的执行方式。

接口幂等

默认情况下,接口应设计为支持幂等,例如dubbo默认在接口执行超时的情况下,会自动重试(可配置重试次数),如不支持幂等,可能会产生副作用。考虑到开发和管理成本,我们采用三种方式结合的方式保证幂等:一是唯一索引;二是状态;三是幂等表。

唯一索引 :防止实体重复创建,特别是在重试场景下。
状态 :一般情况下,业务性较强的场景,可采用状态+乐观锁(防止并发)的方式保证幂等。这种方式简单、直观,开发成本低。
幂等表:用于解决无法通过状态保证幂等的场景,例如库存调整。库存一般作为独立的服务提供接口,对外提供封装的库存调整接口,库存调整封装的命令是无状态的,此时需要在参数中封装业务唯一标识保证幂等。

相关推荐
Re2751 小时前
揭秘索引的 “快”:从翻书到 B+ 树的效率革命
后端
David爱编程2 小时前
Java 三目运算符完全指南:写法、坑点与最佳实践
java·后端
学习编程的小羊3 小时前
Spring Boot 全局异常处理与日志监控实战
java·spring boot·后端
Moonbit4 小时前
MoonBit 作者寄语 2025 级清华深圳新生
前端·后端·程序员
前端的阶梯4 小时前
开发一个支持支付功能的微信小程序的注意事项,含泪送上
前端·后端·全栈
咕噜分发企业签名APP加固彭于晏4 小时前
腾讯元器的优点是什么
前端·后端
AAA修煤气灶刘哥5 小时前
Swagger 用着糟心?试试 Knife4j,后端开发狂喜
后端·面试
bobz9655 小时前
MCP on windows
后端
泡海椒5 小时前
jquickexcel 全功能指南:从数据导入到精美导出的完整流程
后端
iOS开发上架哦6 小时前
移动端网页调试实战,键盘弹出与视口错位问题的定位与优化
后端