【Redis实战篇】利用布隆过滤器解决缓存穿透问题

时间:2024-11-15 15:51:24

前言

在日常业务中,我们将数据存放在数据库中,查询时从数据库查询,但是这样效率并不高,为了提高查询效率,我们将数据缓存在Redis中,查询时直接从Redis中查询,由于Redis中的数据是存放在内存中的,所以查询的效率得到大大提升。但从而也会带来一些问题,其中缓存穿透就是其中一个问题。

文章目录

  • 什么是缓存穿透?
  • 解决方案
    • 1.将DB所有数据放入缓存中
    • 2.布隆过滤器
      • 什么是布隆过滤器
      • 布隆过滤器的误判
  • 实际应用
    • 1.引入依赖
      • 1.1引入Redisson依赖
      • 1.2 配置Redis参数
    • 2.创建布隆过滤器实例
    • 3.在代码中使用
    • 4. 应用中存在问题

什么是缓存穿透?

查询一个不存在的数据,mysql查不到数据也会直接写入缓存,就会导致每次请求都查询数据库

在这里插入图片描述

海量用户如果说查询的用户名存在或不存在,全部请求数据库,会将数据库直接打满。

解决方案

1.将DB所有数据放入缓存中

将数据库所有的数据放入缓存中。

存入缓存引出的问题
这里的数据都是永久数据。占用Reids内存太高。所以不采取这个方案。

2.布隆过滤器

下面为流程图
在这里插入图片描述

什么是布隆过滤器

位图

所谓位图,就是用每一位来存放某种状态,适用于海量数据,整数,数据无重复的场景。通常是用来判断某个数据存不存在的。

布隆过滤器概念

布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间
在这里插入图片描述
布隆过滤器的思想是将一个元素用多个哈希函数映射到一个位图中,因此被映射到的位置的比特位一定为1
所以可以按照以下方式进行查找:分别计算每个哈希值对应的比特位置存储的是否为零,只要有一个为零,代表该元素一定不在哈希表中,否则可能在哈希表中。
注意:布隆过滤器如果说某个元素不存在时,该元素一定不存在,如果该元素存在时,该元素可能存在,因
为有些哈希函数存在一定的误判

优点: 内存占用少、没有多余key
缺点: 实现复杂,存在误判

布隆过滤器的误判

首先布隆过滤器的误判是可以调整的。

  • 布隆过滤器要设置初始容量。容量设置越大,冲突几率越低。
  • 布隆过滤器会设置预期的误判值。

其次在使用布隆过滤器时,就需要考虑误判是不是可以接受

例如在用户设置用户名时,“aaaa”在数据库中不存在,但是在布隆过滤器中的结果返回是存在的,那么“aaaa”这个用户名就不能再被注册,这对用户并不会造成实质性的损失,所以这个误判就是可以接受的。

而在有金钱交易的业务场景中,误判就是不能被接受的。

实际应用

1.引入依赖

1.1引入Redisson依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
</dependency>

1.2 配置Redis参数

这个参数因人而异,根据自己的host地址以及密码等配置即可。

2.创建布隆过滤器实例

import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 布隆过滤器配置
 */
@Configuration
public class RBloomFilterConfiguration {

    
    @Bean
    public RBloomFilter<String> CachePenetrationBloomFilter(RedissonClient redissonClient) {
        RBloomFilter<String> cachePenetrationBloomFilter = redissonClient.getBloomFilter("xxx");
        cachePenetrationBloomFilter.tryInit(0, 0);
        return cachePenetrationBloomFilter;
    }
}

这里tryInit有两个核心参数

public boolean tryInit(long expectedInsertions, double falseProbability)
  1. expectedInsertions:预估布隆过滤器存储的元素长度
  2. falseProbability:运行的误判率

误判率越低,位数组越长,布隆过滤器占用内存越大;
误判率越低,散列Hash函数越多,计算耗时越长。

3.在代码中使用

private final RBloomFilter<String> userRegisterCachePenetrationBloomFilter;

4. 应用中存在问题

当布隆过滤器中不存在时,代表数据要插入数据库,如果恶意短时间内(毫秒级)发送海量请求,这些请求都要落到数据库,造成数据库压力,此时就要引入分布式锁来解决问题,锁定当前数据进行串行执行,防止恶意请求将数据打到数据库。

 RLock lock = redissonClient.getLock(...);
       try {
           if (lock.tryLock()){
               int insert = baseMapper.insert(...);
               if (insert < 1){
                   throw new ClientException(...);
               }
               userRegisterCachePenetrationBloomFilter.add(...);
               return;
           }
           throw new ClientException(...);
       }finally {
           lock.unlock();
       }