京东二面:内存耗尽后Redis会发生什么?我:有点懵~
# 前言
# 内存回收
expire key ttl:将 key 值的过期时间设置为 ttl 秒。
pexpire key ttl:将 key 值的过期时间设置为 ttl 毫秒。
expireat key timestamp:将 key 值的过期时间设置为指定的 timestamp 秒数。
pexpireat key timestamp:将 key 值的过期时间设置为指定的 timestamp 毫秒数。
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 <bytes>
# LRU 算法
Redis 改进后的 LRU 算法
需要额外的空间进行存储。
可能存在某些 key 值使用很频繁,但是最近没被使用,从而被 LRU 算法删除。
浅灰色带是被删除的对象。
灰色带是未被删除的对象。
绿色是添加的对象。
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 得到空闲时间。
# LFU 算法
访问频次递增
提取 0 和 1 之间的随机数 R。
counter - 初始值(默认为 5),得到一个基础差值,如果这个差值小于 0,则直接取 0,为了方便计算,把这个差值记为 baseval。
概率 P 计算公式为:1/(baseval * lfu_log_factor + 1)。
如果 R < P 时,频次进行递增(counter++)。
lfu_log_factor 10
访问频次递减
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。