过期策略

Redis过期策略是:定期删除+惰性删除。

所谓定期删除,指的是Redis默认每隔100ms就随机抽取一些设置了过期时间的key(全部都检查的话十分消耗CPU资源),检查其是否过期,如果过期了就删除。但仅依赖此策略会有许多过期的key未被检查到,因此Redis还使用惰性删除策略,即在读/写key的时候再检查其是否过期,如果过期了则删除。

内存淘汰机制

如果某些key没有被定期删除,也没及时去读/写以触发惰性删除,那么Redis的内存会越来越高,当已用内存超过maxmemory限定时,就会根据内存淘汰机制删除部分key。Redis内存淘汰机制有以下几个:

  • noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错。
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。

缓存穿透

一般的缓存系统,都是根据key去缓存查询,如果不存在对应的value,就应该去数据库查找。如果key对应的value是一定不存在的,并且对该key并发请求量很大,就会对数据库造成很大的压力,这就叫做缓存穿透。

第一个解决办法是对查询结果为空的键也进行缓存,由于这种方式需要更多的键,所以可以设置一个短一点的过期时间。

第二个解决方案则是使用布隆过滤器拦截。布隆过滤器的原理是,当一个元素被加入集合时,通过K个Hash函数将这个元素映射成一个位数组中的K个点,并把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素有可能在。

通过布隆过滤器,一个一定不存在的数据会被它拦截掉,从而避免了对数据库的查询压力。

缓存雪崩

当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,所有请求都会查询数据库,也会给数据库带来很大压力,这就叫做缓存雪崩。对于缓存雪崩有以下几种解决方案:

  • 可以给缓存设置过期时间时加上一个随机时间,使得每个key的过期时间分散开来,不会集中在同一时刻失效。
  • 进行缓存预热,避免在系统刚启动不久由于还未将大量数据进行缓存而导致缓存雪崩。
  • 使用分布式缓存。
  • 提前演练。

热点key重建

当前key是一个热点key(比如说某个热门的娱乐新闻),如果在缓存失效时有大量线程并发请求,那么这些线程会同时去访问数据库并重建key,导致后端系统负载过大,甚至因此崩溃。

为此有以下几种解决方案:

互斥锁

可以使用互斥锁的方式实现,直接利用redis的set命令即可(如SET mutexKey "1" EX 10086 NX),为了防止该锁未被正确释放,还应给该锁设置一个过期时间。

这种方式的缺点在于重建的过程中别的线程都会处于等待状态,整体性能不高。

永远不过期

我们不为每个key设置一个过期时间,但会添加一个逻辑过期时间属性,每次去读的时候都判断一下当前时间是否已经大于逻辑过期时间,如果是的话就使用单独的线程去构建缓存。

这种方式的缺点在于缓存的构建是异步的,因此别的线程在这个过程中依然会取到老值,不保证数据的一致性。

参考资料