面试官:内存耗尽后,Redis 会发生什么?
阅读本文大概需要 7 分钟。
来自:blog.csdn.net/zwx900102/article/details/113806440
前言
内存回收
expire key ttl
:将 key 值的过期时间设置为 ttl 秒。pexpire key ttl
:将 key 值的过期时间设置为 ttl 毫秒。expireat key timestamp
:将 key 值的过期时间设置为指定的 timestamp 秒数。pexpireat key timestamp
:将 key 值的过期时间设置为指定的 timestamp 毫秒数。
PS:不管使用哪一个命令,最终 Redis 底层都是使用 pexpireat 命令来实现的。另外,set 等命令也可以设置 key 的同时加上过期时间,这样可以保证设值和设过期时间的原子性。
ttl key
返回 key 剩余过期秒数。pttl key
返回 key 剩余过期的毫秒数。
过期策略
定时删除: 为每个键设置一个定时器,一旦过期时间到了,则将键删除。这种策略对内存很友好,但是对 CPU 不友好,因为每个定时器都会占用一定的 CPU 资源。 惰性删除: 不管键有没有过期都不主动删除,等到每次去获取键时再判断是否过期,如果过期就删除该键,否则返回键对应的值。这种策略对内存不够友好,可能会浪费很多内存。 定期扫描: 系统每隔一段时间就定期扫描一次,发现过期的键就进行删除。这种策略相对来说是上面两种策略的折中方案,需要注意的是这个定期的频率要结合实际情况掌控好,使用这种方案有一个缺陷就是可能会出现已经过期的键也被返回。
typedef struct redisDb {
dict *dict; //所有的键值对
dict *expires; //设置了过期时间的键值对
dict *blocking_keys; //被阻塞的key,如客户端执行BLPOP等阻塞指令时
dict *watched_keys; //WATCHED keys
int id; //Database ID
//... 省略了其他属性
} redisDb;
8 种淘汰策略
maxmemory
config set maxmemory 1GB
来动态修改。maxmemory-policy
进行配置:PS:淘汰策略也可以直接使用命令 config set maxmemory-policy <策略>
来进行动态配置。
LRU 算法
Redis 改进后的 LRU 算法
需要额外的空间进行存储。 可能存在某些 key 值使用很频繁,但是最近没被使用,从而被 LRU 算法删除。
maxmemory_samples 5
,默认值就是 5,表示随机抽取 5 个 key 值,然后对这 5 个 key 值按照 LRU 算法进行删除,所以很明显,key 值越大,删除的准确度越高。浅灰色带是被删除的对象。 灰色带是未被删除的对象。 绿色是添加的对象。
Redis 如何管理热度数据
typedef struct redisObject {
unsigned type:4;//对象类型(4位=0.5字节)
unsigned encoding:4;//编码(4位=0.5字节)
unsigned lru:LRU_BITS;//记录对象最后一次被应用程序访问的时间(24位=3字节)
int refcount;//引用计数。等于0时表示可以被垃圾回收(32位=4字节)
void *ptr;//指向底层实际的数据存储结构,如:SDS等(8字节)
} robj;
当全局 lruclock > lru
,则使用lruclock - lru
得到空闲时间。当全局 lruclock < lru
,则使用lruclock_max
(即 194 天) -lru + lruclock
得到空闲时间。
需要注意的是,这种计算方式并不能保证抽样的数据中一定能删除空闲时间最长的。这是因为首先超过 194 天还不被使用的情况很少,再次只有 lruclock 第 2 轮继续超过 lru 属性时,计算才会出问题。
10-1=9
天,实际上应该是 194+10-1=203
天。LFU 算法
访问频次递增
提取 0 和 1 之间的随机数 R。 counter - 初始值(默认为 5),得到一个基础差值,如果这个差值小于 0,则直接取 0,为了方便计算,把这个差值记为 baseval。 概率 P 计算公式为: 1/(baseval * lfu_log_factor + 1)
。如果 R < P
时,频次进行递增(counter++
)。
lfu_log_factor
称之为对数因子,默认是 10 ,可以通过参数来进行控制:lfu_log_factor 10
lfu_log_factor
和频次 counter 增长的关系图:lfu_log_factor
为 100 时,大概是 10M(1000万) 次访问才会将访问 counter 增长到 255,而默认的 10 也能支持到 1M(100万) 次访问 counter 才能达到 255 上限,这在大部分场景都是足够满足需求的。访问频次递减
lfu-decay-time
进行控制,默认是 1,单位是分钟。默认值 1 表示:N 分钟内没有访问,counter 就要减 N。lfu-decay-time 1
获取当前时间戳,转化为分钟后取低 16 位(为了方便后续计算,这个值记为 now)。 取出对象内的 lru 属性中的高 16 位(为了方便后续计算,这个值记为 ldt)。 当 lru > now
时,默认为过了一个周期(16 位,最大 65535),则取差值65535-ldt+now
:当lru <= now
时,取差值now-ldt
(为了方便后续计算,这个差值记为idle_time
)。取出配置文件中的 lfu_decay_time
值,然后计算:idle_time / lfu_decay_time
(为了方便后续计算,这个值记为num_periods
)。最后将counter减少: counter - num_periods
。
lfu_decay_time
,如果这个配置默认为 1也即是 100/1=100
,代表 100 分钟没访问,所以 counter 就减少 100。总结
推荐阅读:
内容包含Java基础、JavaWeb、MySQL性能优化、JVM、锁、百万并发、消息队列、高性能缓存、反射、Spring全家桶原理、微服务、Zookeeper、数据结构、限流熔断降级......等技术栈!
⬇戳阅读原文领取! 朕已阅
评论