记一次愚蠢的操作--线程安全问题

时间:2019-08-22 来源:www.kahiizytgzror5fcahct.com

只有秃头可以变得更强壮。

该文本已包含在我的GitHub存储库中,欢迎使用Star:

记住工作中的愚蠢操作,本文关键字:线程安全

(我如何每天写一个Bug)

我这里有一个系统,提供RPC接口来发送各种信息(如短信,电子邮件,微信)和其他渠道。我这边的系统架构是这样的:

5291509-b44e4274266ad858

系统架构

常规:服务系统提供RPC接口。其他人称我提供的界面。我判断服务系统中的服务,拼接等,最后将此消息放入消息队列中。发送方系统使用消息队列中的数据,然后发送消息

示例:Xiao Wang调用我们的RPC接口并希望发送邮件。我判断了邮件的参数并将其组装成我定义的任务,并将Task放入消息队列中。发送方系统使用此任务,并且调用的API完成发送邮件的功能。

小王称我们的RPC接口。只要服务系统将此任务抛入消息队列,我们就会将响应返回给小王。

只要将此任务放入消息队列,我们就会返回成功。所以有时候,小王会问:“我明确回归是成功的,我的邮件怎能不发出去?” ------(异步)

每次我们发送电子邮件时,我们都会存储此电子邮件的信息(存储在MySQL中)。在MySQL中,我们可以知道此电子邮件的发送时间,发送状态等。

小王的电子邮件非常关注他们是否已成功发送出去。如果传输失败,他需要重新发送。因此,他会听取我们数据库的binlog并判断是否需要根据binlog信息重新发送。

由于各种原因,小王希望在调用我们的RPC接口时获得一个唯一的标识符,以便判断电子邮件是否成功。

显然,仓库的电子邮件ID是不可能的(因为他调整了我们的RPC接口,我们将Task返回到消息队列。此时,发送者系统尚未被消耗)

因此,我们将在服务系统中生成一个messageId,然后将其返回给他,将此messageId绑定到Task,然后转到库。

5291509-9a50522ebcef1d71

流程图

在确定了上述要求和想法之后,我将回过头来看看回复给小王的回复对象。我会看到已经存在一个msgId字段。

我搜索了这个字段中的信息,发现没有使用这个msgId。我想,这是对的,我用过它。我看了一下用法,发现这不是直接使用SendResponse,而是一个额外的枚举类,代码可能如下:

使用枚举,它使用起来非常简单。例如,我发现小王的参数存在问题。我的反手是:

服务系统主要做了两件事

判断参数/类型,各种业务逻辑是否存在问题,并将小王带入的参数封装到Task对象中

将Task对象放在消息队列中

5291509-30b16628528dfabb

两个任务

链接:

所以我要做的是:在返回SendResponse之前,我生成一个唯一的msgId并将其插入到SendResponse对象中。

5291509-feb0f8a021f56ca1

只需在返回sendResponse

之前插入msgId

这种需求很快就完成了。如果你只是测试它并且它不会出错,那么你将是决定性的。小王没有说一段时间有任何问题,所以需求得到了满足。

昨天,小王告诉我:“我没有在这里发邮件,有msgId,看看是什么导致它”

5291509-67aca2ed21e29cab

出了点问题

所以我去了线上的日志,发现根据他给出的msgId,我在这里拍摄的日志没有被发送(但是其他日志的任务)。我很恐慌。我们的系统有问题吗?

任务,以及小王发给我的msgId是其他任务的内容。这是一个大问题(无序消费?数据是否全部搞砸了?),恐慌

然后,他继续补充说:

5291509-20c73cfa670d853b

继续添加信息

之后,我发现电子邮件已成功发送,但他获得了其他任务的一些信息,而不是电子邮件。因此,您只能先比较剩余的邮件,然后查看MsgId是什么。

5291509-83db82bd464babfd

解决第一个问题

这些作品是:

该批邮箱已成功发送

小王得到了其他任务的信息

因此,判断系统没问题,但是msgId在并发过程中有问题(获取其他任务的msgId)

所以我去寻找原因。当我检查代码时,我发现我的前同事在服务系统的类中留下了注释。我觉得这绝对是在我没注意到的道路中间,这让小王得到了其他任务的信息。

人肉调试午休或未发现:每个线程都有一个唯一的操作对象,对象的属性不会转义(在方法内部),然后是整个链接,直到链接结束。

后来,我想,我应该只看一下生成msgId的地方。我发现枚举是在项目内部使用的!

觉醒:

现在我有50个线程,每个线程在处理数据时都有一个默认的sendResponse对象。此对象由枚举标识。因此,这50个线程共享此sendResponse对象

50个线程共享此sendResponse对象,每个线程可以修改sendResponse中的msgId属性,这自然是线程不安全的。

因此,小王可以获得其他任务的msgId(在小王的线程设置了msgId之后,它还没有返回,并且三个线程再次改变了msgId,导致小王得到了三维的msgId)

总结:

我终于知道我的前同事在代码上保留了msgId属性,但没有使用这个属性。

使用枚举不应具有有状态属性(可以修改,变量属性)

很高兴出口干货的Java技术公众号:Java3y。公共号码有200多篇原创技术文章,海量视频资源和精美的脑图。

5291509-6f6c05c8cfb87a09

转发给朋友圈是我最大的支持!

我觉得我的文章写得很好,就像它!