1.一般的缓存处理流程
当前台收到请求后,后台先回冲缓存中读取数据,取到直接返回结果,当从缓存取不到结果时,就会访问数据库,从数据库取到数据更新到缓存中,并返回结果,如果数据库也没结果,就返回空。
2. 缓存穿透
描述:
访问一个缓存和数据库都不存在的 key,此时会直接打到数据库上,并且查不到数据,没法写缓存,所以下一次同样会打到数据库上。
如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
缓存穿透流程图如下
解决方案
- 对空值缓存: 如果一个查询返回的数据为空(不管数据是否不存在),任然吧这个空结果(null)进行缓存,设置空结果的超过时间会很短,一般不超过五分钟。
- 设置可访问的名单(白名单): 使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
- 采用布隆过滤器:(布隆过滤器(Bloom Filter))是1970年由布隆提出的。
它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是由一定的误识别率和删除困难。 - **进行实时监控:**当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务。
3.缓存击穿
描述:
某一个热点key,在缓存过期的一瞬间,同时有大量的请求打进来,由于此时缓存过期了,所以请求最终都会走到数据库,造成瞬时数据库请求量大、压力骤增,甚至可能打垮数据库。
解决方案:
- 预先设置热门数据: 在Redis高峰访问之前,把一些热门数据提前存入到redis中,加大这些热门数据key的时长。
- 实时调整: 现场监控哪些数据热门,实时调整key的过期时长。
- 使用锁: 在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,直接走缓存。可以使用“分布式锁”,还是“JVM 锁”
使用 redis 分布式锁的伪代码,仅供参考:
public Object getData(String key) throws InterruptedException {
Object value = redis.get(key);
// 缓存值过期
if (value == null) {
// lockRedis:专门用于加锁的redis;
// "empty":加锁的值随便设置都可以
if (lockRedis.set(key, "empty", "PX", lockExpire, "NX")) {
try {
// 查询数据库,并写到缓存,让其他线程可以直接走缓存
value = getDataFromDb(key);
redis.set(key, value, "PX", expire);
} catch (Exception e) {
// 异常处理
} finally {
// 释放锁
lockRedis.delete(key);
}
} else {
// sleep50ms后,进行重试
Thread.sleep(50);
return getData(key);
}
}
return value;
}
4.缓存雪崩
描述: 大量的热点key设置了相同的过期时间,导致缓存在同一时刻全部失效,造成瞬时数据库大量请求、压力骤增,引起雪崩,甚至导致数据库被打挂。
解决方案:
- 过期时间打散: 既然是大量缓存集中失效,那最容易想到就是让他们不集中生效。可以给缓存的过期时间时加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效。
- 热点数据不过期: 该方式和缓存击穿一样,也是要着重考虑刷新时间的时间间隔和数据库异常如何处理的情况。
- 加互斥锁: 开方式和缓存击穿一样,按key维度加锁,对于同以可key,只允许一个线程去计算,其他线程原地阻塞等待第一个线程的计算结果,让后直接走缓存。