通常服务器 内存 的价格成本要比相同空间大小的磁盘价格高出几倍,为了保证高的性价比,缓存的空间容量要比后端数据总量要小,通常 80% 的请求实际只访问了 20% 的数据,所以合理设置缓存容量很重要(土豪不在乎除外),在有限的内存情况下,Redis 提供了 8 中淘汰数据的策略,这篇文章来学习一下缓存容量和数据淘汰机制的思想。
1.笔记图
2.Redis作为缓存需要思考的问题
- Redis 缓存是怎么工作的?
- Redis 缓存满了怎么办?
- 为什么有缓存一致性、缓存穿透、缓存雪崩、缓存击穿等异常,如何应对?
- Redis 的内存毕竟有限,可以使用快速固态硬盘保存吗?
3.缓存的特征
- 在一个层次化的系统中,缓存一定是一个快速子系统,数据存在缓存中时,能避免每次从慢速子系统中存取数据。对应到互联网应用来说,Redis 就是快速子系统,而数据库就是慢速子系统了
- 缓存系统的容量大小总是小于后端慢速系统的,我们不可能把所有数据都放在缓存系统中
4.Redis缓存请求处理的两种情况
- 缓存命中:Redis 中有相应数据,直接读取 Redis,非常快
- 缓存缺失:
- Redis 中没有保存相应数据,需从后端数据库读取数据,性能会变慢
- 一旦缓存缺失,后续请求若要读到数据,需把缺失的数据写入 Redis,这过程叫作缓存更新
5.Redis缓存类型
- 只读缓存:所有的数据写请求,会直接发往后端的数据库,如果 Redis 已经缓存了相应的数据,应用需要把这些缓存的数据删除,当应用再次读取这些数据时,会发生缓存缺失,应用会把这些数据从数据库中读出来,并写到缓存中
- 读写缓存:
- 读写缓存除了读请求会发送到缓存,写请求也会发送到缓存
- 在使用读写缓存时,最新的数据在 Redis 中,一旦出现掉电或宕机,内存中的数据就会丢失
6.两种写回策略
- 同步直写:写请求发给缓存的同时,也会发给后端数据库进行处理,等到缓存和数据库都写完数据,才给客户端返回,同步直写策略优先保证数据可靠性,增加了缓存的响应延迟
- 异步写回:优先提供快速响应,所有写请求都先在缓存中处理,等到这些增改的数据要被从缓存中淘汰出来时,缓存将它们写回后端数据库
7.缓存设置多大容量
- 二八原理:有 20% 的缓存数据贡献了 80% 的访问,把缓存空间容量设置为总数据量的 20% 的话,就有可能拦截到 80% 的访问
- 业界研究成果:
- 近年来,有些研究人员专门对互联网应用(例如视频播放网站)中,用户请求访问内容的分布情况做过分析
- 20% 的数据不一定能贡献 80% 的访问量,不能简单地按照 总数据量的 20% 来设置缓存最大空间容量,需要结合应用数据实际访问特征和成本开销来综合考虑
- 大容量缓存是能带来性能加速的收益,但是成本也会更高,而小容量缓存不一定就起不到加速访问的效果。一般来说,建议把缓存容量设置为总数据量的 15% 到 30%,兼顾访问性能和内存空间开销
8.LRU核心思想
- 淘汰规则:链头(LRU端)数据最先被淘汰,链尾(MRU端)最后被淘汰
- 新增元素:将其放于链尾,链尾的数据不容易被淘汰,并且插入之前需要判断一下空间是否已满,如果满了需要将链头的数据淘汰
- 获取元素:如果没有在链表中命中,就返回 -1,如果命中,就将其移动到链表尾部
- 节点定义:
class Node{ Object data; //元素值 Node pre; //前指针 Node next; //后指针 }
9.LFU核心思想
- 淘汰规则:如果数据在最近一段时间很少被访问到,可以认为在将来它被访问的可能性也很小。当空间满时,最小频率访问的数据最先被淘汰
- 新增元素:将记录 (key,value) 插入该结构。缓存满时,将访问频率最低的数据置换掉
- 获取元素:返回 key 对应的 value 值
- 实现思路
- LFU 会淘汰访问频率最小的数据,需要一种合适的方法按大小顺序维护数据访问的频率
- LFU 算法本质上可以看做是一个 top K 问题(选出频率最小的元素)
- 可以用最小堆+哈希表实现
10.Redis淘汰策略
- noeviction 策略:一旦缓存被写满了,再有写请求来时,Redis 不再提供服务,而是直接返回错误
- volatile-random 策略:在设置了过期时间的键值对中,进行随机删除
- volatile-ttl 策略:在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除
- volatile-lru 策略:会使用 LRU 算法筛选设置了过期时间的键值对
- volatile-lfu 策略:会使用 LFU 算法选择设置了过期时间的键值对
- allkeys-random 策略:从所有键值对中随机选择并删除数据
- allkeys-lru 策略:使用 LRU 算法在所有数据中进行筛选
- allkeys-lfu 策略:使用 LFU 算法在所有数据中进行筛选
11.Redis中的LRU简化
- Redis 默认会记录每个数据的最近一次访问的时间戳(由键值对数据结构 RedisObject 中的 lru 字段记录)
- Redis 在决定淘汰的数据时,第一次会随机选出 N 个数据,把它们作为一个候选集合
- Redis 会比较这 N 个数据的 lru 字段,把 lru 字段值最小的数据从缓存中淘汰出去
- 当需要再次淘汰数据时,Redis 需要挑选数据进入第一次淘汰时创建的候选集合,能进入候选集合的数据的 lru 字段值必须小于候选集合中最小的 lru 值
- 命令设置:
#让 Redis 选出 100 个数据作为候选数据集 CONFIG SET maxmemory-samples 100
12.优化建议
- 优先使用 allkeys-lru 策略:可以充分利用 LRU 这一经典缓存算法的优势,把最近最常访问的数据留在缓存中,提升应用的访问性能
- 如果业务应用中的数据访问频率相差不大,没有明显的冷热数据区分,建议使用 allkeys-random 策略,随机选择淘汰的数据就行
- 如果业务中有置顶的需求,如置顶新闻、置顶视频,可以使用 volatile-lru 策略,同时不给这些置顶数据设置过期时间。这些需要置顶的数据一直不会被删除,而其他数据会在过期时根据 LRU 规则进行筛选