【redis——缓存穿透】

缓存穿透

Redis缓存穿透是指

  • 查询一个根本不存在的数据,即客户端请求的数据​​既不在缓存中,也不在数据库中​​
  • 导致请求绕过缓存层直接访问数据库的现象。即每次请求都会直接打到数据库,从而绕过缓存保护机制。
  • 这种现象在高并发场景下会对数据库造成巨大压力,甚至可能导致数据库崩溃。

缓存穿透的解决方案

缓存空对象(Null Object)

当数据库查询为空时,仍然将空结果(如"NULL"或空对象)缓存起来,并设置较短的过期时间。

优点​​
实现简单直接
能有效防止重复穿透

​​缺点​​
可能缓存大量无效key,占用内存空间
可能发生数据不一致的问题

布隆过滤器(Bloom Filter)

布隆过滤器(Bloom Filter)是一种空间效率极高的概率型数据结构,由Burton Howard Bloom于1970年提出,用于快速判断一个元素是否可能存在于集合中。

布隆过滤器的核心原理

布隆过滤器的核心思想是​​通过多个哈希函数和一个位数组来表示集合元素的存在性​​,具有以下显著特点:

​​空间效率极高​​:仅需存储二进制位,无需保存元素本身
​​概率型判断​​:可能存在误判(False Positive),但绝不会漏判(False Negative)
​​查询速度快​​:时间复杂度为O(k),k为哈希函数数量
​​不支持删除​​:标准布隆过滤器不支持删除操作

误判机制
占用位: 2,5,8
已添加: apple
未添加: orange
检测位: 2,5,8
冲突导致误判
查询元素
计算哈希值
查询元素
h₁(e)→2
h₂(e)→5
h₃(e)→8
读取位标志
所有位=1?
返回: 可能存在
(可能误判)
返回: 肯定不存在
添加元素
计算哈希值
输入元素
h₁(e)→2
h₂(e)→5
h₃(e)→8
设置位标志
位数组状态:
[0,0,1,0,0,1,0,0,1,0]
### Redis缓存穿透的解决方案及其原理 #### 1. 使用布隆过滤器(Bloom Filter) 布隆过滤器是一种空间效率高的概率型数据结构,用于测试某个元素是否属于集合的一部分。其核心思想是在请求到达缓存之前,通过布隆过滤器快速判断该键是否存在。如果布隆过滤器返回“不存在”,则可以直接拒绝访问数据库,从而避免缓存穿透的发生。 布隆过滤器的工作原理基于多个哈希函数和位数组。对于每一个加入集合的元素,它会被多个哈希函数映射到位数组中的不同位置,并将这些位置置为1。查询时,只需检查对应的位是否全为1即可得出结论[^5]。然而需要注意的是,布隆过滤器可能会产生误判,即认为某些不存在的元素存在,但这并不会直接影响系统的安全性,因为最终仍然可以通过数据库验证真伪。 ```java // 布隆过滤器初始化示例 import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; public class BloomFilterExample { private static final int EXPECTED_INSERTIONS = 1000000; // 预期插入数量 private static final double FPP = 0.01; // 可接受的错误率 public static void main(String[] args) { BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), EXPECTED_INSERTIONS, FPP); String keyToCheck = "nonexistentKey"; if (!bloomFilter.mightContain(keyToCheck)) { System.out.println("Key does not exist in database."); } else { // Proceed to check the actual database. } } } ``` #### 2. 缓存空对象 另一种简单的解决方法是对那些确实不存在的数据,在第一次查询后将其设置为空值并存储到缓存中。这样后续对该数据的任何请求都可以直接从缓存获取而无需再次查询数据库。不过这种方法也有缺点:如果恶意用户不断尝试不同的不存在键,则会造成大量无意义的缓存条目占用内存资源[^1]。 为了缓解这一问题,可以设定较短的有效时间来定期清理这些空对象记录。 ```lua -- Lua脚本实现缓存空对象逻辑 if redis.call('GET', KEYS[1]) == nil then -- 如果缓存中没有找到对应key,则向DB发起查询... local valueFromDb = fetchValueFromDatabase(KEYS[1]) if valueFromDb ~= nil then redis.call('SETEX', KEYS[1], ARGV[2], valueFromDb) else -- 对于null/nil的结果也进行缓存处理 redis.call('SETEX', KEYS[1], '0', ARGV[1]) end return valueFromDb or '' end return redis.call('GET', KEYS[1]) ``` #### 3. 加锁机制(Mutex Locking) 加锁策略适用于高并发环境下的单次加载情况。当发现某项数据不在缓存中时,程序不会立刻去读取数据库,而是先尝试获得一个分布式锁。只有成功拿到锁的那个线程才会继续执行数据库查询并将结果更新至缓存;其他未能取得锁的任务则需等待一段时间后再重新尝试获取缓存内容[^4]。 此方式能够有效减少重复计算以及不必要的IO开销,但在极端条件下仍可能出现短暂的服务延迟现象。 --- ### 总结 以上介绍了三种主流应对Redis缓存穿透的技术手段——分别是利用高效的空间节省算法构建前置屏障、合理管理零值反馈周期以及引入同步控制单元防止竞争条件引发额外负载等问题。具体选用哪种方案取决于应用场景的具体需求和技术栈现状等因素综合考量决定。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值