Redis系列11:内存淘汰策略
1 前言
通过前面的一些文章我们知道,Redis的各项能力是基于内存实现的,相对其他的持久化存储(如MySQL、File等,数据持久化在磁盘上),性能会高很多,这也是高速缓存的一个优势。
但是问题来了,每一台机器内存终归是有限的,即使是集群模式,总的内存空间也是有限的,不能无限制的消耗。而在Redis的使用过程中,很有可能出现使用消耗超过内存实际大小的情况。比如以下几种情况:
- 未设置过期时间,Redis的Key将一直存在,直至我们明确将它删除。
- 过度跟不合理的持久化(无论是RDB快照 或是 AOF日志),都会在内存和磁盘中反复操作,需要一定的内存空间进行处理。
- 不及时清理过期缓存:清理过期缓存的方式主要有以下两种,并不是实时或者准实时,所以存在部分过期缓存依旧存在的问题。
- 主动定期删除: Redis 默认每 1 秒运行 10 次(平均每 100 ms 执行一次),每次随机抽取部分设置过期时间的 key,检查是否过期,若是过期就直接删除,直至过期的 key 比率低于 1/4。
- 被动惰性删除:缓存过期并不马上清理,当客户端的请求查询该 key 的时候,检查下 key 是否过期,如果过期,则删除该 key,重新获取。如果长时间未请求,就会有过期缓存滞留。
- 不合理不规范的使用缓存,导致内存耗尽,比如:
- 过度使用缓存,既缓存冷数据也能缓存热数据,导致内存占用过多,性能也没有得到有效提高
- 缓存数量过多或者单个缓存的Value体积过大
- 缓存过期时间设置过长或者根本不设置
2 Redis内存淘汰策略
所以,如果放任上面的那几种情况,内存终归会满的,Redis自身有一套比较完善的内存淘汰策略来专门应对这个问题,在Redis Memory占用超过我们配置的阈值的时候触发策略执行。
# redis.conf 配置最大内存空间占用为2gb,超过则执行内存淘汰策略
redis > CONFIG SET maxmemory 2gb
内存淘汰策略一共有8中,除了一种不执行淘汰策略之外,其他7种都是按照各自不一的算法对内存中现有的数据进行处理。
我们下面详细来看一下这些淘汰策略,把他们分成三大类,8小类来逐一讲解:
2.1 不淘汰策略
2.1.1 noeviction 不淘汰策略
noeviction指的是即使资源超过 maxmemory 限制的值也不会执行淘汰,只是不允许创建新的缓存了。
当Redis内存占用达到我们上面的配置的阈值(比如 5gb)之后,就不允许新增缓存key了,当有新的缓存要创建的时候,Redis 直接返回error。
2.2 仅淘汰配置过期时间key
这边仅针对配置了过期时间的数据进行淘汰
2.3.1 volatile-lru :删除最近最少使用的key
LRU(Least Recently Used)是按照最近最少使用原则来筛选数据,即最不常用的数据会被筛选出来。
如果我们的服务中有冷热数据隔离需求,这无疑是一个比较好的办法。可以将缓存的一些不经常使用的冷数据,而且数据size比较大的,筛选出来清理掉。而近期频繁被使用的key就被保留下来了。
常见的场景如下:
- 电商平台的冷热数据:比如冬季,保暖冬装、电暖设备的浏览次数就会升高,而相应的冷饮、制冷设备(冰箱、空调)的浏览次数就会降低,那么LRU策略下优先删除的就是最近一段时间未访问的缓存信息。
- 外卖平台:每天的1113点,1719点,一定是美食外卖品种的高频率访问时间段,而日用品、果蔬生鲜 大都会避开这个高峰期,这时如果内存不够用了,那么就会成为被优先删除的缓存类型。
2.3.2 volatile-lfu:删除访问次数最少的key(4.0 之后新增的策略)
LRU算法的不足之处在于,一个本身很少被访问的key,只是刚刚被访问了1次,就被认为是最近有使用的热点数据,导致短时间内不会被淘汰。
而LFU弥补了这个不足,LFU(Least Frequently Used)淘汰策略会根据key的最近访问频率进行淘汰,解决上面说的这个不足。
- LFU在LRU的基础上,为每个数据增加了一个计数器,用于统计该数据的访问次数。
- 当使用LFU策略淘汰数据时,会根据数据的访问次数进行筛选,把访问次数最低的数据淘汰出内存。
- 如果两个缓存数据的访问次数相同,LFU再比较这两个key最近一次的访问时间,把访问时间更早的缓存key淘汰出内存。
常见的应用场景:
- 对于电商平台中的冷门的商品,电子书App中热度较低、阅读量较低的书籍。这种类型的缓存会优先被淘汰掉。
2.3.3 volatile-random:随机删除过期key
针对有配置过期时间,但没有明显的冷热访问频率区别,所有的查询分布比较均衡的数据。这时候就使用 allkeys-random 策略吧,让它随机选择需要淘汰数据,也相对公平。
常见的使用场景有:
- 电商平台:常规时段的商品浏览。
- 钉钉之类工具:老师无差别抽查学生的作业。
2.3.4 volatile-ttl:删除过期时间内剩余时间最短的key
这个特性仅限于配置过期时间的场景,它是根据当前时间 跟 过期时间的差额进行由短到长的排序,较短的优先淘汰。
asc_sort(validate_time - current_time)
这种算法相对来说也不考虑缓存的访问频率和重要程度,仅按照创建的先后进行清理,越早的缓存越早清理。
所以不具备明显特征的业务场景都适用。
2.3.5 补充说明
业务场景有一些数据始终不需要删除,比如置顶新闻、视频,还有我们自己置顶的weibo。为了保障它们不被清理掉,就给这些数据不设置过期时间,这样的话 volatile类型的淘汰策略就不会影响了。但如果是 allkeys 开头的策略依旧会影响到。
2.3 淘汰所有缓存类型的key
无论是否配置了过期时间的数据均可进行淘汰。
从微服务拆分的角度说,不同的服务类型个方向的服务进行院子隔离会比较一点。这一点设计思维在缓存上依旧适用。
我们可以将不需要过期时间的缓存信息 和 需强制配置过期时间的缓存key分开。针对业务场景分别使用 volatile-xx策略 和 allkyes-xxx策略。
2.3.1 allkeys-lru:删除最近最少使用的key
保留最近有使用的key,类似volatile-lru
2.3.2 allkeys-lfu:删除访问次数最少的key
最不经常使用的,类似volatile-lfu
2.3.3 allkeys-random:随机删除过期key
无差别随机删除,volatile-random,为添加新数据腾出空间
2.4 策略命令的使用
# 获取当前内存淘汰策略
redis > config get maxmemory-policy
# 获取Redis能使用的最大内存大小:如果不设置最大内存大小或者设置最大内存大小为0,在64位操作系统下不限制内存大小,在32位操作系统下最多使用3GB内存。
redis > config get maxmemory
# 通过命令配置淘汰策略
redis > config set maxmemory-policy volatile-lru
# 设置Redis最大占用内存大小,这边最大占用内存大小配置为2000M
redis > config set maxmemory 2000mb
3 总结
一张图总结