线上问题——学习记录幂等判断失效问题分析

一、业务流程

image.png
上图是对save和saveScore两个接口的流程抽象,save是上传答题数据,saveScore则是上传答题分数,为保证幂等和防止并发调用,这两个接口都加了分布式锁(还是两层哦)。第一层使用的是不同的锁,因为处理的是不同的表,第二层处理的是相同的表,为了保证数据在某个维度上的唯一,所以使用了相同的锁。本篇文章则主要记录下表C中的逻辑唯一键出现重复数据的问题排查过程,该问题包含了对锁、事务及Mybatis框架的综合运用和理解。

二、问题分析

ROUND 1

问题分析

最开始只看到了第二层锁的逻辑,始终无法想通,两个接口获取的同一把锁,且加锁后都做了幂等判断,查询缓存和数据库,没有再插入,有则更新,C表的重复数据是怎么来的呢?从链路上分析,save先获取到第二层锁,插入了数据;释放锁后,saveScore开始执行,这时候查询数据库返回的结果居然是null!
这时候首先想到的是save的事务没有提交,但查看代码,事务是在获取锁3后才开启的,因此save释放锁3前,事务必定也是提交了的,而saveScore也是在获取锁3后才开启的事务。
image.png
image.png
我想大部分同学到这里会定义为玄学问题,但作为一个资深码农,要相信一切问题必有根因(除非真的是玄学)。最终,终于在saveScore的入口找到了罪魁祸首。
image.png
问题很简单,在锁3的调用入口处已经开启了事务,当save执行第二层逻辑提交事务之前,saveScore已经开启了事务,由于事务的隔离性(可重复读),在获取到锁3时,查询的是事务开始时的快照,即使save的事务已经提交,对于saveScore来说也是不可见的。

解决方案

这里修改方法目前想到以下几种:

  • 让save和saveScore串行化,但这两个接口是主链路上的核心接口,并发量很高,这样会导致性能降低,除非必要不会考虑。
  • 去掉saveScore第一层逻辑的事务,但这样无法保证这几个表的一致性。
  • 将saveScore第二层逻辑也改为异步,那每次调用时都会新启一个事务,同时还能提高接口的并发度,但是这要取决于业务,改动影响较大。
  • 设置saveScore的第二层事务传播级别为REQUIRED_NEW,这样也是每次会开启一个新的事务,也就能读取到save提交的数据了。

综合考虑,设置传播级别是影响最小的(有其它想法的欢迎补充),如果业务方能接受saveScore异步处理,那么这种方法则是最优的。
问题分析到这就结束了?当然没有。

ROUND 2

从流程图上可以看到,表C、D、E是在一个事务里的,那为什么只有表C有重复,表D、E数据却是正常的呢?
先说表E,这个比较简单,首先该表有唯一索引,不像前两个表只有普通索引,其次,业务上该表存放的数据粒度更粗,也就是前面两个表多次插入,这个表只会插入一次,其余都是更新,所以就算没有唯一索引,出现重复记录的概率也远比前两个表小很多。
再看表D,这个表可真的是让我一度以为是自己对事务理解没到位,经过反复验证,证明自己理解是没有问题的。那真的是玄学?于是请教了大佬。
经过大佬分析,事实证明我想的太简单了,最后得到一个推翻上面所有分析的结论。首先我们数据库事务隔离级别设置的read committed,不是repeatable read(是我太想当然,先入为主了)。那么表D、E没有重复数据就能解释了(事实上表D还是有重复数据存在,只是不是当前分析的链路,不过那又是另外一个问题,稍后分析);但是为啥表C的幂等判断失效了呢?
image.png
对表C采用的是双重校验,从链路上看两次查询C都是没有查到数据的,所以才会新增一条,但能获取到锁3说明另一个事务已经提交,当前事务由于是read committed一定也能查询到数据才对,那说明只有一种可能,锁内的查询C是从缓存查的,不是从数据库查的l。
image.png
从阿里云的sql洞察上也能验证这个猜想,按照代码逻辑,saveScore应该有两次查询才对,但实际上只有一条,说明有一次查询(结合skywalking分析得到是第二次没有查数据库)没有到达数据库。我们这个链路上的表都是做了redis缓存的,但是如果是第一次查询是null值,根本不会缓存到redis,那还有什么缓存会拦截到查询SQL呢?还有一个我们平时可能都忽略了的,那就是Mybatis的一二级缓存,一级缓存是默认开启的。先来复习一下Mybatis的一级缓存:
image.png
我们这里的两次查询是完全一样的,且是在同一个sqlSesson极短时间内重复查询,中间没有更新,完全符合一级缓存的使用条件,就是这玩意儿搞得鬼,我以前也没遇到过这个问题,所以也忽略了Mybatis的这个机制,还一直认为这玩意儿就是个摆设,这次算是给我上了一课。

ROUND 3

最后再回头来看看为啥D表也有重复的数据,下图是统计的部分重复数据:
image.png
这个其实很简单,上文细心的朋友可以发现我的分析都是按照save先执行,saveScore后执行的逻辑来分析的,但这两个接口执行时序其实不是固定的,当saveScore先执行,再获取到锁二时就已经开启了事务,即使锁3释放了,内层事务2也不会提交,因为和外层事务是同一个事务,那么save获取到锁3开始执行时也查不到数据,就会重复插入了。这里要解决的话也可以把内层事务传播级别声明为REQUIRED_NEW即可,对业务也没有影响。

三、总结

这次的问题好在对业务没有什么影响,但代码确实写的有问题,考验了对锁、事务以及框架的总和运用和理解。另外在分析问题时不要想当然,先入为主,一定要大胆猜想、亲手验证,只要有发现一点疑点就不应该发放过,因为很有可能就这一个疑点就推翻之前所有的分析。
最后附上链路:F947DF6DC8C94E339ED1BAC5AF5E9812(edu-study、edu-study-async),感兴趣的看官可自行分析验证,有问题欢迎指出。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/511629.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NXP实战笔记(十一):32K3xx基于RTD-SDK在S32DS上配置LPSPI(同步、异步、DMA、主机、从机、中断、轮询)

目录 1、概述 2、RTD-SDK配置 2.1、配置目标 2.2、主、从机引脚配置 2.3、时钟配置 2.4、LPSPI配置 2.5、中断配置 2.6、DMA配置(使用DMA才会配置) 2、dma Logic Instance 2.7、RM配置(使用DMA的情况下必须配置此选项) 3、代码实现 1、概述 S32K3_低功耗LPSPI轮询…

【源码】imx6ull实现触摸屏单点实验-移植tslib和qt

一、本实验实验的器材: 1.正点原子imx6ull的阿尔法开发板v2.2 2.屏幕ALIENTEK 4.3 RGBLCD 二、实验已经移植好的文件: 仓库代码:https://gitee.com/wangyoujie11/atkboard_-linux_-driver.git 1.文件说明 arm-qt.tar.bz2:移植好的…

基于springboot的粮食仓库管理系统(源码+论文)

目录 前言 一、功能设计 二、功能实现 三、库表设计 四、论文 前言 随着我国粮食产量的逐年提升,传统的粮食仓库管理模式已经无法适应当前仓储管理的应用需求,互联网系统平台的发展以及系统化管理模式的普及,粮仓的管理也由传统的人工统…

Cmake的使用

第一步:安装Cmake 双击点开即可,无脑下一步。 第二步:编写一个简单的Cmake项目 CMakeLists.txt文件 # 设置最低的 CMake 版本要求 cmake_minimum_required(VERSION 3.10)# 设置项目名称 project(MyProject)# 添加可执行文件 add_executabl…

SUSE12系统破解root密码

SUSE12系统破解root密码 1、重启系统,重启时出现引导界面,按e 2、在启动 linux 的启动选项里加上 init/bin/bash,然后按 F10 启动,通过给内核传递 init/bin/bash 参数使得 OS 在运行 login 程序之前运行 bash,出现命令…

【力扣白嫖日记】626.换座位

前言 练习sql语句,所有题目来自于力扣(https://leetcode.cn/problemset/database/)的免费数据库练习题。 今日题目: 626.换座位 表:Seat 列名类型idintstudentvarchar id 是该表的主键(唯一值&#xf…

Uni-ControlNet: All-in-One Control toText-to-Image Diffusion Models——【论文笔记】

本文发表于NeurIPS 2023 项目官网:Uni-ControlNet: All-in-One Control to Text-to-Image Diffusion Models 一、Introduction 近两年来,扩散模型在图像合成任务中表现优异,尤其是文本到图像(T2I)扩散模型已成为合成高…

TDengine 在 DISTRIBUTECH 分享输配电数据管理实践

2 月 27-29 日,2024 美国国际输配电电网及公共事业展(DISTRIBUTECH International 2024)在美国-佛罗里达州-奥兰多国家会展中心举办。作为全球领先的年度输配电行业盛会,也是美洲地区首屈一指的专业展览会,该展会的举办…

08 OpenCV 腐蚀和膨胀

文章目录 作用算子代码 作用 膨胀与腐蚀是数学形态学在图像处理中最基础的操作。其卷积操作非常简单,对于图像的每个像素,取其一定的邻域,计算最大值/最小值作为新图像对应像素位置的像素值。其中,取最大值就是膨胀,取最小值就是腐…

#QT(程序打包)

1.IDE:QTCreator 2.实验:打包到windows(exe文件) 3.记录: (1)切换工厂到"release",release模式基本没有调试信息,比较精简,debug模式有很多调试信…

Databend 开源周报第 134 期

Databend 是一款现代云数仓。专为弹性和高效设计,为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务:https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展,遇到更贴近你心意的 Databend 。 支持多语句事务…

Redis 之六:Redis 的哨兵模式(Sentinel)

Redis 哨兵(Sentinel)模式是一种高可用性解决方案,用于监控和自动故障转移的集群系统。 在 Redis Sentinel 架构中,哨兵是一组运行在特殊模式下的 Redis 进程,它们可以监控一个或多个主从复制结构中的 Redis 主服务器以…