有一些往事总是会在酒后提起
我问朋友,在工作上有什么事情是到现在还记忆尤新的?
我朋友一个激灵坐起来,点上一支烟,只见烟头亮了4、5下,才见他吐出一口烟来,说道:“那还真有”
起因
刚参加工作那会做个积分兑换的项目,做完安安稳稳回家过年,哪知道才第三天,就被主管紧急召回,说是捅了大篓子,让我们快来。
回来了才知道项目被灰产盯上,积分被刷,业主业绩没做上去,东西倒是一箱箱往外兑,粗略估计,一天差不多损失上万。
我们赶紧排查,通过积分明细发现,这些人一秒就能获取一次积分,这远远超过了咱们规则的限定值,这肯定是用脚本了,我们后来联系上他,死活不承认。业主希望把负面影响减小到最大,选择不再追究,从我们的合同中扣。
那是我刚毕业做的第一个项目,上来就是项目人员全体降薪一个月,降20%!
俗话说,吃一堑长一智,所以我对这个事情印象还是蛮深的。
问题原因
先说下接口的逻辑层次结构:
–controller
积分获取接口,用PointController表示
–service
积分获取接口service,用PointService表示
我用伪代码来表示整个调用逻辑:
PointController
1.创建一个分布式锁对象,该分布式锁使用redis的NX命令实现
2.随后创建一个3s过期的分布式锁,以便锁住该新增积分的请求
3.最后在新增积分执行完后,在finally中释放锁
4.最后返回该用户的最终积分
PointService
public class PointService {@Transactional(rollbackFor = Exception.class)public void addPoint() {//查询积分规则PointRule pointRule = getPointRule();//查询用户该积分项的积分获取记录总数Integer total = getPointRecords();//判断该用户的积分记录总数是否大于 积分规则限定的次数//大于则不处理,返回if(total - pointRule.getRuleTimes >= 0) {return;}//生成积分记录int insert = insertPointRecords();//更新用户总积分if(insert > 0) {updateUserPoint();}}
}
PointService 中的添加积分逻辑:
首先查询该项目积分规则,查询用户该积分项的积分获取记录总数
2.判断该用户的积分记录总数是否大于 积分规则限定的次数,大于则不处理,返回
3.生成积分记录
4.更新用户总积分
5.该添加积分的逻辑整体上看好像没什么问题,也确实在一切正常的情况下运行是不会有问题的。
如果PointService 中的添加积分逻辑在分布式锁有效期3s内执行完,是不会有问题的。
但如果PointService中的添加积分逻辑超过3s…那是不是后续请求又可以获取锁了,这也正是这次事故的原因。
因为PointService中的添加积分逻辑超过了3s,并且上一个请求的事务还未提交,后续请求已经获取锁进入PointService,在查询积分记录后,判断还是满足规则,继续执行后续的逻辑,造成用户能够获取多次积分。
处理方案
原因总结一下:
添加积分逻辑处理时间过长
分布式锁超时
第一个问题:逻辑改动过大,需要时间调整,没有采用
第二个问题:换成Redisson,因为redisson在即使超时的情况下也会续锁,避免锁超时
“那时候刚毕业,团队也是一个临时组成的班组,没什么经验,工作也忙,忽略了代码Review,最关键的是没做接口并发测试,或者根本没做出来,也没有去做长事务监控,以及频繁请求。”
“那你有什么经验想跟大家分享的?”我问他。
“有!”
他说:“做项目别忘记测试!”
感谢每一个认真阅读我文章的人!!!
作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。
软件测试面试文档
我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
视频文档获取方式:
这份文档和视频资料,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!以上均可以分享,点下方小卡片即可自行领取。