缓存穿透是指在缓存中不存在某个数据,且数据库中也不存在该数据的情况下,用户不断发起请求,导致请求直接穿透缓存,打到数据库,进而造成数据库压力过大,甚至宕机。这种情况往往是由于恶意攻击者或者系统设计不完善引起的。例如,如果一个系统允许用户通过 id
查询数据,但请求的 id
不存在,那么查询就会跳过缓存,直接访问数据库。
1. 缓存穿透的产生原因
1.1 频繁请求不存在的缓存数据
如果缓存中没有某个 key
对应的值,而请求不断命中这些不存在的数据,会每次都去查询数据库,给数据库造成压力。
1.2 恶意攻击
恶意用户可能会频繁发送请求,查询一些不合法的或根本不存在的数据,导致缓存无法生效,频繁命中数据库。
2. 解决缓存穿透的方法
2.1 对请求增加校验机制
- 输入参数的合法性校验:对传入的查询参数进行校验,比如如果查询
id
小于等于 0,或不符合业务规则,则直接返回,不去查询数据库和缓存。这样可以避免一些恶意请求带来的压力。 - 用户权限验证:在某些情况下,可以通过验证用户的权限来减少无效请求。
public User getUserById(Long id) {if (id <= 0) {return null; // 不合法的id,直接返回}return userRepository.findById(id).orElse(null);
}
2.2 缓存空值或特殊值
- 当数据库中不存在某个数据时,缓存一个空值(比如
null
或者一个特殊的占位符),这样下次再请求相同数据时,直接从缓存返回空值,而不会再次查询数据库。需要注意的是,缓存空值的时间通常会设置较短,以避免长期占用缓存空间。
// 通过unless指定条件
@Cacheable(value = "users", key = "#id", unless = "#result == null")
public User getUserById(Long id) {// 查数据库,如果没有数据返回nullreturn userRepository.findById(id).orElse(null);
}
在这个例子中,当数据库中没有该 id
对应的用户时,缓存会保存 null
,从而避免下次请求再次访问数据库。
2.3 布隆过滤器(Bloom Filter)
2.3.1 什么是布隆过滤器
布隆过滤器(Bloom Filter)是一种数据结构,用于快速判断一个元素是否属于一个集合中。
它使用多个Hash函数将一个元素映射成一个位阵列(Bit array)中的一个点,将Bit array理解为一个二进制数组,数组元素是0或1。
当一个元素加入集合时,通过N个散列函数将这个元素映射到一个Bit array中的N个点,把它们设置为1。
检索某个元素时再通过这N个散列函数对这个元素进行映射,根据映射找到具体位置的元素,如果这些位置有任何一个0,则该元素一定不存在,如果都是1很可能存在误判。
- 布隆过滤器是一种空间效率非常高的数据结构,可以快速判断一个元素是否存在。通过在缓存查询之前先使用布隆过滤器来判断请求的
key
是否可能存在,如果布隆过滤器判断key
不存在,则可以直接拒绝请求,不再访问数据库和缓存,从而有效防止缓存穿透。 - 布隆过滤器可能会有少量的误判(判断某个不存在的元素为存在),但不会出现漏判(判断存在的元素为不存在)。因此,这种方法适合用于大规模数据场景,降低缓存穿透带来的压力。
2.3.2 哈希函数的基本特性
同一个数使用同一个哈希函数计算哈希值,其哈希值总是一样的。
对不同的数用相同的哈希函数计算哈希值,其哈希值可能一样,这称为哈希冲突。
哈希函数通常是单向的不可逆的,即从哈希值不能逆向推导出原始输入。这使得哈希函数适用于加密和安全应用。
2.3.3 为什么会存在误判
主要原因是哈希冲突。布隆过滤器使用多个哈希函数将输入的元素映射到位数组中的多个位置,当多个不同的元素通过不同的哈希函数映射到相同的位数组位置时就发生了哈希冲突。
由于哈希函数的有限性,不同的元素可能会映射到相同的位置上,这种情况下即使元素不在布隆过滤器中可能产生误判,即布隆过滤器判断元素在集合中。
2.3.4 如何降低误判率
增加Bit array空间,减少哈希冲突,优化散列函数,使用更多的散列函数。
2.3.5 布隆过滤器的优缺点
布隆过滤器的优点是:二进制数组占用空间少,插入和查询效率高效。
缺点是:存在误判率,并且删除困难,因为同一个位置由于哈希冲突可能存在多个元素,删除某个元素可能删除了其它元素。
2.3.6 布隆过滤器的应用场景
1、海量数据去重,比如URL去重,搜索引擎爬虫抓取网页,使用布隆过滤器可以快速判定一个URL是否已经被爬取过,避免重复爬取。
2、垃圾邮件过滤:使用布隆过滤器可以用于快速判断一个邮件地址是否是垃圾邮件发送者,对于海量的邮件地址,布隆过滤器可以提供高效的判定。
3、安全领域:在网络安全中,布隆过滤器可以用于检查一个输入值是否在黑名单中,用于快速拦截一些潜在的恶意请求。
4、避免缓存穿透:通过布隆过滤器判断是否不存在,如果不存在则直接返回。
2.3.7 布隆过滤器使用示例
- 初始化布隆过滤器时,将数据库中所有有效的
id
加入到布隆过滤器中。 - 每次查询时,先判断布隆过滤器是否可能存在该
id
,如果不存在则直接返回,避免查询缓存和数据库。
BloomFilter<Long> bloomFilter = BloomFilter.create(Funnels.longFunnel(), 1000000);public User getUserById(Long id) {// 先通过布隆过滤器判断id是否存在if (!bloomFilter.mightContain(id)) {return null; // 如果布隆过滤器判断不存在,则直接返回}// 如果布隆过滤器判断可能存在,则继续查缓存或数据库return userRepository.findById(id).orElse(null);
}