本文转载自微信公众号《微笑的建筑师》,作者雷佳。转载本文,请联系LoveSmile的架构师公众号。事故背景公司近期安排了一波抢购活动。由于后台小哥操作不当,导致活动没有效果,被用户和代理商投诉。经理让我带同事去复查网上事故。是什么原因造成的?抢购活动原定于零时准时开始。22:00,运营商通过后台将产品上线。23:00,后台小哥已经将商品导入缓存。Redis承担了大部分用户的查询请求,避免所有请求都落在数据库上。缓存命中如上图,预计大部分请求都会命中缓存,但是由于后台小哥在预热缓存的时候设置了所有商品的缓存时间为2小时后过期,所以所有商品都会在同一个时间点,所有的请求都会瞬间全部落在数据库上,导致数据库在压力下崩溃,所有的用户请求都超时报错。其实所有的请求都是直接落到数据库上的,如下图:缓存雪崩是什么时候发现的?凌晨01点02分,SRE接到系统告警,登录运维管理系统发现数据库节点CPU和内存飙升超过阈值,赶紧联系后台开发人员定位查看。为什么我没有早点发现?由于缓存设置的过期时间为2小时,缓存可以在凌晨1点前命中大部分请求,数据库服务正常。发现后采取了哪些措施?后台小哥通过日志定位和排查发现问题后,进行了一系列操作:首先通过APIGateway(网关)限制大部分流量,然后重启宕机的数据库服务,然后重新预热缓存确认缓存和数据库服务正常后,网关流量正常释放,01:30左右抢购活动恢复正常。如何避免下次发生?造成这次事故的原因其实是缓存雪崩。查询数据量巨大,请求直接落在数据库上,导致数据库压力过大宕机。其实业界解决缓存雪崩的方法已经比较成熟,比如:统一过期添加互斥锁,缓存永不过期(1)统一过期设置不同的过期时间,让缓存失效的时间点尽量统一。通常可以在有效期上加上一个随机值,也可以统一规划有效期。缓存键过期时间均匀分布(2)添加互斥锁与缓存击穿的解决方案一致。同时只允许一个线程建立缓存,其他线程阻塞排队。互斥访问(3)缓存永不过期与缓存击穿的解决方案一致。缓存在物理上永远不会过期,并且使用异步线程来更新缓存。异步更新缓存回顾总结和同事一起回顾了这次线上事故,大家对缓存雪崩有了更深的认识。为了避免缓存再次发生雪崩事故,我们一起讨论了多种解决方案:(1)统一过期(2)加互斥锁(3)缓存永不过期。希望技术人员能够尊重每一行代码!
