定义
Redis 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成数据库崩溃的情况。这通常发生在热点数据上,即那些被频繁访问的数据。
处理 Redis 缓存击穿的一种常见策略是使用 布隆过滤器(Bloom Filter) 加上 互斥锁(Mutex) 的方式。布隆过滤器可以快速判断一个元素是否一定不存在于集合中(可能会有误判,但误判率极低),从而避免了对数据库的无效查询。而互斥锁则用于在缓存重建时保证只有一个线程去访问数据库,从而避免对数据库的并发访问压力。
你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。
实现步骤
我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:
- 引入布隆过滤器:
- 在数据放入缓存时,同时将该数据的 key 放入布隆过滤器中。
- 当查询缓存时,首先通过布隆过滤器检查该 key 是否一定不存在,如果不存在则直接返回或进行其他处理;如果存在,则继续查询缓存。
- 缓存查询:
- 查询缓存,如果缓存中存在,直接返回数据。
- 如果缓存中不存在,则进行下一步。
- 互斥锁处理:
- 使用分布式锁(如 Redis 的 SETNX、Lua 脚本或者 Redisson 提供的锁机制)来确保只有一个线程能进入查询数据库的流程。
- 如果获取到锁,则查询数据库,并将结果放入缓存,然后释放锁。
- 如果没获取到锁,则可以选择等待锁释放后重试或直接返回错误。
public Object getDataFromRedisWithBloomFilter(String key) {
// 检查布隆过滤器
if (!bloomFilter.contains(key)) {
// 一定不存在,直接返回或进行其他处理
return null;
}
// 查询缓存
Object value = cache.get(key);
if (value != null) {
return value;
}
// 缓存未命中,加锁
Lock lock = redisLock.lock(key);
if (lock.tryLock()) {
try {
// 再次检查缓存,因为可能其他线程已经查询过数据库并更新了缓存
value = cache.get(key);
if (value == null) {
// 缓存依然为空,查询数据库
value = queryDataFromDB(key);
// 将数据放入缓存
cache.put(key, value);
// 更新布隆过滤器(可选,根据具体情况决定)
bloomFilter.add(key);
}
} finally {
lock.unlock();
}
} else {
// 等待锁释放或返回错误
// 可以选择等待锁释放后重试或返回空值
}
return value;
}
注意事项
- 布隆过滤器的误判率:虽然布隆过滤器可以高效地判断元素是否一定不存在,但其存在误判率,即
有可能将不存在的元素判断为存在。因此,在使用布隆过滤器时,需要权衡其误判率和性能。 - 锁的粒度:锁的粒度应该尽可能小,以减少锁的竞争,提高性能。
- 锁的释放:确保在 finally 块中释放锁,以避免死锁。
- 缓存预热:在系统启动时,可以将热点数据预先加载到缓存中,以减少缓存击穿的可能性。