优雅实现多系统一致性补偿方案

前言

我们在开发的过程中,如果一个业务操作需要本地写MYSQL数据以及对第三方系统做写操作,那么这种流程就涉及到分布式系统一致性的问题,然而并非所有系统都能使用成熟的分布式事务方案

PS:示例代码推送到 gitee.com/dailycreate...

案例说明

以一个财务报账业务为例,涉及到的系统如下:

系统名 作用 实现方案
单据系统 申请单内容以及凭证的生成 JAVA
BPM 实现流程的运转 购买成熟系统(例如:泛微)
SAP 财务凭证 购买成熟系统
邮件服务 发送邮件通知 购买成熟系统

详细解释下各系统作用:

  1. 单据系统:财务报账,会提交很多信息(例如:报账事由i you、报账金额与明细)。同时也会生成财务凭证(不了解凭证也没关系,它就是给财务人员看的东西,对技术人员来说就是数据库的一堆数据)
  2. BPM系统:非常成熟的流程管理系统,以非常直观的方式来实现流程的搭配,不了解的可以自行百度扫盲。在此案例中,需要使用BPM的两个能力:1)调用API,审核通过 2)调用API,获取流程的待审人
  3. SAP系统:财务专用系统,不用过多了解,只要知道在财务审核完成后,会将单据系统生成的凭证数据通过API调用的方式发送给SAP即可
  4. 邮件服务:字面意思不用解释

"审核通过"业务流程

当审核人员审核通过时,大致流程如下:

  1. 保存业务数据+记录审核日志
  2. 调用BPM接口,审核通过
  3. 调用BPM接口,获取最新待审人
  4. 如果没有待审人,说明已经审完,生成凭证并推送SAP

代码如下

风险分析

如图所示,如果在1和2出现异常,由于有事务的存在,操作1内的几条mysql写操作会被回滚,因此所有数据都没有任何变化。

但如果1和2正常执行,操作3发生异常,操作1的数据会因为事务回滚,但操作2并不能。因此整个系统会出现一个很诡异的现象:单据系统内,没有任何日志记录,用户操作的数据也没有保留下来,但BPM那边却已经审核通过了,这在任何正常流程中,都是不可能出现的状态。

对于用户而言,他在页面会收到报错,然后可能会再次点击"审核通过",而此时BPM那边却显示,流程已经走到下一个节点,该用户无权限操作。

问题分析

根本原因其实不难,因为MYSQL事务只能管他自己,没法控制第三方系统

解决思路

一个字:拆!

对于分布式系统,没有任何人能保证远程调用不出问题,因此在做设计时,就必须能够对这种情况做出应对

上面的操作,打包放进一个大事务就是根因,因此将其拆成小事务则是方案之一,在拆分时,需要遵循以下几个原则:

  1. 小事务内,尽量只有一个远程写操作
  2. 该远程写操作放到方法最后,保证在其返回成功后就能立刻提交事务
  3. 小事务可能会因为某些原因失败,因此需要机制来进行重试

基于以上原则,改动如下

第一步,新建一张任务表:

sql 复制代码
CREATE TABLE `transaction_job` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`type` varchar(255) NOT NULL COMMENT '任务类型',
`data` varchar(255) NOT NULL COMMENT '任务数据',
`error_message` varchar(255) DEFAULT NULL COMMENT '错误信息',
`context` varchar(255) DEFAULT NULL COMMENT '任务上下文',
`create_time` bigint(20) NOT NULL COMMENT '创建时间',
`update_time` bigint(20) NOT NULL COMMENT '更新时间',
`retry_times` int(11) NOT NULL DEFAULT '0' COMMENT '重试次数',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='事务任务表';

作用:保存小事务的关键数据到data字段中,以保证通过该字段,就能正确执行小事务。另外也需要保存当前操作人的信息,以便在后续重试中能获取得到。

第二步,通过定时任务,查出transaction_job表中未完成的数据,并执行对应的操作,这里通过简单的策略模式,将框架代码和业务代码做了分离

首先是框架代码核心逻辑

策略模式接口,一个实现类,就是一个小事务

其中更新待审人的任务实现如下

第三步,改造业务代码,不再一次性把流程写完,而且是在第一个小事务中,顺便往 transaction_job中插入一条数据,以执行第二个小事务

整体思路就是这样

相关推荐
敲代码的嘎仔27 分钟前
28届后端开发-海康威视日常实习一面(已OC)
java·开发语言·后端·面试·海康威视·实习·大厂
IT_陈寒1 小时前
JavaScript数组排序踩的坑,差点让我加班到凌晨
前端·人工智能·后端
接着奏乐接着舞。1 小时前
【2026】73道Redis 常见面试题与参考答案
数据库·redis·后端·缓存
掘金者阿豪2 小时前
Seedance 2.0/2.5 虚拟素材能跨 Key 共用吗?一次讲清 Asset ID、账号隔离与 SaaS 素材架构
前端·后端
程序员黎剑2 小时前
Spring-Bean生命周期-构造器访问Autowired字段为null
java·后端·spring
泡海椒2 小时前
JQuick-Curl 拦截器实战:统一 Token、日志、请求预处理,让第三方接口调用真正工程化
后端
东方小月2 小时前
一篇文章带你深入拆解Skill的本质与工程实现,让你不再滥用Skill
前端·人工智能·后端
夫唯不争,故无尤也2 小时前
分布式训练全栈地图
分布式·ddp·fsdp·ray·verl
国科安芯3 小时前
星载CAN总线通信网络中抗辐射MCU的通信可靠性设计分析
网络·人工智能·分布式·单片机·嵌入式硬件·架构