事故描述:app 新用户注册成功后,打开首页,客户端请求后端接口为用户分配一个引导策略。由于客户端没有做全局缓存,在每次打开首页的同时,很多业务都请求策略这个接口,导致后端接口在处理并发请求的时候,没有加锁机制处理,在同一秒向数据库插入了多条数据。
每一功能上线后,的卢都能拍着胸脯说
可最后,
经过后端和客户端多次查看代码、讨论...(当时也很激烈)
得出了Bug点和解决方案
Bug点
客户端没有做全局缓存,频繁、同时请求同一接口。
后端数据库字段索引设计不合理(一个用户有且只有一个引导策略,应该加unique:唯一索引,而不是index:普通)。
后端业务代码处理不合理:先查缓存,再查数据库。没有考虑到负载均衡下网关请求延迟,数据库主从同步的延迟(可能主库插入了数据,但是从库中没有查询到数据,这样就会插入新的数据)。
毕竟线上事故,所以、、、即使在睡觉也得起来修复bug。在团队大佬的带领下,我们给出了几种解决方案
解决方案
客户端加全局缓存,只要接口正常返回数据,就缓存在客户端并增加过期时间,如果数据有变动再更新到缓存,这样避免了很多不必要的请求,减少服务器的很多压力。
数据库 guide表修改index索引为unique索引,但是现在表的数据近800万,更新索引需要一定的时间,可能引起其他未知问题。除非什么时候在“停服”期间修改索引,最终这个方案被抛弃掉。
后端增加锁机制:
先查询缓存,如果缓存没有查询数据再更新到缓存。
入库时增加锁处理,处理时间给1000毫秒,一旦数据库入库成功,立即更新到缓存,然后解锁。这一步非常关键,之前是解锁之后再更新缓存导致加锁没有效果,因为并发的时间间隔非常短。
比如A ,B两个请求在同一秒不同的毫秒来请求接口,A请求处理完了刚好解锁成功,这个时候还没来得及更新到缓存里,且B请求已经处理完了,又因为之前主从延迟原因在从库没有查询到数据,所以再次插入数据。
这几句值得我们仔细品味一番,
方案敲定开始撸coding...
请求策略 getUserGuide(int $userId)
分配策略并入库 allotGuide(int $userId)
至此,经过for模拟并发测试、上线观察了几天,数据不再有重复数据
Bug 总算了修复了,心里的一块石头也落下了,在没修复之前,一直惴惴不安,终日惶惶恐恐。
“人到中年不得已,保温杯里泡枸杞。皮糙肉厚还油腻,一顿能吃五斗米。”
最后附上加锁,解锁逻辑代码,如有更好的优化方案,欢迎来喷...
加锁
解锁
领取专属 10元无门槛券
私享最新 技术干货