测试基础 ES 无事务引发的一次线上故障

CKL的思考 · 2023年04月19日 · 3139 次阅读

很多知识细节都来自生产事故,只有经历过,才能记得住。今天的故事,也源于一次线上事故。

01

某天,做完产品的业务升级后,还是比较放松的。刚想搞点别的事,用户群就有用户反馈有问题。真的是不让人省心,先看问题吧。如下图,用户在添加卡片时,提示错误,无法新增,但是列表里又多出了一些数据。点击查看详情时,又提示空白。

这个问题还是比较严重的,优先解决报错的问题,回想了升级的内容后,很容易就定位到问题了,顺利解决(你以为我要说报错的问题吗?这是另一个更长的故事)。

解决完问题后,我关注的是另一个问题:按理说,如果新增数据报错了,列表数据应该也是没有的,这里第 2 步中会多出这么几条数据?

02

根据对系统的了解,我判断出列表数据读取的是 ES 上的数据,而详情页(第 3 步)中的数据来源于数据库,是因为两边的数据不一致吗?查了下数据库,确实没有那几条重复的数据。那问题就比较明显了,应该是新建卡片时,同进写 ES 和数据库,出了问题,导致两边数据不一致。拉代码看看呗。

如上图,在新增卡片时,先做了数据库的插入,然后做 ES 的插入,最后做事件的通知及其他操作。看着好像也没什么问题。

等等,不对,为什么这里没做事务管理?如果有事务,失败了不就会回滚么?

03

研发应该不会犯这么低级的错误,再看看代码。想到了 Spring 中有统一的事务管理注解,应该会使用到的,为什么会没生效呢?找了下,还真是有用了 Transactional,那应该不会有问题的呀。

正常情况下,只要在类上添加@Transactional注解就完事了,那是什么原因导致注解失效了呢?问问 ChatGPT 吧,回复如下:

看着也没什么问题,这些情况都不符合我的场景。那问题出在哪里呢?在其官网上也没找到相关的信息,看看其他大神的文章吧。在 Bing 上搜索了一阵子,发现在别人的文章中有提到@Transactional失效中的原因有一条:如果数据库引擎不支持事务,那么就无法回滚对应的数据。

隐约记得 ES 是不支持事务的,会是这个问题么?

04

继续问问 ChatGPT 吧,看看它能给我什么思路。

看来问题的根源找到了:

用户在操作新增卡片时,先往数据库插了条数据,然后 ES 上也增加了对应的数据,但是在做事件更新时,出了问题(第 1 步的报错信息来源于此),触发了@Transactional事务回滚的机制,所以数据库里的数据被回滚了。但是由于 ES 不支持事务,所以@Transactional也没办法回滚,所以列表中的数据还是能被查看到(第 2 步,ES 中的数据还是在的),但是点击详情(第 3 步)时,因为在数据库中找不到对应的数据,所以页面无法显示。

# 05

针对这个问题,团队讨论了下,解决方案有三种:

  1. 因为双写(同时写数据库和 ES),才导致了这个问题,那就去掉双写,只写数据库,然后通过异步或者 MQ 的方式,再去写 ES,这样能解决一致性的问题,但是时效性会差点。

  2. 在异常类中统一处理,如果发现这个方法有异常抛出,就记录数据信息,去 ES 中做对应的回退操作(分类处理,例如数据库是 insert 操作,就调用 ES 的 delete 操作数据删除),人为实现 ES 的回滚;

  3. 解偶,在方法中只处理双写操作,其他的业务逻辑做异步处理(例如这个场景中,事件更新可以异步处理,并做对应的补偿机制),这样就不会影响主数据的一致性。

最终我们选择了代价最小的第 3 种方案,如果你有更好的方案,可以联系我哟,感谢。

06

解决完问题,回想下如何避免此类事件的发生(应该是个小概率事件)。对于事务的一致性测试,在平时很容易被忽略,大家都还是相信开发会使用事务的。但是对于事务管理是否会失效,没有引起足够的重视。

对于测试人员而言,常见的事务一致性测试场景有哪些呢?

a. 双写或者多写的情况:随着现在中间件使用得越来越多,双写或者多写的情况也会增加,当数据记录在多个地方时,需要关注一致性问题

b. 异步处理,常见的是 MQ,如果消费失败,是否有对应的补偿机制来保障一致性

c. 跨系统的数据存储,有些业务数据存在关联性,又分布在不同的系统中,如何保障一致性,也是测试人员需要关注的。

奇怪的知识是不是又增加了呢?

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册