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

前言

我们在开发的过程中,如果一个业务操作需要本地写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中插入一条数据,以执行第二个小事务

整体思路就是这样

相关推荐
Marktowin5 小时前
Mybatis-Plus更新操作时的一个坑
java·后端
赵文宇5 小时前
CNCF Dragonfly 毕业啦!基于P2P的镜像和文件分发系统快速入门,在线体验
后端
程序员爱钓鱼6 小时前
Node.js 编程实战:即时聊天应用 —— WebSocket 实现实时通信
前端·后端·node.js
Libby博仙7 小时前
Spring Boot 条件化注解深度解析
java·spring boot·后端
源代码•宸7 小时前
Golang原理剖析(Map 源码梳理)
经验分享·后端·算法·leetcode·golang·map
小周在成长7 小时前
动态SQL与MyBatis动态SQL最佳实践
后端
瓦尔登湖懒羊羊7 小时前
TCP的自我介绍
后端
小周在成长7 小时前
MyBatis 动态SQL学习
后端
子非鱼9217 小时前
SpringBoot快速上手
java·spring boot·后端