一 缓存穿透
1. 行为
查询一个一定不存在的数据。存储层(姑且认为是db,下面都用db指代)查不到数据则不写入缓存,那么下次请求这个不存在的数据同样会到db层查询,失去了缓存的意义。流量大或人为恶意攻击可能会使db宕掉。
2. 解决方案
(1) 布隆过滤器。将全量可能存在的数据哈希到一个足够大的bitmap中,布隆可能误报,但绝不会漏报,那么一定不存在的数据会被拦截掉,从而缓解了对db的压力
(2) 空结果也进入缓存。如果查询返回的结果为空 (数据不存在 | 服务不可用), 仍将数据-空结果进行缓存,注意将其过期时间设置非常短(不超过5min)
二 缓存雪崩
1. 行为
设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时实效,请求全部打到db,db瞬间压力过重雪崩。
2. 解决方案
(1) 加锁或采用队列保证缓存的单线程,避免失效时大量请求落到db存储系统
(2) 缓存时间离散化。在愿缓存的失效时间基础上增加一个随机值,降低同一时间集体失效概率
三 缓存击穿
1. 行为
对于设置了过期时间的某些key,在过期的时间点,恰好对这个key有大量的并发请求过来,这些请求发现缓存过期同时请求db加载数据并回设到缓存,这个高并发的请求可能瞬间把后端db压垮。
2.解决方案
(1) 永不过期
(2) 使用互斥锁。缓存失效的时候,不直接load db,而是使用缓存工具中带有成功返回标识的方法(比如redis的setnx,memcache的add)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存。否则重试整个get缓存的方法。
在redis2.6.1之前版本未实现setnx的过期时间,所以给出两种版本代码参考
a) setnx无过期时间版本:
1 String get(String key) { 2 String value = redis.get(key); 3 if (value == null) { 4 if (redis.setnx(key_mutex, "1")) { 5 // 3 min timeout to avoid mutex holder crash 6 redis.expire(key_mutex, 3 * 60) 7 value = db.get(key); 8 redis.set(key, value); 9 redis.delete(key_mutex); 10 } else { 11 //其他线程休息50毫秒后重试 12 Thread.sleep(50); 13 get(key); 14 } 15 } 16 }
b) redis2.6.1后, setnx有过期时间版本:
1 public String get(key) { 2 String value = redis.get(key); 3 if (value == null) { //代表缓存值过期 4 //设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db 5 if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表设置成功 6 value = db.get(key); 7 redis.set(key, value, expire_secs); 8 redis.del(key_mutex); 9 } else { //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可 10 sleep(50); 11 get(key); //重试 12 } 13 } else { 14 return value; 15 } 16 }
原文地址:https://www.cnblogs.com/balfish/p/8270112.html