缓存穿透是什么?怎么解决?#
当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透的问题。

最常见的场景就是有攻击者伪造了大量的请求,请求某个不存在的数据。这会造成两个后果。
- 缓存里面没有对应的数据,所以查询会落到数据库上。
- 数据库也没有数据,所以没有办法回写缓存,下一次请求同样的数据,请求还是会落到数据库上。
解决手段:
- **回写特殊值:**在缓存未命中,而且数据库里也没有的情况下,往缓存里写入一个特殊的值。这个值就是标记数据不存在,那么下一次查询请求过来的时候,看到这个特殊值,就知道没有必要再去数据库里查询了。但如果如果攻击者每次都用不同的且都不存在的 key 来请求数据,那么"回写特殊值"这种手段会丧失它的效果,而且因为要回写特殊值,会浪费不少 Redis 的内存。这可能会进一步引起另外一个问题,就是 Redis 在内存不足,执行淘汰的时候,把其他有用的数据淘汰掉,而更好的就是考虑使用布隆过滤器
- 使用布隆过滤器:布隆过滤器是一种快速判断元素是否存在的数据结构,它可以在很小的内存占用下,快速判断一个元素是否在一个集合中。将所有可能存在的数据哈希到一个足够大的位数组中,当一个请求过来时,可以先通过查询布隆过滤器快速判断数据是否存在,如果不存在,则直接返回,避免对数据库的查询。
- 限流策略:针对频繁请求的特定数据,可以设置限流策略,例如使用令牌桶算法或漏桶算法,限制对这些数据的请求频率,减轻数据库的压力。
回答#
缓存穿透是指查询一个不存在的数据,由于缓存未命中,请求直接穿透到数据库,导致数据库压力骤增。这种现象通常由恶意攻击或错误查询引发。
有很多解决策略,比如:
- 布隆过滤器:在缓存层前设置布隆过滤器,快速判断数据是否存在,避免无效查询。
- 缓存空对象(回写特殊值):即使查询结果为空,也往缓存里写入一个特殊的值,这个值就是标记数据不存在,设置较短过期时间,防止重复查询冲击数据库。
- 限流策略:针对频繁请求的特定数据,可以设置限流策略
通过这些措施,可以有效缓解缓存穿透问题,保护数据库不受过度查询的压力。
布隆过滤器是怎么工作的?#
布隆过滤器由初始值都为0的位图数组和N个哈希函数两部分组成。在写入数据库数据时,在布隆过滤器里做个标记,这样下次查询数据是否在数据库时,只需要查询布隆过滤器,如果查询到数据没有被标记,说明不在数据库中。
- 第一步,使用 N 个哈希函数分别对数据做哈希计算,得到 N 个哈希值
- 第二步,将第一步得到的 N 个哈希值对位图数组的长度取模,得到每个哈希值在位图数组的对应位置
- 第三步,将每个哈希值在位图数组的对应位置的值设置为 1
举个例子,假设有一个位图数组长度为 8,哈希函数 3 个的布隆过滤器。

在数据库写入数据 x 后,把数据 x 标记在布隆过滤器时,数据 x 会被 3 个哈希函数分别计算出 3 个哈希值,然后在对这 3 个哈希值对 8 取模,假设取模的结果为 1、4、6,然后把位图数组的第 1、4、6 位置的值设置为 1。当应用要查询数据 x 是否数据库时,通过布隆过滤器只要查到位图数组的第 1、4、6 位置的值是否全为 1,只要有一个为 0,就认为数据 x 不在数据库中。
布隆过滤器由于是基于哈希函数实现查找的,高效查找的同时存在哈希冲突的可能性,比如数据 x 和数据 y 可能都落在第 1、4、6 位置,而事实上,可能数据库中并不存在数据 y,存在误判的情况。
所以,查询布隆过滤器说数据存在,并不一定证明数据库中存在这个数据,但是查询到数据不存在,数据库中一定就不存在这个数据。
布隆过滤器有什么缺陷?#
布隆过滤器特性:
高效存储:相比哈希表,布隆过滤器可以用很小的空间存储大规模数据。
无误判删除:无法删除元素(除非使用变体,如计数布隆过滤器)。
存在误判率:可能误判元素存在,但不会误判不存在。
无法获取原始数据:只能用于查询,不能存储数据本身。
其中布隆过滤器最重要的缺陷就在于无法确定一个数据是一定存在,只能判断数据一定不存在,所以适用场景也受这个缺陷(特性)的制约,所以这个缺陷(特性)一定要提到,其它的比如无法删除可以酌情扩展。
回答#
布隆过滤器由于是基于哈希函数实现查找的,会存在哈希冲突的可能性,数据可能落在相同位置,存在误判的情况。查询布隆过滤器说数据存在,并不一定证明数据库中存在这个数据,但是查询到数据不存在,数据库中一定就不存在这个数据。
不支持一个关键字的删除,因为一个关键字的删除会牵连其他的关键字。改进方法就是counting Bloom filter,用一个counter数组代替位数组,就可以支持删除了。
缓存击穿是什么?怎么解决?#
如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮。

缓存击穿的关注点是热点数据不存在于缓存上,因为如果是普通数据,那就是正常的缓存未命中,反之热点数据不在缓存中了,容易导致大量请求落到数据库上
解决手段:
- 设置热点数据的热度时间窗口:对于热点数据,可以设置一个热度时间窗口,在这个时间窗口内,如果一个数据被频繁访问,就将其缓存时间延长,避免频繁刷新缓存导致缓存击穿。
- 使用互斥锁或分布式锁:在缓存失效时,只允许一个线程去查询数据库,其他线程等待查询结果。可以使用互斥锁或分布式锁来实现,确保只有一个线程能够查询数据库,其他线程等待结果,避免多个线程同时查询数据库造成数据库压力过大。
- 缓存永不过期:对于一些热点数据,可以将其缓存设置为永不过期,避免缓存击穿。
- 异步更新缓存:在缓存失效时,可以异步地去更新缓存,而不是同步地去查询数据库并刷新缓存。这样可以减少对数据库的直接访问,并且不会阻塞其他请求的响应。
回答#
如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮。
我们可以通过延长热点数据过期时间、使用互斥锁或分布式锁控制查询频率、缓存永不过期等手段来解决这个问题。
缓存雪崩是什么?怎么解决?#

- 场景1:缓存集中失效。比如为了双十一,将一批商品放入到缓存中,设置两小时过期,凌晨2点过期了,导致对这一批商品的访问都落到了数据库,数据库就会产生压力波峰。
- 场景2:当然缓存雪崩一个最严重特殊情况是,在流量高峰,一个缓存节点直接出现问题,甚至扩大到缓存集群出现问题,那么就会导致本应该访问缓存的流量透传到数据库上。
解决手段:
大量数据同时过期的解决手段:
- 设置缓存数据的随机过期时间:在设置缓存数据的过期时间时,加上一个随机值,使得不同的缓存数据在过期时刻不一致。这样可以避免大量数据同时过期,减轻数据库负荷。
- 分布式锁或互斥锁:在缓存失效时,使用分布式锁或互斥锁来保证只有一个请求可以重新加载缓存。其他请求等待该请求完成后,直接从缓存中获取数据。这样可以避免多个请求同时访问数据库。
- 数据预热:在系统启动或者非高峰期,提前将热点数据加载到缓存中,预热缓存。这样即使在高并发时,也能够从缓存中获取到数据,减轻数据库的压力。
- 后台更新缓存:业务线程不再负责更新缓存,缓存也不设置有效期,而是让缓存“永久有效”,并将更新缓存的工作交由后台线程定时更新。
- 数据库优化:对于缓存雪崩问题,除了缓存层面的应对策略,还可以从数据库层面进行优化,如提升数据库性能、增加数据库的容量等,以应对大量请求导致的数据库压力。
Redis故障宕机的解决手段:
- 服务熔断或请求限流机制:启动服务熔断机制,暂停业务应用对缓存服务的访问,直接返回错误,所以不用再继续访问数据库,保证数据库系统的正常运行,等到 Redis 恢复正常后,再允许业务应用访问缓存服务。服务熔断机制是保护数据库的正常允许,但是暂停了业务应用访问缓存服系统,全部业务都无法正常工作。也可以启用请求限流机制,只将少部分请求发送到数据库进行处理,再多的请求就在入口直接拒绝服务。
- 提高缓存本身的可用性:通过主从节点的方式构建 Redis 缓存高可靠集群,如果 Redis 缓存的主节点故障宕机,从节点可以切换成为主节点,继续提供缓存服务,避免了由于 Redis 故障宕机而导致的缓存雪崩问题。
回答#
缓存雪崩是指缓存中大量数据同时过期,导致所有请求直接访问数据库,造成数据库压力激增甚至崩溃。解决方法包括:
- 设置缓存过期时间的随机值,避免同时过期;
- 使用分布式锁或队列控制数据库访问,防止瞬间高并发;
- 实现缓存高可用,如主从复制或集群部署,确保缓存服务不中断;
- 热点数据永不过期,定时异步更新缓存;
- 提前预加载缓存,确保数据在过期前已更新。通过这些措施,可以有效缓解缓存雪崩问题。

