在如今这个流量迸发的环境下,如何解决大数据场景,高并发,高性能,热点数据访问等优化问题成为了程序员们所想的重中之重,而这时就涉及到缓存问题,这已经不在是简单的key-value存取,在具体的业务场景中,需要很强化的架构设计,而缓冲架构设计不到位的话,受到非正常访问数据冲击,MySQL支撑不住,很容易导致系统崩溃。说到缓冲,脑中的第一反应是否是如下结构?
缓冲的有效期TTL可以做到数据弱一致性,有效期失效后,保证数据的一致性,节省空间。通常使用的过期策略有三种:
- 定时过期
优点:针对设置过期时间的key创建一个定时器,到期立刻清楚,对内容友好。
缺点:会占用大量的CPU去处理过期的数据,影响缓存的响应时间和吞吐量,所以这里不推荐使用。 - 惰性过期
优点:可以最大化地节省CPU资源
缺点:只有接收到访问请求时,才会判断该数据是否已过期,过期则清除。对内存非常不友好,极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。 - 定期过期
优点:调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。
缺点:假设一种场景,比如有A,B,C,D…等1000W条数据,其中以B为例,每次B数据都逃过扫描,一直扫描不到,但是B数据又没有用,这样也没法对B进行清除,也不太友好。
这时我们可以采用定期过期+惰性删除结合这种方案;比如redis默认每隔100ms检查,是否有过期的key,有则清除,需要说明的是,redis是随机抽取进行检查,因此,采用定期删除策略,会导致很多key到时间没有删除。
于是,惰性删除派上用场。接收到请求查看是否过期,过期则删除,但还有一种情况就是如果定期删除没删除key。然后也没去请求key,惰性删除也没生效。这样,redis的内存会越来越高。
针对这一情况,就需要加入缓存淘汰策略,有两种:LRU,LFU(redis 4.x 后支持LFU策略)
- LRU(时间)
- 新数据插入到列表头部,当缓存命中,则将数据移到列表头部
- 假设内存不足时,列表满的时候,列表最末的数据将被淘汰
- LFU(频率)
- 一个数据在一段时间使用次数少,那么之后一段时间使用的可能性也很小
- LFU需要定期衰减
看图会发现,user4因为基数积累过大,一直没被访问都不会被淘汰,反而近期经常访问的数据却互相淘汰。针对这一情况我们就要采用定期衰减,定期减半,这样没人访问的数据哪怕基数再大都会被淘汰,举个不恰当的例子,要雨露均沾而不能一家独大。
针对淘汰策略符合情况的,使用volatile-lru,当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
redis默认使用的是noeviction,这一点还需手动配置,其他几种allkeys-lru,allkeys-random,volatile-random,volatile-ttl都不是太友好,这里不多做赘述,读者有想深入了解的可自行查看一些淘汰策略资料。
关于缓存模式,更新方式,缓存穿透及缓存雪崩放在(二)中做一个详细叙述,读者可点击查阅。