一.什么是缓存穿透?
缓存穿透是指当客户端请求的数据在缓存(如 Redis)中不存在,并且在数据库中也不存在时,直接绕过缓存去请求数据库。这种情况会导致:
-
缓存系统无法发挥作用,数据每次都会直接请求数据库。
-
在某些恶意请求的情况下(例如攻击者故意频繁请求不存在的数据),会产生大量查询请求,给数据库带来很大的压力,可能导致数据库宕机或服务性能大幅下降。
缓存穿透通常是由查询一个在数据库中根本不存在的对象引起的,特别是当没有合理的过滤机制时,攻击者可以发送大量的无效请求,这些请求由于缓存中没有,因此每次都会打到数据库,极大增加了数据库的负担。图例如下
二.常见的两种解决方案-----缓存空对象和布隆过滤器。
方案一:缓存空对象
缓存空对象的做法是指:当缓存和数据库中都查不到某个请求的数据时,将一个空对象(如null
值或占位符)缓存起来,并且设置一个合理的过期时间(TTL,Time To Live),从而避免相同的无效请求反复查询数据库。
优点
- 实现简单,维护方便:这种方法不需要额外的复杂逻辑,只需简单地在查不到数据时将结果缓存起来,并设置一个过期时间。后续如果有相同的数据请求,就可以直接从缓存中返回空值,而不再查询数据库。
缺点
- 内存消耗:缓存空对象的做法虽然有效避免了频繁访问数据库,但会在缓存中存储大量空对象,可能会造成额外的内存消耗,尤其是在高并发请求下,存储这些空数据会占用一定的内存空间。
- 数据不一致问题:如果数据在短时间内被插入数据库,而之前的空对象缓存还没有过期,可能会导致短时间内的数据不一致问题。解决办法是合理地设置TTL,让空对象缓存尽快过期。
具体流程
- 请求 Redis:客户端首先向缓存(Redis)发出请求。
- 未命中:如果缓存中没有该数据,则继续向下一个数据源(即数据库)请求。
- 请求数据库:缓存未命中的情况下,会请求数据库。
- 如果数据库中也没有相应的数据,就将
null
值或者空对象存储到缓存中,并设置TTL,避免后续重复请求同样不存在的数据直接查询数据库。 - 返回数据:如果数据库中有数据,则将数据缓存到 Redis,并返回给客户端。
这种方案适合那些请求频次较高但数据缺失的情况,通过缓存空对象来防止数据库压力过大。
方案二:布隆过滤器
布隆过滤器-----是一种空间效率非常高的概率性数据结构,它能够判断某个数据是否存在于集合中。布隆过滤器可以有效减少无效请求对数据库的影响。
工作原理
布隆过滤器通过多个哈希函数将数据映射到一个位数组中,如果某个数据不在位数组中,则可以确定数据在数据库中也不存在。反之,如果布隆过滤器认为数据存在,它有可能存在,但也可能是误判(因为布隆过滤器存在一定的误报率)。
优点
- 内存占用少:布隆过滤器的空间复杂度很低,即使处理海量数据也不会占用过多内存。
- 有效减少数据库请求:由于布隆过滤器能够判断某个请求是否必定不在数据库中,对于那些不在数据库中的请求,布隆过滤器会直接拒绝掉,防止其打到数据库。
缺点
- 存在误判的可能性:布隆过滤器是一个概率性数据结构,存在一定的误判率,也就是说,布隆过滤器可能会误判某个数据存在,但实际上它并不存在。这种情况下,数据仍会打到数据库,增加一定的压力。
- 实现复杂:布隆过滤器的实现和维护比缓存空对象要复杂,尤其是当需要动态调整数据时,如何扩展位数组、调整哈希函数等都需要额外的考虑。
图示流程
- 请求布隆过滤器:客户端请求数据时,首先会通过布隆过滤器判断数据是否存在。
- 数据不存在:如果布隆过滤器判断数据不存在,那么请求会被直接拒绝掉,避免继续访问缓存或数据库。
- 放行请求:如果布隆过滤器判断数据可能存在,则允许请求继续。
- 请求 Redis:接下来,客户端请求 Redis 缓存。
- 缓存命中:如果缓存中存在数据,则直接返回数据。
- 缓存未命中:如果缓存中没有该数据,系统会继续请求数据库。
- 查询数据库:如果缓存和布隆过滤器都没法满足请求,则会查询数据库。
- 缓存数据:在数据库中查询到数据后,将数据缓存到 Redis,并返回给客户端。
三.两种方案的比较
-
缓存空对象-----------更适合于简单实现和维护,适合那些在请求频次高且不存在数据的情况下,通过缓存空对象来防止对数据库的频繁访问。但需要注意内存占用和数据一致性的问题。
-
布隆过滤器-----------适用于需要处理大量请求且数据比较稀疏的场景。它通过高效的内存占用来判断请求的数据是否可能存在,减少无效的数据库查询。但是布隆过滤器存在误判可能,且实现起来更为复杂,适用于对内存要求较高的应用。