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

公司新产品供应链平台基于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默认在接口执行超时的情况下,会自动重试(可配置重试次数),如不支持幂等,可能会产生副作用。考虑到开发和管理成本,我们采用三种方式结合的方式保证幂等:一是唯一索引;二是状态;三是幂等表。

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

相关推荐
程序猿毕设源码分享网8 分钟前
基于springboot校园招聘系统源码和论文
java·spring boot·后端
山山而川粤23 分钟前
美食推荐系统|Java|SSM|JSP|
java·开发语言·后端·学习·mysql
小小药25 分钟前
012-spring的注解开发
java·后端·spring
AI人H哥会Java1 小时前
【Spring】基于XML的Spring容器配置——Bean的作用域
java·开发语言·后端·spring·架构
vvw&1 小时前
如何在 Ubuntu 22.04 上安装 Elasticsearch
linux·运维·服务器·后端·ubuntu·elasticsearch·搜索引擎
uhakadotcom2 小时前
2025年java技术发展趋势展望
java·后端·架构
xiaocaibao7772 小时前
Rust语言的数据库编程
开发语言·后端·golang
LeonNo112 小时前
golang,多个proxy拉包的处理逻辑
开发语言·后端·golang
龙少95432 小时前
【springboot中最适合用什么技术来实现在线聊天】
java·spring boot·后端
xiaocaibao7773 小时前
Bash语言的语法
开发语言·后端·golang