BloomFilter:布隆过滤器和Redis缓存穿透

布隆过滤器原理及应用
本文介绍布隆过滤器的基本原理与特性,探讨其如何通过牺牲存储空间换取高效的查询速度,尤其适用于大数据环境中快速判断元素是否存在的情况。此外,还讨论了布隆过滤器在解决缓存穿透问题上的应用。

概念

BloomFilter,中文名叫布隆过滤器,用于判断目标值是否存在。

例子

设一个队伍有10人。需要判断张三是否在其中。
常规的思路就是遍历这10人。然而随着人数规模的增加,遍历所需要的时间会越来越长。
因此考虑这样一个方案:维护一个辅助list,其元素值为0或1,表示该人是否在队伍中。索引使用每个人姓名的笔画数。例如张三,姓名笔画数是10画,那么list[10]就设为1。这样,当我们需要判断张三是否在队伍中时,先算出笔画数10,然后看list[10]是否为1。若list[10]为0,则说明张三一定不在队伍里,就不需要遍历队伍了。
然而这方案有个很明显的特点:list[10]为1,并不能说明张三一定在,因为有跟张三姓名笔画相同的人;但list[10]为0,说明张三一定不在。于是,当list[10]为1时,就去遍历一下队伍,看张三是否在。
这也意味着,如果张三要从队伍中去掉,则需要遍历整个队伍看是否还有其他人的姓名笔画也是10。当确定没有这样的人时,才能将list[10]由1改为0。
可以很明显地看出,相比于直接遍历的方案,该方案需要一个额外的辅助list空间。

原理

如上,就是BloomFilter的流程。
关于辅助list,BloomFilter定义了一个位列阵,其元素值是一个二进制,仅为0或1。默认为0。
BloomFilter是一个典型的用空间换时间的算法。

存储

关于辅助list空间的索引,为了尽可能减少冲突带来的影响,BloomFilter使用了三次hash。也就是一个数据,对其进行三次不同的hash,分别得到三个值k、l、m。然后使用这三个值作为位列阵的索引值,将对应元素值设为1。

查找

当需要判断某个数据是否存在时,对该数据进行三次hash,分别得到k、l、m。然后使用这三个值作为位列阵的索引值,查询对应的元素值。
若三个元素值都为1,则说明该元素可能是存在的;但若至少有一个元素值为0,则说明该元素一定是不存在的。
对于一个元素,若其三个位列阵的点位值都是1,但该元素实际却并不存在,则称之为误判。

删除

就像上述例子中说的,若要删除某个元素,则需对所有元素进行遍历才能确定位列阵中该元素对应的三个值是否可改为0。因此删除困难。一般来说,只添加,不删除。

特点

对于一个数据,若其通过了BloomFilter判定,则不一定在数据库中。但若未通过,则一定不在。
可以看出,BloomFilter有如下优点:

  • 插入/查询时间复杂度是O(1),非常快。
  • 并不存储数据本身,保密性好。
  • 可对所有数据实现映射。

对应地,BloomFilter有如下缺点:

  • 随着元素增多,误算率也会增加。
  • 只能添加元素,不能删除元素。

Redis中BloomFilter的应用

Redis对BloomFilter进行了应用。其实现方式有两种:使用Redis的bitmap实现;使用布隆过滤器插件实现。
根据原理可以知道,BloomFilter需要设置一个初始大小,用于表示预计放入的元素数量。可以想象,该值若过小,则hash冲突的元素就会增多,从而导致误判率的提升。因此该值应设置较大一些。

缓存穿透

对一个数据访问,先判断是否在Redis中,若是则返回;若否则查询数据库,并将查到的值设置到Redis中。这样下次查询就可以顺利查到了。
但若一个数据访问,既不在Redis中也不在数据库中,那么该访问将先访问Redis再访问数据库。若再次访问该数据,则该流程会再次重复。
于是,攻击黑客就发送大量针对不存在数据的请求,那么上述访问流程就会反复重复,导致数据库承担大量压力。在该流程中缓存这道墙形同虚设,称为缓存穿透。
BloomFilter就可以很好地解决该问题:一个针对虚假数据的访问,Redis借助BloomFilter可以很明确地判断出该数据一定不在数据库中,于是就直接返回,不再查询数据库。

缓存穿透和缓存击穿

注意区别二者的概念:

  • 缓存穿透:Redis和数据库中都没有目标数据,每次都要先查Redis再查数据库。
  • 缓存击穿:Redis和数据库中存在目标数据,但Redis的数据过期了。此时有大量并发请求访问该数据,导致这些请求全部发送到数据库上,为数据库带来巨大压力。
<think>好的,我需要帮助用户了解如何使用布隆过滤器解决Redis缓存穿透问题。首先,我得回忆一下缓存穿透的概念。缓存穿透是指大量请求查询不存在的数据,导致请求直接落到数据库,增加数据库压力。布隆过滤器在这里的作用是快速判断请求的数据是否存在,从而拦截无效请求。 接下来,我需要整理布隆过滤器的工作原理。布隆过滤器由位数组多个哈希函数组成。添加元素时,多个哈希函数生成不同的索引,将位数组对应位置设为1。查询时,如果所有哈希位置都为1,可能存在;否则一定不存在。但要注意,布隆过滤器可能存在误判,即判断存在时可能实际不存在,但判断不存在时则一定不存在。这点需要强调。 然后,结合引用中的资料,如何将布隆过滤器集成到Redis中呢?根据引用[1][5],Redis的Bloom Filter模块提供了BF.ADDBF.EXISTS命令,或者使用Redisson客户端初始化布隆过滤器。需要详细说明步骤,比如初始化布隆过滤器、预热数据、查询流程等。 最佳实践部分,参考引用[4]提到的参数选择,比如预期数据量误判率,合理设置布隆过滤器的参数。同时,要提到即使使用布隆过滤器,也需要结合其他方法,比如缓存空值接口校验,形成多层防护。 可能的问题点包括误判率数据同步。需要解释如何通过增加位数组大小哈希函数数量来降低误判率,以及数据更新时如何同步到布隆过滤器,比如使用双布隆过滤器或者在低峰期批量更新。 最后,确保回答结构清晰,分步骤说明,并引用相关资料。检查是否覆盖了用户的问题,包括原理、实现步骤、代码示例、注意事项等,并生成相关问题供用户进一步学习。</think>### 如何用布隆过滤器防止 Redis 缓存穿透?最佳实践分步解析 #### 1. 缓存穿透问题概述 缓存穿透是指大量请求查询**不存在于缓存数据库**的数据(如无效ID或恶意构造的键),导致请求直接穿透数据库,引发性能风险。例如:频繁查询`user_id=-1`的数据[^4]。 #### 2. 布隆过滤器核心原理 布隆过滤器(Bloom Filter)是一种概率型数据结构,通过以下机制拦截无效请求: - **数据结构**:一个长度为$m$的二进制位数组 + $k$个独立的哈希函数 - **添加元素**:对元素$x$进行$k$次哈希,将位数组对应位置设为1 例如:添加`user_100`时,哈希函数生成$h_1=3$、$h_2=7$、$h_3=9$,则位数组的3、7、9位设为1 - **查询元素**:若所有哈希位置均为1,则判定**可能存在**;否则**一定不存在** 公式:$BF(x)=1 \iff \forall i \in [1,k], \text{bit}[h_i(x)]=1$ #### 3. 实现步骤(结合Redis) ##### 步骤1:初始化布隆过滤器 使用Redis的Bloom Filter模块(需Redis 4.0+)或第三方库(如Redisson): ```python # Python示例:使用RedisBloom from redisbloom.client import Client rb = Client() rb.bfCreate('user_filter', 0.01, 100000) # 预期容量10万,误判率1% ``` 或通过Redisson(Java)[^5]: ```java // Java示例 RBloomFilter<String> bloomFilter = redisson.getBloomFilter("user_filter"); bloomFilter.tryInit(100000L, 0.01); // 参数同上 ``` ##### 步骤2:预热数据 将数据库中所有有效键预先加入布隆过滤器: ```python # 从数据库读取所有有效用户ID valid_user_ids = db.query("SELECT id FROM users") for uid in valid_user_ids: rb.bfAdd('user_filter', uid) ``` ##### 步骤3:查询拦截流程 ```mermaid graph TD A[客户端请求user_id=x] --> B{布隆过滤器检查x存在?} B -- 不存在 --> C[直接返回空或错误] B -- 存在 --> D[查询Redis缓存] D -- 命中 --> E[返回缓存数据] D -- 未命中 --> F[查询数据库并回填缓存] ``` #### 4. 关键优化方向 1. **参数选择**(引用[3]的参考表): - 位数组大小$m$与预期元素数量$n$、误判率$p$的关系: $$m = -\frac{n \ln p}{(\ln 2)^2}$$ - 哈希函数数量$k$: $$k = \frac{m}{n} \ln 2$$ - 示例:$n=10^6$,$p=0.1\%$时,$m≈14.4MB$,$k=10$ 2. **降低误判率**: - 使用双重布隆过滤器(主备结构) - 定期重建过滤器(如每天凌晨低峰期) 3. **数据同步策略**: ```python # 新增数据时同步更新布隆过滤器 def add_user(user_id): db.insert(user_data) rb.bfAdd('user_filter', user_id) # 原子操作保证一致性 redis.set(f"user:{user_id}", user_data) ``` #### 5. 最佳实践组合方案(引用[4]) | 方案 | 实现方式 | 作用 | |------|----------|------| | 布隆过滤器 | 拦截绝对不存在请求 | 第一层防御 | | 缓存空值 | 对查询结果为空时缓存`NULL`,TTL=5分钟 | 减少重复穿透 | | 接口校验 | 拦截ID≤0或格式错误的请求 | 前置过滤 | #### 6. 代码示例(完整流程) ```java // 引用[2]的Java示例扩展 public User getUserById(String id) { // 1. 布隆过滤器检查 if (!bloomFilter.mightContain(id)) { return null; // 直接拦截 } // 2. 查询Redis缓存 User user = redis.get(id); if (user != null) { return user; } // 3. 查询数据库 user = db.query("SELECT * FROM users WHERE id=?", id); if (user == null) { redis.setex(id, 300, "NULL"); // 缓存空值5分钟 } else { redis.setex(id, 3600, user); // 缓存有效数据1小时 } return user; } ``` #### 7. 注意事项 - **误判容忍度**:需根据业务场景选择可接受的误判率(如资讯类应用可接受0.1%,金融交易需更严格) - **数据删除问题**:布隆过滤器不支持删除操作,若业务需要删除数据,可改用Counting Bloom Filter - **冷启动问题**:系统初始化时需确保全量数据已加载到过滤器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值