没有MQ如何使用本地消息表实现分布式事务

本篇文档所有内容,都是基于上一篇文章的进行创作的。

单线程版

我们一个业务场景,课程培训中心无法接入内网的MQ,导致我们需要对标准的本地消息表方案进行修改。

这里说一下和标准的本地消息表实现的区别:此时本地事务完成后,没有办法向MQ发送消息了,此时需要依赖调度任务中心来调度。调度任务定时触发,会捞起消息状态为"处理中"的消息,然后安全培训服务调用课程中心接口推送数据,等待课程中心返回数据处理结果,推送过程是同步的,当收到课程培训中心的响应后,如果数据推送成功,那么更新本地消息表的状态为已成功。

异常情况分析

序号 安全培训服务处理业务数据 消息表 课程培训中心处理业务数据 说明
1 失败 - - 安全培训服务回滚
2 成功 失败 - 安全培训服务回滚
3 成功 成功 失败 调度任务中心定时调度,线程重新处理
4 成功 成功 成功 数据最终一致
5 成功 成功 超时 消息超时未被处理,死信补偿

优缺点

优点

  1. 数据推送的流程得到了简化,不需要使用MQ,可以降低系统维护的成本
  2. 在并发小的场景同样也可以保证数据推送的一致性

缺点

  1. 对数据库的依赖较高
  2. 单线程的进行数据推送,性能得不到保证,在高并发场景下极容易造成消息的堆积,从而影响正常的业务

多线程版

和单线程版本的区别:调度任务定时触发,会捞起消息状态为"处理中"的消息,并将消息进行切分,分成不同的数据段提交到线程池中,线程池中不同的线程负责将不同的数据段推送的课程培训中心,推送过程是同步的,当收到课程培训中心的响应后,如果数据推送成功,那么更新本地消息表的状态为已成功。

异常情况分析

序号 安全培训服务处理业务数据 消息表 线程池推送数据 课程培训中心处理业务数据 说明
1 失败 - - - 安全培训服务回滚
2 成功 失败 - - 安全培训服务回滚
3 成功 成功 失败 - 调度任务中心定时调度,线程重新处理
4 成功 成功 成功 失败 调度任务中心定时调度,线程重新处理
5 成功 成功 成功 成功 数据最终一致
6 成功 成功 超时 超时 消息超时未被处理,死信补偿

这里的异常情况,不再需要考虑MQ出现异常的情况,需要考虑线程池出现异常的情况,因为线程池没有类似MQ重试的功能,所以需要依靠调度任务中心定时调度。

一些思考

定时任务集中式扫表消息有延迟

上述方案中,第3步调度任务把需要处理的数据全部查询出来然后切分后交给线程池处理,像这种集中式的扫表,消息推送会存在延迟。因此可以改成,调度任务每次只是查处需要推送的数据的id起点,然后线程池再根据这个id起点,让不同的线程分别去扫表查询需要处理的消息。

类似这样:

ini 复制代码
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) ExecutorServiceUtils.getThreadPool();
int poolSize = threadPoolExecutor.getPoolSize();
// 根据id切分推送任务,并把任务提交到线程池
int minId=(int)idRangeMap.get("minId");
int maxId=(int)idRangeMap.get("maxId");
int startId=minId,endId;
for (int i = 0; i <poolSize; i++) {
    endId=minId+THREAD_PUSH_DATA_SEGMENT*10;
    if (endId>maxId){
        endId=maxId;
    }
    log.info("第{}个线程处理,id:{}至id:{}的数据",i,startId,endId);
    ExecutorServiceUtils.getThreadPool().submit(new PushDataTask(startId,endId));
    startId=endId;
}

调度任务多次捞起同一条数据处理如何保证幂等

如果调度任务定时触发的时间间隔设置的比较短,一条数据还没有被推送过去,就又被捞起来处理,这样同样会造成数据的重复推送。此外,调度任务触发后,会加锁,锁定一段待推送的数据(避免因为安全培训服务是多实例,造成数据重复推送),这样还有可能造成,两个锁锁住的数据会有重叠,这样其实也是导致数据重复推送。

因此,有两种策略可以解决这个问题:

  1. 合理设置调度任务定时触发的时间,可以适当设置的长一些,同时线程推送的数据量也可以设置的小一些,也就是尽量让线程在一个相对较长的时间内完成相对少的数据推送
  2. 可以增加一个加锁状态:在数据被第一次加锁的时候,把数据的状态修改为"处理中-已加锁",每次调度任务捞取数据加锁的时候都避开这些数据,即可保证每把锁锁定的数据段都是唯一的了。
相关推荐
movee1 小时前
一台低配云主机也能轻松愉快地玩RDMA
linux·人工智能·后端
程序员清风2 小时前
什么时候会考虑用联合索引?如果只有一个条件查就没有建联合索引的必要了么?
java·后端·面试
Seven972 小时前
【设计模式】掌握建造者模式:如何优雅地解决复杂对象创建难题?
java·后端·设计模式
子洋3 小时前
AnythingLLM + SearXNG 实现私有搜索引擎代理
前端·人工智能·后端
heart000_13 小时前
基于SpringBoot的智能问诊系统设计与隐私保护策略
java·spring boot·后端
汐泽学园3 小时前
基于ASP.NET校园二手交易网站设计与实现
后端·asp.net
MZWeiei4 小时前
Scala:case class(通俗易懂版)
开发语言·后端·scala
小杨4044 小时前
springboot框架项目实践应用三(监控运维组件admin)
spring boot·后端·监控
sevevty-seven6 小时前
Spring Boot 自动装配原理详解
java·spring boot·后端