可引起缓存失效的三大问题——穿透、雪崩和击穿:
含义: 指查询一个缓存和数据库中均不存在的数据(极端的如负数或Integer.MAX_VALUE),由于缓存未命中,查询请求将会转到数据库,但数据库中也无此数据,且以往历次查询得到NULL结果的未被写入缓存,导致每次查询该数据的请求都会越过("穿透")缓存,落到数据库,使得缓存这道屏障形同虚设。
风险: 利用不存在的数据对数据库进行高并发查询,数据库感到瞬时压力过大,终因暗箭难防,寡不敌众,撒手库寰,含恨而去。
解决方案: 将从数据库查询的NULL结果以适当形式(比如0)写入缓存,并设置较短的过期时间。
含义: 指在将数据写入缓存时,恰巧出现大量同批次数据的Key设置了相同的过期时间的情况,或者过期时间值比较单一,数据过期时间重合率高,导致大量数据的缓存在某一时刻同时失效(面积之大,势如雪崩),使得大批量请求落库。
风险: 孤军奋战的DB,势单力薄,终究无法顶住瞬间打来的大并发流量的突袭而被打崩,“溃不成库”。
解决方案: 在设置失效时间时采用随机值,如1-50秒随机,这样每个缓存的过期时间的重复率就会降低,从而避免集体失效事件的发生率。
含义: 一些访问热度较高的数据,会在某些时段被大批请求极端高并发地访问。有一种情形是,某一高热度数据恰巧在大批请求到来时失效,那么,这一大批查询请求,就会像枪林弹雨击穿贪官恶霸的脑壳一般,全都穿过缓存打到DB老巢,形成缓存击穿。
风险: 历尽沧桑的DB老巢,本就风雨飘摇,定然扛不住大流量的瞬间精准暴击而轰然崩塌。
解决方案:
1、加锁。对于大量并发请求,只放行其中一个请求去DB中查询,剩余的请求一律等待(自旋)。被放行的请求查询完毕之后立即释放锁,其它请求才能抢到锁。后续抢到锁的请求,仍要先去查缓存,而此时缓存中已有数据,便不会再到DB中查询,从而避免缓存击穿;
2、延长高热度数据的过期时间,或者采用自动续期机制,但对于电商类应用,最好设置高热数据的Key永不过期;
3、分散存放。将高热度数据分布式地存放于不同的缓存分片或缓存集群中。