来源:juejin.cn/post/7302308334203486245
👉 欢迎加入小哈的星球,你将获得: 专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17..., 点击查看项目介绍; 《从零手撸:前后端分离博客项目(全栈开发)》 2期已完结,演示链接:http://116.62.199.48/; 截止目前,累计输出 71w+ 字,讲解图 3088+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,Spring Cloud Alibaba 等等,戳我加入学习,解锁全部项目,已有2400+小伙伴加入
前言 案例说明 解决思路 优化 结语
前言
我们在开发的过程中,如果一个业务操作需要本地写MYSQL数据以及对第三方系统做写操作,那么这种流程就涉及到分布式系统一致性的问题,然而并非所有系统都能使用成熟的分布式事务方案
PS:示例代码推送到:
https://gitee.com/dailycreatebug/demo-codes
案例说明
以一个财务报账业务为例,涉及到的系统如下:
系统名 | 作用 | 实现方案 |
---|---|---|
单据系统 | 申请单内容以及凭证的生成 | JAVA |
BPM | 实现流程的运转 | 购买成熟系统(例如:泛微) |
SAP | 财务凭证 | 购买成熟系统 |
详细解释下各系统作用:
单据系统: 财务报账,会提交很多信息(例如:报账事由、报账金额与明细)。同时也会生成财务凭证(不了解凭证也没关系,它就是给财务人员看的东西,对技术人员来说就是数据库的一堆数据) BPM系统: 非常成熟的流程管理系统,以非常直观的方式来实现流程的搭配,不了解的可以自行百度扫盲。在此案例中,需要使用BPM的两个能力:1)调用API,审核通过 2)调用API,获取流程的待审人 SAP系统: 财务专用系统,不用过多了解,只要知道在财务审核完成后,会将单据系统生成的凭证数据通过API调用的方式发送给SAP即可
“审核通过”业务流程
当审核人员审核通过时,大致流程如下:
保存业务数据+记录审核日志 调用BPM接口,审核通过 调用BPM接口,获取最新待审人 如果没有待审人,说明已经审完,生成凭证并推送SAP
代码如下
风险分析
如图所示,如果在1和2出现异常,由于有事务的存在,操作1内的几条mysql写操作会被回滚,因此所有数据都没有任何变化。
但如果1和2正常执行,操作3发生异常,操作1的数据会因为事务回滚,但操作2并不能。因此整个系统会出现一个很诡异的现象:单据系统内,没有任何日志记录,用户操作的数据也没有保留下来,但BPM那边却已经审核通过了,这在任何正常流程中,都是不可能出现的状态。
对于用户而言,他在页面会收到报错,然后可能会再次点击“审核通过”,而此时BPM那边却显示,流程已经走到下一个节点,该用户无权限操作。
问题分析
根本原因其实不难,因为MYSQL事务只能管他自己,没法控制第三方系统
解决思路
一个字:拆!
对于分布式系统,没有任何人能保证远程调用不出问题,因此在做设计时,就必须能够对这种情况做出应对
上面的操作,打包放进一个大事务就是根因,因此方案就是将大事务拆开,在拆分时,需要遵循以下几个原则:
小事务内,尽量只有一个远程写操作 该远程写操作放到方法最后,保证在其返回成功后就能立刻提交事务 小事务可能会因为某些原因失败,因此需要机制来进行重试
整体思路就是这样
基于以上原则,改动如下
第一步,新建一张任务表:
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字段中,以保证通过该字段,就能正确执行小事务。另外也需要保存当前操作人的信息context。
第二步,通过定时任务,查出transaction_job
表中未完成的数据,并执行对应的操作,这里通过简单的策略模式,将框架代码和业务代码做了分离
首先是框架代码核心逻辑
扫描任务
执行任务
这是一个策略模式接口,每个小事务就封装一个独立的实现类
其中更新待审人的任务实现如下,其实就是把那部分代码复制过来
第三步,改造业务代码,不再一次性把流程写完,而且是在第一个小事务中,顺便往 transaction_job
中插入一条数据,以执行第二个小事务
其中步骤2(插入任务数据的代码)的具体实现如下(transactionJobService.create
)
优化
上述方案种,除第一个事务外,后续事务都是通过定时任务来执行的,因此这些事务都存在一定的延迟,用户体验不好,解决办法也非常简单,只需要利用好Spring对于事务的生命周期管理,稍微改造一下插入任务的方法(transactionJobService.create
)
这是Spring提供的工具类,afterCommit()
方法会在事务提交后执行,因此加上这段代码后,思路就变成了这样
如果小事务顺利执行,会立刻将该任务改为“成功”,因此从用户端是感受不到延迟的
注意
可能存在添加任务后,定时任务也立刻扫描到了这条数据,同一任务就会被主线程与定时任务线程同时执行,所以实际应用中需要考虑这个问题(比如:加锁再执行,执行前再检查数据库状态)
结语
目前只给出了解决思路的核心,但真实项目中,还添加了不少额外功能,以后会逐渐更新进来
👉 欢迎加入小哈的星球,你将获得: 专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17..., 点击查看项目介绍; 《从零手撸:前后端分离博客项目(全栈开发)》 2期已完结,演示链接:http://116.62.199.48/; 截止目前,累计输出 71w+ 字,讲解图 3088+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,Spring Cloud Alibaba 等等,戳我加入学习,解锁全部项目,已有2400+小伙伴加入
最近面试BAT,整理一份面试资料《Java面试BATJ通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式:点“在看”,关注公众号并回复 Java 领取,更多内容陆续奉上。
PS:因公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。
点“在看”支持小哈呀,谢谢啦