Skip to content

缓存

什么是缓存

从我的理解来看,缓存就是一块临时的存储空间,用来存放访问频次较高的数据。本质上是用「空间」换「响应速度」,核心目标是减轻数据库的查询压力

从这个概念中,可以提炼出几个关键点:

  1. 临时:为了避免存储空间的浪费,缓存数据应尽量设置过期时间,到期后自动销毁。
  2. 存储空间:一般选择读写性能较高的内存(本地内存或共享内存),部分场景下也会结合 SSD 进一步提升性能。
  3. 访问频次较高的数据:为了避免浪费存储空间,应尽量只缓存访问频次较高的数据,切莫把任何数据都放进缓存。
  4. 数据库的查询压力:将一些复杂的数据库查询结果进行缓存,可以减少数据库的访问压力,从而提升用户的响应速度。

了解了基础概念之后,我们再来看缓存使用中常见的几个问题,这也是面试中经常被问到的高频考点。

缓存的三个经典问题

如果缓存没有用好,可能会引发一些难以发现和定位的线上事故,主要有三类:缓存雪崩缓存穿透缓存击穿

一、缓存雪崩

概念中提到了「临时」这个关键词,因此大部分缓存数据都有过期时间。假设有一批数据是通过定时任务从数据库写入缓存的,并且统一设置了相同的过期时间。当这批数据在同一时间节点集体过期、而数据库侧又因某些原因没有及时写入新数据时,所有请求都会同时打向数据库,导致数据库查询压力骤增,甚至并发过大而瘫痪、无法正常服务。

那么应该如何应对呢?

  1. 避免所有数据设置同一个过期时间节点,应根据数据类型、数据更新时效性来设置。
  2. 数据过期时间应大于数据更新周期,同时要考虑更新耗时,并增加更新失败的异常告警提示。
  3. 对于一些访问频次较高或数据库查询压力较大的数据,可以不设置过期时间,改为由程序主动控制该数据的移除或更替。

二、缓存穿透

概念中提到了「访问频次较高的数据」,这里就会出现一种情况:比如某个查询结果一直是空数据,空数据按理不属于访问频次高的数据,所以虽然经过了缓存层,但缓存中并没有这份空数据,请求便直接穿透进入了数据库。尽管数据库查询结果也是空,但仍然承受了一次查询压力。这种现象就是缓存穿透——击穿了缓存,直接前往数据库查询。

那么应该如何应对呢?

  1. 过滤非法请求:比如一些从参数上就能判断为空的数据,可以直接在程序层面处理。
  2. 缓存空结果:为了提升性能,可以将查询为空的结果也缓存起来(建议设置较短的过期时间),这样下次用户再访问时,可以直接从缓存中判断并返回。
  3. 使用布隆过滤器:空数据较多时,缓存空结果会浪费内存空间。可以将这些空数据的键名通过布隆过滤器记录到缓存中,尽可能减少内存占用,同时查询判断也更高效。

三、缓存击穿

这个概念与缓存雪崩有些类似,但它不是大面积的缓存集体失效,而是某一个访问频次极高的热点数据突然失效,导致这一刻的高并发请求全部穿透到了数据库。数据库并发压力升高、响应变慢,进一步还可能引发数据库异常,影响其他业务。

那么应该如何应对呢?

  1. 对于访问频次高、查询较为复杂的数据,可以不设置过期时间,但需要程序去维护数据的更替与删除。
  2. 如果确实需要设置过期时间,应确保它大于缓存更新时间,避免键过期后无法命中。
  3. 使用**原子操作(互斥锁 / 逻辑过期)**方案:当多个请求都要去数据库查询同一个数据时,只让其中一个请求去重建缓存,并告知其他请求「缓存正在生成中」,让它们读取上一次的缓存数据或短暂等待,避免同一时间大量请求同时查询数据库。

总结

一句话区分三者的不同:

  • 缓存雪崩:大量缓存 key 在同一时间集体失效,请求全部打向数据库。
  • 缓存穿透:请求的数据本身不存在(空数据),缓存无法命中,每次都穿透到数据库。
  • 缓存击穿:单个热点 key 失效,高并发请求同时打向数据库。