【原创】缓存穿透-布隆过滤器实现

Spring Boot项目布隆过滤器实现,解决Redis缓存穿透

本文主要介绍如何在Spring Boot项目中使用布隆过滤器解决Redis缓存穿透问题。首先,我们将简要介绍Redis缓存问题和布隆过滤器的概念,然后实现一个简单的Spring Boot应用程序,使用google的guava布隆过滤器来解决缓存穿透。实际使用中也可以使用Redisson组件实现redis自己的布隆过滤器结构。

1. 布隆过滤器

布隆过滤器(Bloom Filter)是一种空间效率极高的概率型数据结构,用于判断一个元素是否在一个集合中。它存在一定的误判率,但是可以很好地解决缓存穿透问题。
它具有空间效率和查询时间效率的优点,但是存在一定的误判率。当布隆过滤器判断一个元素在集合中时,它可能是错误的(即误判),但是如果判断一个元素不在集合中,那么它一定是正确的。

布隆过滤器实际使用:
  • 缓存穿透问题:当一个请求查询一个不存在于缓存中的数据时,如果直接访问数据库,会造成数据库的压力增大。此时,可以使用布隆过滤器来判断请求的数据是否存在于缓存中,如果不存在,则直接返回空结果,避免了对数据库的不必要访问。

  • 防止恶意访问:在网络安全中,有一类攻击称为“扫描攻击”,攻击者会不断地向服务器发送大量的请求,以寻找弱点。布隆过滤器可以用于记录已知的恶意IP地址,从而快速地判断请求是否来自恶意IP地址。

  • 垃圾邮件过滤:在邮件系统中,布隆过滤器可以用来过滤垃圾邮件。将已知的垃圾邮件的特征(如发件人、主题、内容等)保存在布隆过滤器中,当新邮件到来时,可以快速地判断该邮件是否属于垃圾邮件。

其他一些场景:
  1. 50亿个电话号码,现有10万个电话号码,如何判断这10万个是否已经存在在50亿个之中?(可能方案:数据库,set,hyperloglog)
  2. 新闻客户端看新闻时,它会不断推荐新的内容,每次推荐时都要去重,那么如何实现推送去重?
  3. 爬虫URL去重?
  4. NoSQL数据库领域降低数据库的IO请求数量?
  5. 邮箱系统的垃圾邮件过滤?布隆过滤器(Bloom Filter)就是专门来解决这种问题的,它起到去重的同时,在空间上还能节省90%以上,只是存在一定的误判概率。

2. Redis缓存问题

2.1. 缓存穿透

请求缓存中不存在的数据,以此来让请求全部穿透到数据库,从而攻击数据库。

解决办法:
  • 布隆过滤器:将所有可能存在的数据哈希到一个足够大的布隆过滤器中,如果不存在则可以直接返回,否则需要进一步查询。
  • 缓存空对象:当数据库中不存在数据时,仍然将其存入缓存中,但是过期时间可以设置短一些,避免缓存一直存在。

2.2. 缓存击穿

一个热点数据突然失效或过期时,大量的请求同时涌向数据库,导致数据库压力过大。

解决办法:
  • 设置热点数据永不过期:将热点数据设置成永不过期,确保缓存数据不会失效。
  • 加锁:在缓存失效时,使用互斥锁来控制只有一个线程去查询数据库,其他线程等待查询结果。

2.3. 缓存雪崩

大量的数据同时失效或过期时,大量的请求同时涌向数据库,数据库压力过大。

解决办法:
  • 数据过期时间分散:将缓存数据的过期时间分散开,避免所有数据同时失效。
  • 缓存数据预热:在系统启动时,预先将热点数据加载到缓存中,避免在使用过程中由于缓存失效导致的问题。
  • 备份缓存:使用多级缓存架构,备份缓存中的数据,避免缓存失效时直接请求数据库。

3. 实现步骤

3.1 引入依赖

pom.xml文件中,引入Spring Boot和Redis相关依赖:

<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <!-- Spring Boot Data Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Google Guava 使用google的guava布隆过滤器实现-->
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>30.1-jre</version>
    </dependency>
</dependencies>

3.2 配置Redis和布隆过滤器

application.properties文件中,配置Redis连接信息:

spring.redis.host=localhost
spring.redis.port=6379

创建一个布隆过滤器配置类BloomFilterConfig:

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BloomFilterConfig {

    @Bean
    public BloomFilter<String> bloomFilter() {
        return BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), 1000000, 0.01);
    }
}

3.3 实现缓存服务

创建一个CacheService接口,并实现它。在实现类中,使用布隆过滤器判断数据是否存在,从而避免缓存穿透:

import com.google.common.hash.BloomFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@Service
public class CacheServiceImpl implements CacheService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private BloomFilter<String> bloomFilter;

    @Override
    public Object get(String key) {
        // 使用布隆过滤器判断key是否存在
        if (bloomFilter.mightContain(key)) {
            return redisTemplate.opsForValue().get(key);
        }
        return null;
    }

    @Override
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
        bloomFilter.put(key);
    }
}

3.4 编写测试接口

创建一个简单的REST API,用于测试布隆过滤器在解决缓存穿透问题上的作用:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CacheController {

    @Autowired
    private CacheService cacheService;

    @GetMapping("/cache/{key}")
    public String getCacheValue(@PathVariable String key) {
        Object value = cacheService.get(key);
        if (value == null) {
            return "Cache miss, and the key does not exist in database.";
        }
        return value.toString();
    }

    @GetMapping("/cache/{key}/{value}")
    public String setCacheValue(@PathVariable String key, @PathVariable String value) {
        cacheService.set(key, value);
        return "Cache set successfully.";
    }
}

4. 测试

启动Spring Boot应用程序,然后使用以下命令测试缓存穿透的解决方案:

  1. 设置缓存:

    curl http://localhost:8080/cache/testKey/testValue
    ```
    
  2. 获取缓存:

    curl http://localhost:8080/cache/testKey
    ```
    
  3. 查询不存在的键:

    curl http://localhost:8080/cache/nonexistentKey
    ```
    

在第三个命令中,布隆过滤器将有效地防止缓存穿透,因为它能判断出该键不存在。

5. 总结

使用布隆过滤器,可以将数据库中的所有唯一主键放入布隆过滤器中。每当需要查询某个关键字时,先查询布隆过滤器。如果查询结果表明该关键字不存在,就可以直接返回查询结果为空。如果查询结果表明该关键字存在,就需要进行数据库的访问,查询具体的数据是否存在,不存在则在返回空。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值