Redis之所以性能强,最主要的原因就是基于内存存储。然而单节点的Redis其内存大小不宜过大,会影响持久化或主从同步性能。当内存达到上限,就无法存储更多数据了。因此,Redis内部会有两套内存回收的策略:
内存过期策略
内存淘汰策略
我们可以通过修改redis.conf文件,添加下面的配置来配置Redis的最大内存:
maxmemory 1gb
内存过期处理
存入Redis中的数据可以配置过期时间,到期后再次访问会发现这些数据都不存在了,也就是被过期清理了。
过期命令
Redis中通过 expire 命令可以给KEY设置 TTL(过期时间):
# 写入一条数据
set num 123
# 设置20秒过期时间
expire num 20
不过set命令本身也可以支持过期时间的设置:
# 写入一条数据并设置20s过期时间
set num EX 20
过期策略
Redis不管有多少种数据类型,本质是一个 KEY_VALUE 的键值型数据库,而这种键值映射底层正式基于HashTable来实现的,在Redis中叫做Dict.
问:Redis如何判断KEY是否过期呢?
答:在Redis中会有两个Dict,也就是HashTable,其中一个记录KEY-VALUE键值对,另一个记录KEY和过期时间。要判断一个KEY是否过期,只需要到记录过期时间的Dict中根据KEY查询即可。
问:Redis是何时删除过期KEY的呢?
答:Redis并不会在KEY过期时立刻删除KEY,因为要实现这样的效果就必须给每一个过期的KEY设置时钟,并监控这些KEY的过期状态。无论对CPU还是内存都会带来极大的负担。
Redis的过期KEY删除策略有两种:
惰性删除:过期后不会立刻删除,Redis会在每次访问KEY的时候判断当前KEY有没有设置过期时间,如果有,过期时间是否已经到期。
周期删除:通过一个定时任务,周期性的抽样部分过期的key,然后执行删除。
执行周期有两种:
SLOW模式:Redis会设置一个定时任务 serverCron(),按照 server.hz 的频率来执行过期 key 清理
FAST模式:Redis 的每个事件循环前执行过期 key 清理(事件循环就是 NIO 事件处理的循环)。
SLOW模式规则:
-
① 执行频率受
server.hz
影响,默认为10,即每秒执行10次,每个执行周期100ms。 -
② 执行清理耗时不超过一次执行周期的25%,即25ms.
-
③ 逐个遍历db,逐个遍历db中的bucket,抽取20个key判断是否过期
-
④ 如果没达到时间上限(25ms)并且过期key比例大于10%,再进行一次抽样,否则结束
FAST模式规则(过期key比例小于10%不执行):
-
① 执行频率受beforeSleep() 调用频率影响,但两次FAST模式间隔不低于2ms
-
② 执行清理耗时不超过1ms
-
③ 逐个遍历db,逐个遍历db中的bucket,抽取20个key判断是否过期
-
④ 如果没达到时间上限(1ms)并且过期key比例大于10%,再进行一次抽样,否则结束
内存淘汰策略
对于某些特别依赖于Redis的项目而言,仅仅依靠过期KEY清理是不够的,内存可能很快就达到上限。因此Redis允许设置内存告警阈值,当内存使用达到阈值时就会主动挑选部分KEY删除以释放更多内存。这叫做内存淘汰机制。
内存淘汰时机
Redis每次执行任何命令时,都会判断内存是否达到阈值:
// server.c中处理命令的部分源码
int processCommand(client *c) {
// ... 略
if (server.maxmemory && !server.lua_timedout) {
// 调用performEvictions()方法尝试进行内存淘汰
int out_of_memory = (performEvictions() == EVICT_FAIL);
// ... 略
if (out_of_memory && reject_cmd_on_oom) {
// 如果内存依然不足,直接拒绝命令
rejectCommand(c, shared.oomerr);
return C_OK;
}
}
}
淘汰策略
Redis支持8种不同的内存淘汰策略:
- noeviction: 不淘汰任何key,但是内存满时不允许写入新数据,默认就是这种策略。 volatile-ttl: 对设置了TTL的key,比较key的剩余TTL值,TTL越小越先被淘汰。
- allkeys-random:对全体key ,随机进行淘汰。也就是直接从db->dict中随机挑选。
- volatile-random:对设置了TTL的key ,随机进行淘汰。也就是从db->expires中随机挑选。 allkeys-lru: 对全体key,基于LRU算法进行淘汰。
- volatile-lru: 对设置了TTL的key,基于LRU算法进行淘汰。
- allkeys-lfu: 对全体key,基于LFU算法进行淘汰。
- volatile-lfu: 对设置了TTL的key,基于LFI算法进行淘汰。
其中比较容易混淆的有两个算法:
-
LRU(Least Recently Used),最近最久未使用。用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。
-
LFU(Least Frequently Used),最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高。