Redis 是如何做内存回收的
前言
redis 是基于内存存储,所以性能比较强,但是单节点的 Redis 内存大小不宜过大,要不然会影响持久化或者主从同步性能,可以在 redis.conf 配置文件中设置最大内存,maxmemory 1gb
,但是当内存使用达到上限时,就无法存储更多数据了,这时该怎么办呢?
在 Redis 中提供了两种内存回收策略:
过期策略
在过期策略中会了解到以下内容:
(1)Rediskey 的 TTL 记录方式:
在 RedisDB 中通过一个 Dict 记录每个 Key 的 TTL 时间
(2)过期 key 的删除策略:
惰性清理:每次查找 key 时判断是否过期,如果过期则删除
定期清理:定期抽样部分 key,判断是否过期,如果过期则删除。
(3)定期清理的两种模式:
SLOW 模式执行频率默认为 10,每次不超过 25ms
FAST 模式执行频率不固定,但两次间隔不低于 2ms,每次耗时不超过 1ms
淘汰策略
会了解到八种淘汰策略
过期策略
通过expire
命令给 Redis 的 key 配置 TTL(存活时间)
当 TTL 到期之后,对应的内存得到释放,从而实现内存回收的目的。那么 Redis 是如何知道某个 key 是否过期的呢?
这就涉及到 Redis 数据库的存储原理,Redis 是基于 key-value 存储的,因此所有的 key、value 都会保存在一个 Dict 结构中,也可称为 keyspace,如果一个 key 设置了 TTL,则会另外存储在一个 Dict 中,也就是存 key 和它对应的 TTL 存活时间。也就是说,在 Redis 数据库结构体中,有两个 Dict:一个用来记录 key-value ,另一个用来记录 key-TTL。这样直接查看 key 对应的 TTL 即可知道 key 是否到期。
那么,当 TTL 到期后就立即删除了 key 吗?
不会立即删除,可以采用惰性删除和定期删除。
惰性删除,就是当在访问一个 key 的时候去检查 key 的存活时间,如果存活时间已经过期了才会执行删除。但是这里又有一个问题,如果这个 key 一直没有人访问,那么就一直不会被删除,这样也会占用内存,所以为了解决这个问题,Redis 中提供了一个
周期删除的过期策略
,就是通过一个定时任务定期删除:Redis 中提供了一个
周期删除的过期策略
,就是通过一个定时任务,周期性的抽样部分过期的 key,然后执行删除,执行周期有两种:Redis 初始化时会设置一个定时任务 serverCron(),按照 server.hz 的频率来清理过期的 key,模式为 SLOW。
SLOW 模式规则如下:
执行频率受 server.hz 影响,默认为 10,即每秒执行 10 次,每个执行周期为 100ms
执行清理耗时不超过一次执行周期的 25%
逐个遍历 db,逐个遍历 db 中的 bucket(哈希表的数组),抽取 20 个 key 判断是否过期
如果(执行总时长)没达到时间上限(25ms)并且过期 key 比例大于 10%,再进行一次抽样,否则结束
Redis 的每个事件循环前会调用
beforceSleep()
函数,清理过期的 key,模式为 FAST(执行的频率比较高,执行时间比较短 1ms)FAST 模式规则如下(过期 key 比例小于 10%不执行):
执行频率受
beforeSleep()
调用频率影响,但两次 FAST 模式间隔不低于 2ms执行清理耗时不超过 1ms
逐个遍历 db,逐个遍历 db 中的 bucket,抽取 20 个 key 判断是否过期
如果没达到时间上限 (1ms)并且过期 key 比例大于 10%,再进行一次抽样,否则结束
内存淘汰策略
内存淘汰就是当 Redis 内存使用达到设置的阈值时,Redis 主动挑选部分 key 删除以释放更多内存。
那么,
Redis 什么时候检查内存够不够:当内存被访问的时候就去检查内存。
如何挑选要删除的 key?在 Redis 中提供了八种淘汰策略
(1)
noeviction
:不淘汰任何 key,但是内存满时不允许写入新数据,默认就是这种策略。在 redis.conf 中有
maxmemory-policy noeviction
配置(2)
volatile-ttl
:对设置了下 TTL 的 key,比较 key 的剩余 TTL 值,TTL 越小越先被淘汰(3)
allkeys-random
:对全体 key,随机进行淘汰。也就是直接从 db→dict 中随机挑选(4)
volatile-random
:对设置了 TTL 的 key,随机进行淘汰。也就是从 db-sexpires 中随机挑选。(5)
allkeys-lru
:对全体 key,基于 LRU 算法进行淘汰(最少最近使用)(6)
volatile-lru
:对设置了 TTL 的 key,基于 LRU 算法进行淘汰(7)
allkeys-lfu
:对全体 key,基于 LFU 算法进行淘汰(最少频繁使用)(8)
volatile-lfu
:对设置了 TTL 的 key,基于 LFI 算法进行淘汰
要是用 LRU
和LFU
算法的话需要知道 key 的使用频率和最近一次的使用时间,在 Redis 的数据都会被封装为 RedisObject 结构:【了解即可】
下图是八种淘汰策略的流程图,
在判断内存策略中是采用 LRU 还是 LFU 时需要判断 key 的值的 TTL,但是在 Redis 中有大量的 key,如果将 TTL 进行排序是不现实的,eviction_pool 相当于淘汰池,想淘汰一些 key,但是又不想进行遍历,所以从数据库中挑选一些样本,放入 eviction_pool 中,然后再整体比较一次,看看他们的 TTL 和使用频率,基于这个去做淘汰。redis 中总共有 16 个数据库,从数据库中随机挑选 key,放入 eviction_pool 淘汰池中,但是每种算法的挑选方式不同,所以淘汰方式也不同,但是对于 eviction_pool 淘汰池需要制定一个标准的淘汰标准:按照 key 的某一个值的升序进行排列,值越大的优先淘汰,按什么方法升序排列:LRU:用当前减去最近一次被访问的时间(时间最长越容易被淘汰);LFU:255-频率次数(最越大越容易被淘汰),TTL:用最大时间-剩余时间(值越大越容易被淘汰)计算完成上述方法之后,还需要判断 eviction_pool 淘汰池有没有满,如果满了则需要比较要放入淘汰池的 key 的计算结果比淘汰池的某个值大,这样的 key 需要放进去,然后把淘汰池的最小的挤出去,然后升序排列。
如果觉的不错的话,别忘记三连哦!
版权声明: 本文为 InfoQ 作者【小曾同学.com】的原创文章。
原文链接:【http://xie.infoq.cn/article/a834fc7b7242c133df3218cf4】。文章转载请联系作者。
评论