【布隆过滤器】如何防止缓存穿透、海量邮箱的垃圾邮件过滤等问题?

目录

一、布隆过滤器是什么?

二、布隆过滤器的模拟实现

2.1、模拟实现

2.2、布隆过滤器的优点和缺点

优点:

缺点:

2.3、布隆过滤器的删除功能

2.4、布隆过滤器的使用场景


一、布隆过滤器是什么?

        

        它是一种概率型数据结构,特点是高效的插入和查询,作用是可以告诉你“某个数据一定不存在,或是可能存在”,原理是通过多个哈希函数,将一个数据映射到位图中,好处是不仅提高了查询效率,也可以节省大量的内存空间,底层相当于 哈希+位图;

解读:为什么能知道“某样东西一定不存在,或者可能存在”?

        哈希冲突。因为他的原理是通过多个哈希函数来进行映射,好比我要存放两个字符串,有可能,这两个字符串经过哈希函数计算,映射到的位置正好相同,如下图:

但是,不难理解的一点是,假设有三个哈希函数进行哈希,那么如果我要查找某一个字符串,是否一定不存在,那么一定是肯定的,因为三个位置上只要有一个不为1,就说明要查找的这个字符串一定不存在

PS:

1.一般使用布隆过滤器来说,是会给定一个误判率的;

2.布隆过滤器没有存储当前的数据(如上图);

二、布隆过滤器的模拟实现

2.1、模拟实现

        这里的逻辑实现太简单了,就不展开论述了,对于添加和查找功能,就是通过不同的哈希函数进行哈希来存入或查找不同元素,查找元素时,一旦有一个数值经过哈希函数无法在位图中找到,就说明一定不存在;

代码如下:

class SimpleHash {

    public int cap;//容量
    public int seed;//随机

    public SimpleHash(int cap, int seed) {
        this.cap = cap;
        this.seed = seed;
    }

    /**
     * 根据seed的不同,创建不同点哈希函数
     * @param key
     * @return
     */
    int hash(String key) {
        int h;
        return (key == null) ? 0 : (seed * (cap-1)) & ((h = key.hashCode()) ^ (h >>> 16));
    }

}
public class MyBloomFilter {
    //bitSet的初始化大小
    public static final int DEFAULT_SIZE = 1 << 20;
    //位图
    public BitSet bitSet;
    //记录存储的数据数量
    public int usedSize;

    public static final int[] seeds = {3,5,12,6,24,32};

    public SimpleHash[] simpleHashes;

    public MyBloomFilter() {
        bitSet = new BitSet(DEFAULT_SIZE);
        //创建哈希函数
        simpleHashes = new SimpleHash[seeds.length];
        for(int i = 0; i < simpleHashes.length; i++) {
            simpleHashes[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]);
        }
    }

    /**
     * 添加元素到布隆过滤器
     * @param val
     */
    public void add(String val) {
        //让每个哈希函数分别处理当前数据,并存入位图中
        for(int i = 0; i < simpleHashes.length; i++) {
            bitSet.set(simpleHashes[i].hash(val));
        }
    }

    /**
     * 是否包含val,这里会存在一定的误判
     * @param val 一定是通过这几个哈希函数看对应的位置
     * @return
     */
    public boolean contains(String val) {
        //只要有1个为0 那么一定不存在
        for(int i = 0; i < simpleHashes.length; i++) {
            if(!bitSet.get(simpleHashes[i].hash(val))) {
                return false;
            }
        }
        return true;
    }

    //测试
    public static void main(String[] args) {
        MyBloomFilter myBloomFilter = new MyBloomFilter();
        myBloomFilter.add("hello");
        myBloomFilter.add("hello2");
        myBloomFilter.add("hello3");
        myBloomFilter.add("hehe");
        myBloomFilter.add("haha");
        System.out.println(myBloomFilter.contains("hello4"));
    }
}

PS:布隆过滤器不支持删除工作,因为删除元素时,可能会影响到其他元素;例如有两个字符串在位图中若有一个占用相同的比特位,那么删除其中任意一个字符串,都有可能造成另一个字符串找不到的情况;

2.2、布隆过滤器的优点和缺点

优点:

1.增加和查询时间复杂度都是:O(k)  ,这里k是哈希函数的个数;

2.布隆过滤器不需要存储元素本身,对有保密要求的场合有一定优势;

3.能够承受一定的误判;

4.因为底层是位图实现,因此可以存放海量数据,其他数据结构不行;

缺点:

1.有误判率,即假阳性,不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)

2.不能获取元素本身;

3.一般情况下布隆过滤器不能删除元素;

4.如果采用计数方式删除,可能会存在计数回绕问题。

2.3、布隆过滤器的删除功能

布隆过滤器不能直接删除数据,因为删除元素时可能会影响到其他元素。

        但是办法总还是有的(计数器):将布隆过滤器中的每一个bit位扩展成一个小的计数器,插入元素时给计数器加一,删除元素时给计数器减一,这是一种多占用几倍的存储空间代价来进行删除功能;

存在缺陷:

1.无法确认元素是否真正在布隆过滤器中;

2.存在计数回绕

2.4、布隆过滤器的使用场景

1.去重功能;

2.判断给定数据是否存在:海量数据(数据亿级)存储校验、防止缓存穿透、邮箱的垃圾邮件过滤、黑名单功能等;

例如,缓存穿透就是指大量的请求查询缓存上数据不存在,导致直接请求到数据库,而数据库上也没有数据(后续无法同步到 redis 上),导致数据库宕机等问题. 

解决方案:

其中一种处理方案就是通过布隆过滤器提前把海量数据都进行编号,如果布隆过滤器判断编号可能存在,则直接去读取存储在 Redis 缓存中的数据;如果此时 Redis 缓存没有存在对应的商品数据,则直接去读取数据库,并将读取到的信息重新载入到 Redis 缓存中。如果布隆过滤器判断编号不存在,直接过滤该请求。


<think>嗯,用户想了解如何使用布隆过滤器防止缓存穿透,需要最佳实践和实现方法。首先,我得回顾一下缓存穿透的基本概念,就是请求的数据在缓存和数据库中都不存在,导致每次请求都打到数据库,增加压力。布隆过滤器在这里的作用就是快速判断数据是否存在,从而拦截无效请求。 接下来,我需要整理布隆过滤器的工作原理。布隆过滤器通过多个哈希函数将元素映射到位数组中,查询时如果所有哈希位都为1,可能存在,否则一定不存在。这里要提到误判率,以及如何通过调整位数组大小和哈希函数数量来优化。 然后,最佳实践部分应该包括初始化布隆过滤器、数据预热、查询流程和误判处理。比如,在系统启动时,将数据库中存在的数据加载到布隆过滤器中,查询时先检查布隆过滤器,如果不存在就直接返回,避免访问缓存和数据库。同时,要注意误判的情况,可能需要额外的日志或监控。 实现方法方面,用户可能需要具体的代码示例。比如使用Redis的布隆过滤器模块,或者用Guava库。需要给出Java和Python的代码片段,展示如何初始化、添加数据和查询数据。另外,参数设置也很要,比如位数组大小和哈希函数数量,可以参考一些经验值或计算公式。 还要考虑实际应用中可能出现的问题,比如如何更新布隆过滤器,当数据库新增数据时,需要同步更新布隆过滤器,否则会导致新数据被误判为不存在。另外,分布式环境下的布隆过滤器如何维护一致性,可能需要使用Redis的分布式特性。 用户提供的引用中有提到Spring Boot项目中配置Redis布隆过滤器,可以引用相关代码示例,比如注入BloomFilterHelper和RedisBloomFilter,调用add和contains方法。这部分需要确保代码的正确性,并解释每个步骤的作用。 此外,引用中提到的误判率计算和优化方向也需要涵盖。比如,误判率公式是$(1 - e^{-kn/m})^k$,调整参数m和k来平衡空间和误判率。可能还需要提到动态布隆过滤器或布谷鸟过滤器作为优化方向。 最后,总结应用场景和注意事项,比如数据预热的要性,参数调优的必要性,以及布隆过滤器无法删除元素的限制。可能用户还想知道如何测试和监控布隆过滤器的效果,这部分可以建议添加日志记录误判情况,或者监控数据库的查询压力是否下降。 总之,回答需要结构清晰,从原理到实践,逐步引导用户理解如何部署布隆过滤器防止缓存穿透,同时提供代码示例和参数建议,确保用户能够实际操作并优化。</think># 使用布隆过滤器防止缓存穿透的实践方法 ### 一、缓存穿透问题解析 缓存穿透指**查询不存在的数据**时,请求直接穿透缓存层到达数据库,导致数据库压力骤增。例如恶意攻击者伪造大量不存在的ID发起请求[^3]。 ### 二、布隆过滤器核心原理 1. **数据结构**:$m$位的位数组 + $k$个哈希函数 2. **写入操作**:对元素进行$k$次哈希,将对应位设为1 3. **查询操作**:若所有哈希位均为1则`可能存在`,否则`必然不存在` 4. **误判率公式**: $$ P_{error} = \left(1 - e^{-kn/m}\right)^k $$ 其中$m$为位数组长度,$n$为元素数量[^4] ### 三、最佳实践方案 #### 1. 架构设计 ```python 请求流程: 客户端 -> 布隆过滤器 -> [存在?] -> 查询缓存 -> [命中?] -> 返回数据 | | 否 未命中 | | 拦截请求 查询数据库 -> 回填缓存 ``` #### 2. 实现步骤 **(1) 初始化过滤器** Java示例(使用Guava): ```java BloomFilter<String> filter = BloomFilter.create( Funnels.stringFunnel(UTF_8), 1000000, // 预期元素量 0.01 // 误判率 ); ``` **(2) 数据预热** ```java // 加载数据库已有数据 database.getAllKeys().forEach(key -> filter.put(key)); ``` **(3) 查询拦截** Python示例(使用redisbloom): ```python def get_data(key): if not redis.bf.exists('myFilter', key): return None # 直接拦截 # 继续查询缓存/数据库... ``` #### 3. 参数优化建议 | 元素规模 | 建议位数组大小 | 哈希函数数 | |---------|---------------|-----------| | 100万 | 9.6MB | 7 | | 1000万 | 96MB | 7 | | 1亿 | 960MB | 7 | *基于0.1%误判率计算[^4]* ### 四、Spring Boot整合示例 ```java @Autowired private BloomFilterHelper bloomFilterHelper; // 添加元素 redisBloomFilter.add(bloomFilterHelper, "user_filter", userId); // 查询判断 if(!redisBloomFilter.contains(bloomFilterHelper, "user_filter", userId)){ return Result.error("数据不存在"); } ``` *需配置RedisBloomFilter和BloomFilterHelper[^5]* ### 五、注意事项 1. **数据更新同步**:新增数据时需同步更新布隆过滤器 2. **误判监控**:建议记录误判日志,定期分析误判率 3. **容量规划**:提前预留20%空间余量应对数据增长 4. **冷启动方案**:采用渐进式预热避免服务启动时数据库过载
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈亦康

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值