日常Bug排查-偶发性读数据不一致

bug · 浏览次数 : 0

小编点评

**日常 Bug 排查-偶发性读数据不一致前言** **每日 Bug 排查系列**都是一些简单 Bug 的排查。笔者将在这里介绍一些排查 Bug 的简单技巧,同时顺便积累素材。 **Bug现场业务场景** 支付场景是支付成功后对支付状态进行更新,并将支付成功时间戳写入子单据中。由于支付成功之后需要执行一些动作,因此正常请求顺序应为: 1. 请求1:更新主单据状态为 success。 2. 在事务内,请求2更新子单据状态为 success。 3. 调用下游 RPC 传参,更新主单据状态。 **思路 1:事务没生效** 在请求 1 中,我们是在事务内更新的数据,所以数据应该始终保持一致才对。然而,在实际中,概率约为几亿分之一,在请求 2 中获取的子单据时间戳为 0 时,会引发数据库隔离级别错误,导致调用失败。 **思路 2:RC隔离级别** 根据业务的数据库配置,该应用采用 RR(Read Committed)隔离级别。在该隔离级别下,读操作会阻塞直到提交操作完成。由于在请求 1 中获取的子单据时间戳为 0,导致调用下游 RPC 时获取的子单据时间戳为 0,导致调用失败。 **结论** 由于 RC隔离级别解释的矛盾导致调用失败,笔者最终找到了问题的关键:该应用在换库的过程中采用了默认的配置,导致原来设置为 RR 的库在换了大容量机器后被默认改成了 RC隔离级别。由于该库包含了部分 RC 库,因此最终的问题还是出现在该库中。

正文

日常Bug排查-偶发性读数据不一致

前言

日常Bug排查系列都是一些简单Bug的排查。笔者将在这里介绍一些排查Bug的简单技巧,同时顺便积累素材。

Bug现场

业务场景

先描述这个问题出现的业务场景。这是一个支付的场景,如果支付成功了,我们就把支付状态置为success(主单据更新)同时写入支付成功时间戳为t1(子单据更新)。支付成功之后,我们还需要做其它的动作,做这个动作的时候我们需要刚才的支付成功时间戳t1。那么,我们正常的请求顺序即为:

Bug现场

奇怪的是,线上运行时候,会有极小的概率(大概是几亿分之一)获取的这个时间戳为0!也即在读到主单为success的时候,看到的子单时间戳是0!由于时间戳为0,所以调用下游RPC传参错误导致了调用失败。
如下图所示:

思路

因为在请求1中,我们是在事务内更新的,数据应该始终保持一致才对。那很直观的第一个思考点就是:
思路1: 是不是事务没生效?笔者看了下源代码,使用没有问题,也不存在类内方法互相调用的情况。再者说,如果事务没生效,概率不至于这么低。
思路2:稍加思索一下,好像这个是事务隔离级别的原因。在这个Case里面,看上去数据库采用的RC隔离级别,也就是读已提交。如下图所示:

t1时刻,请求2查询到的子单据时间戳为0
t2时刻,请求1提交,这时候将子单据时间戳更新为t1,主单据状态为success
t3时刻,请求2由于RC隔离级别,能看到请求1的提交,主单状态为success,所以判定可以进行下游RPC的调用,但是由于在t1时刻获取到的时间戳为0,导致调用失败

矛盾点

数据库隔离级别是RC应该能非常好的解释出现Bug时的行为。于是笔者查了一下隔离级别,发现是RR,这就陷入了矛盾!但由于RC这个隔离级别解释这个Bug非常的靠谱,所以笔者看了下业务的数据库配置,发现它有100个库。那么就自然有了下一步猜想:这100个库中有的是RR的,有的是RC的。出问题的那个库正好就是RC的。

指定库查询隔离级别

于是笔者就根据业务的shardKey到了指定的库查询隔离级别,发现它果然是RC级别的,真相大白!这100个库中大概有1/3的库是RC隔离级别。

后续修复

这个问题是由于DBA在换库的过程中采用了默认的配置,导致原来设置为RR级别的库在换了大容量机器后被默认改成了RC隔离级别。DBA找了个时间将隔离级别切换回RR后问题就消失了,并编写了相应的巡检脚本防止此类问题再次发生。

总结

隔离级别是比较微妙的,相关问题大多只在高并发大流量下才会有偶发性的显现,分库分表集群中不同DB的隔离级别由于种种原因导致的不一致会加大问题的排查难度。有时候遇到无法解释问题时可以考虑下底层组件的设置问题。

与日常Bug排查-偶发性读数据不一致相似的内容:

日常Bug排查-偶发性读数据不一致

日常Bug排查-偶发性读数据不一致 前言 日常Bug排查系列都是一些简单Bug的排查。笔者将在这里介绍一些排查Bug的简单技巧,同时顺便积累素材。 Bug现场 业务场景 先描述这个问题出现的业务场景。这是一个支付的场景,如果支付成功了,我们就把支付状态置为success(主单据更新)同时写入支付成功

日常Bug排查-改表时读数据不一致

前言 日常Bug排查系列都是一些简单Bug的排查。笔者将在这里介绍一些排查Bug的简单技巧,同时顺便积累素材。 Bug现场 线上连续两天出现NP异常,而且都是凌晨低峰期才出现,在凌晨的流量远没有白天高峰期大。而出问题的接口又是通常的业务请求。于是,很自然的,我们就想凌晨有什么特殊的运维动作,翻了下时

日常Bug排查-MVCC和for update混用导致读数据不一致

日常Bug排查-MVCC和for update混用导致读数据不一致 前言 日常Bug排查系列都是一些简单Bug的排查。笔者将在这里介绍一些排查Bug的简单技巧,同时顺便积累素材。 Bug现场 又是喜闻乐见的读数据不一致的问题。这次的问题是这样,业务在一个事务中更新A和B两个表的两个数据。但是在另一个

日常Bug排查-连接突然全部关闭

日常Bug排查-连接突然全部关闭 前言 日常Bug排查系列都是一些简单Bug的排查。笔者将在这里介绍一些排查Bug的简单技巧,同时顺便积累素材。 Bug现场 最近碰到一个问题,一台机器上的连接数在达到一定连接数(大概4.5W)连接数之后会突然急速下降到几百。在应用上的表现就是大量的连接报错,系统失去

日常Bug排查-读从库没有原子性?

日常Bug排查系列都是一些简单Bug排查。问题虽小,但经常遇到,了解这些问题,会让我们少走点弯路,提升效率。说不定有些问题你遇到过哦:) Bug现场 业务开发同学突然问了笔者一个问题,从库读会不会没有原子性?我下意识的反应怎么可能,只要是遵守MySQL主从Replication协议的原子性至少是能够

如何实现简单的分布式链路功能?

为什么需要链路跟踪 为什么需要链路跟踪?微服务环境下,服务之间相互调用,可能存在 A->B->C->D->C 这种复杂的服务交互,那么需要一种方法可以将一次请求链路完整记录下来,否则排查问题不好下手、请求日志也无法完整串起来。 如何实现链路跟踪 假设我们从用户请求接口开始,每次请求需要有唯一的请求

debug技巧之远程调试

一、前言 大家好啊,我是summo,今天给大家分享一下我平时是怎么调试代码的,不是权威也不是教学,就是简单分享一下,如果大家还有更好的调试方式也可以多多交流哦。 当我们的应用发布到线上之后,就不能随意启停了,但如果线上出现了BUG怎么办呢?大多数时候我们会借助线上打印的日志进行排查问题,如果幸运的话

《最新出炉》系列入门篇-Python+Playwright自动化测试-42-强大的可视化追踪利器Trace Viewer

1.简介 在我们日常执行自动化测试工作的过程中,经常会遇到一些偶发性的bug,但是因为bug是偶发性的,我们不一定每次执行都能复现,所以我们在测试执行的时候,追踪用例执行就变得非常重要了。playwright提供了一个Playwright Trace Viewer工具来追踪测试执行,这是一个GUI工

FastJson不成想还有个版本2啊:序列化大字符串报错

# 背景 发现陷入了一个怪圈,写文章的话,感觉只有大bug或比较值得写的内容才会写,每次一写就是几千字,争取写得透彻一些,但这样,我也挺费时间,读者也未必有这么多时间看。 我想着,日常遇到的小bug、平时工作中的一些小的心得体会,都还是可以写写,这样也才是最贴近咱们作为一线开发生活的,也不必非得是个

测试的底层逻辑

写这篇文章,是希望把我的一些我认为是非常有价值的经验总结出来,能够帮助刚做测试不久的新同事,或者是测试经验丰富的老同事以共享。希望我们可爱的新同事,准备要在测试领域耕耘的伙伴,能够通过我的文章了解到测试的底层逻辑,也就是我们测试工作中可能看不到隐藏较深的点,而不只是日常所见的写用例、提bug、开发自动化、做平台;俗话说外行看热闹,内行看门道。