前面写了一篇文章来介绍布隆过滤器《布隆过滤器你真的懂了吗》,这一篇将重点介绍一下如何使用布隆过滤器来防止缓存击穿和穿透
缓存穿透
缓存穿透是指一个请求查询缓存和数据库中都不存在的数据,这样的请求会直接穿透缓存层,导致请求直接打到数据库上,从而影响系统的性能。 对于缓存穿透问题,可以使用布隆过滤器对请求进行过滤,将不存在的数据直接拦截,避免请求直接穿透缓存层,减轻对数据库的压力。
缓存击穿
缓存击穿是指一个热点数据的缓存失效,导致大量请求同时打到数据库上,从而导致数据库压力剧增,甚至可能导致宕机。
对于缓存击穿问题,在缓存穿透的基础上,在缓存失效的同时,先获取一个锁,然后从数据库中加载数据并更新缓存,最后释放锁,避免大量请求直接打到数据库上,从而保护数据库的性能。
怎么实现
- 客户端发起请求,并由应用程序进行处理。
- 应用程序先查询布隆过滤器,如果数据不存在于布隆过滤器中,则直接返回缓存未命中的结果。
- 如果数据可能存在于缓存中,则继续查询缓存。
- 如果缓存命中,则直接返回数据。
- 如果缓存未命中,则尝试从锁中获取缓存。
- 如果获取锁失败,则直接返回缓存未命中的结果,避免缓存击穿。
- 如果获取锁成功,则再次检查缓存,避免缓存击穿。
- 如果缓存命中,则将数据添加到布隆过滤器,并返回数据。
- 如果缓存未命中,则查询数据库,并将数据添加到缓存和布隆过滤器,然后返回数据。
这里来用redisson 创建一个布隆过滤器实现一下查询的过程
RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("bloom-filter");
bloomFilter.tryInit(10000, 0.01);
在查询缓存之前,先查询布隆过滤器
String data = "some data";
if (!bloomFilter.contains(data)) {
// 数据不存在于布隆过滤器中,直接返回缓存未命中的结果
return null;
}
// 数据可能存在于缓存中,继续查询缓存
String result = cache.get(data);
if (result == null) {
// 尝试从锁中获取缓存
RLock lock = redissonClient.getLock("lock:" + data);
try {
if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
try {
// 再次检查缓存,避免击穿
result = cache.get(data);
if (result == null) {
// 数据不存在于缓存中,查询数据库并将结果添加到缓存中
result = database.get(data);
if (result != null) {
cache.set(data, result);
}
}
} finally {
lock.unlock();
}
} else {
// 获取锁失败,避免击穿
return null;
}
} catch (InterruptedException e) {
// 处理中断异常
}
}
return result;