掌柜大作战(25):健身房签到系统,多线程使用不当造成的1个严重bug

版权声明:襄阳雷哥的版权声明 https://blog.csdn.net/FansUnion/article/details/83654678

10月份,我负责的健身房系统出现了1个严重bug,一时半会也没有找到原因。
最终,让另外一个同事分析,发现是多线程的bug。

在之前的工作问题记录中,提到过1次多线程bug。
一个同事,异步保存一条记录Record。然后传给我方法1个id,这个时候,我去查询get(id)找不到对象,只好退出,因为有时候,它那个异步保存的方法,还没有把对象保存到数据库。

业务场景
资源:健身房 操课室和器械区
用户预约-健身房签到-如果不来违约-违约3次加入黑名单。

对违约的处理:
第1步:
如果人来了,在签到签退的时候,实时生成1条违约记录。
如果人没来,先扫描预约记录,生成1条对应的违约记录。(用定时任务A)
第2步:
用定时任务B,扫描定时任务A生成的违约记录list,处理1条包含2个事项:
标记违约记录为“已处理”、个人违约次数增加1次。

多次事故都出在了定时任务B上,我的一些想法和实践。
1、事故发生
为了避免1条记录被多个线程同时处理
使用数据库的select for update悲观锁,2台机器上的2个定时任务,都去抢锁。抢锁失败的,自动退出。
抢锁成功,通过startTask,标记任务正在执行。任务执行完成,endTask,标记任务执行完毕。

问题出现了:
startTask
多线程。
endTask
中间的多线程任务,还没有执行完成,endTask标记任务结束。
另外一台机器,也开始执行,最终导致1条记录被2个线程处理。
代码中,修改1条记录使用了乐观锁,但是不起作用,因为2台机器的2个线程,是先后处理的。
最后的结果是:用户违约1次,违约次数增加了2。

2、解决事故
方法1:不使用多线程,最早也是这么做的。由于某些特殊因素,搞了多线程,每个线程单独处理1条记录。
方法2:所有任务执行完成时,标记任务结束。
好的办法是使用Future多线程。
方法3:Thread.sleep(10分钟),10分钟后,再关闭endTask。
具体时常,根据业务场景,自定义设置。

3、多线程和并发的一些考虑点
a、只让1台机器处理记录,上面提到了。
b、2个线程同时修改1条记录,使用version乐观锁,只让1条修改成功。
具体到健身房,如果2个线程同时处理的是1个人的2次违约,也只会成功1条。
定时任务下次执行,再次处理。
乐观锁有个坑,最早使用过date类型作为乐观锁的条件,事实证明,date类型很坑,线上遇到过问题,测试环境无法复现。
c、防止1条记录被多次处理。
使用hasHandled,标记某个记录是否已经被处理了。
被处理了,直接退出。
d、生成违约记录判断重复
根据预约记录,生成违约记录的时候,也需要判断重复。
1个人的1次预约,最多只能违约1次。
e、个人违约记录id集合。
不但记录1个人的违约次数,还把对应的预约记录id保存起来。
2个比较下,如果对不上,看下数据,就很容易跟踪并发现问题产生的原因。

最大的启示
系统从一开始做的时候,就考虑多线程和并发。
之前的想法是,系统刚刚开始做,先快点把功能做完。早期,也没几个人用,系统流量不大,没啥问题。

事实是:功能一直增加,流量和场景越来越多。
在不断的增加和修改代码中,需要考虑并发修改,但是代码却一直没动。
举个例子:
白天时段:用户来健身房签到,如果违约,实时处理。
夜间时段:定时任务处理用户预约记录,如果违约,处理掉。
2个时间段,不交叉,没有并发问题。

后来增加了“用户手动取消预约功能”,是个异步MQ消息,随时都可能发过来。
这个时候,就可能存在2个线程,同时处理1个用户的1条记录。
--------------------- 
作者:小雷FansUnion 
来源:CSDN 
原文:https://blog.csdn.net/FansUnion/article/details/78649141 
版权声明:本文为博主原创文章,转载请附上博文链接!

猜你喜欢

转载自blog.csdn.net/FansUnion/article/details/83654678
今日推荐