---
# System prepended metadata

title: 三大缓存问题
tags: [Redis]

---

# 三大缓存问题
---

## 缓存穿透
缓存穿透是指缓存没有发挥作用，业务系统虽然去缓存查询数据，但缓存中没有数据，业务系统需要再次去存储系统查询数据。

- 存储数据不存在 （设置空值）
- 缓存数据生成耗费大量时间或者资源 （分页存储缓存，监控爬虫进行处理）
1. bloom filter：类似于哈希表的一种算法，用所有可能的查询条件生成一个bitmap，在进行数据库查询之前会使用这个bitmap进行过滤，如果不在其中则直接过滤，从而减轻数据库层面的压力。
2. 空值缓存：一种比较简单的解决办法，在第一次查询完不存在的数据后，将该key与对应的空值也放入缓存中，只不过设定为较短的失效时间，例如几分钟，这样则可以应对短时间的大量的该key攻击，设置为较短的失效时间是因为该值可能业务无关，存在意义不大，且该次的查询也未必是攻击者发起，无过久存储的必要，故可以早点失效。

## 缓存雪崩

缓存雪崩是指当缓存失效（过期）后引起系统性能急剧下降的情况。

1. 更新锁机制 
- 对缓存更新操作进行加锁保护，保证只有一个线程能够进行缓存更新，未能获取更新锁的线程要么等待锁释放后重新读取缓存，要么就返回空值或者默认值。
- 因此分布式集群的业务系统要实现更新锁机制，需要用到分布式锁，如 ZooKeeper。
2. 后台更新机制。
- 由后台线程来更新缓存，而不是由业务线程来更新缓存，缓存本身的有效期设置为永久，后台线程定时更新缓存。
    - 后台线程除了定时更新缓存，还要频繁地去读取缓存（读取时间间隔不能设置太长）
    - 业务线程发现缓存失效后，通过消息队列发送一条消息通知后台线程更新缓存。（这种方式实现依赖消息队列，复杂度会高一些，但缓存更新更及时）

在普通的缓存系统中一般例如redis、memcache等中，我们会给缓存设置一个失效时间，但是如果所有的缓存的失效时间相同，那么在同一时间失效时，所有系统的请求都会发送到数据库层，db可能无法承受如此大的压力导致系统崩溃。

- 线程互斥：只让一个线程构建缓存，其他线程等待构建缓存的线程执行完，重新从缓存获取数据才可以，每个时刻只有一个线程在执行请求，减轻了db的压力，但缺点也很明显，降低了系统的qps。
- 交错失效时间：这种方法时间比较简单粗暴，既然在同一时间失效会造成请求过多雪崩，那我们错开不同的失效时间即可从一定长度上避免这种问题，在缓存进行失效时间设置的时候，从某个适当的值域中随机一个时间作为失效时间即可。

## 缓存热点

同一个时间访问量突然剧增

- 缓存热点的解决方案就是复制多份缓存副本，将请求分散到多个缓存服务器上，减轻缓存热点导致的单台缓存服务器压力。
- 缓存副本设计有一个细节需要注意，就是不同的缓存副本不要设置统一的过期时间，否则就会出现所有缓存副本同时生成同时失效的情况，从而引发缓存雪崩效应。正确的做法是设定一个过期时间范围，不同的缓存副本的过期时间是指定范围内的随机值。

## 缓存击破（缓存击穿）

缓存击穿实际上是缓存雪崩的一个特例，击穿与雪崩的区别即在于击穿是对于特定的热点数据来说，而雪崩是全部数据。

- 二级缓存：对于热点数据进行二级缓存，并对于不同级别的缓存设定不同的失效时间，则请求不会直接击穿缓存层到达数据库。（LRU（Least recently used，最近最少使用）算法根据数据的历史访问记录来进行淘汰数据，其核心思想是“如果数据最近被访问过，那么将来被访问的几率也更高”。）


###### tags: `Redis`
