一文搞懂Redis内存策略,面试再也不怕被问了

Redis 内存优化

Redis是内存型数据库,其容量大小一般是GB级别。如果我们持续不断地往Redis中存入数据,那么内存将会无限增长,最后直接OOM。为了解决这个问题,Redis提供了一些策略实现内存回收:

  • 内存过期策略

  • 内存淘汰策略

内存过期策略

什么是内存过期

为了减少Redis 内存使用,一般情况下我们在往Redis中写入数据的时候都会对Redis key设置一个过期时间。Redis 提供了给数据设置过期时间的功能:

  • EXPIRE key seconds

  • SETEX key seconds value

C 复制代码
// example 1
set name sam
expire name 10  // 10秒后get name 结果为nil

// example 2
setex name 10 sam
get name // 10秒后get name 结果为nil

通过上面的例子可以发现,当key的TTL到期以后,再次访问name返回的是nil,说明这个key已经不存在了,对应的内存也得到释放。从而起到内存回收的目的。

对Redis 的 key设置过期时间除了有助于减少内存的使用。在业务上也有其他的作用。比如有的业务场景就是需要某个数据具体有效期:如短信验证码有效期为60秒。

Redis 判断数据过期的实现原理

在Redis database结构体中,有两个Dict:一个用来记录key-value;另一个用来记录key-TTL。

C++ 复制代码
typedef struct redisDb {
    dict *dict;                 /* 存放所有key及value的地方,也被称为keyspace*/
    dict *expires;              /* 存放每一个key及其对应的TTL存活时间,只包含设置了TTL的key*/
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP)*/
    dict *ready_keys;           /* Blocked keys that received a PUSH */
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    int id;                     /* Database ID,0~15 */
    long long avg_ttl;          /* 记录平均TTL时长 */
    unsigned long expires_cursor; /* expire检查时在dict中抽样的索引位置. */
    list *defrag_later;         /* 等待碎片整理的key列表. */
} redisDb;

Redis是如何知道一个key是否过期呢?

  • 利用两个Dict分别记录key-value对及key-ttl对

    • dict: 数据库键空间,保存着数据库中所有键值对
    • expire:过期字典,保存着键的过期时间。 其中过期字典的键指向Redis数据库中的某个key(键),过期字典的值是一个long long类型的整数,这个整数保存了key所指向的数据库键的过期时间(毫秒精度的UNIX时间戳)
  • 是不是TTL到期就立即删除了呢?:不是

    • 惰性删除:并不是在TTL到期后就立刻删除,而是在访问一个key的时候,检查该key的存活时间,如果已经过期才执行删除。

    • 周期删除:通过一个定时任务,周期性的抽样部分过期的key,然后执行删除。执行周期有两种:

      • Redis服务初始化函数initServer()中设置定时任务,模式为SLOW。SLOW模式执行频率默认为10,每次不超过25ms
      • Redis的每个事件循环前会调用beforeSleep()函数,执行过期key清理,模式为FAST。FAST模式执行频率不固定,但两次间隔不低于2ms,每次耗时不超过1ms
    • 定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,所以 Redis 采用的是 定期删除 + 惰性删除

  • Redis为什么没有采用定时删除呢?即比如设置60s后过期,60s后自动触发任务执行key的删除

    • 这是因为redis扫描key是需要花费时间的,会存在一定的时延。因此无法保证删除的时效性和正确性。因此Redis目前并没有使用定时删除策略。
    • redis的定时删除可能导致某些key 集中过期,阻塞cpu。

SLOW模式规则:

  • 执行频率受server.hz影响,默认为10,即每秒执行10次,每个执行周期100ms。

  • 执行清理耗时不超过一次执行周期的25%.默认slow模式耗时不超过25ms

  • 逐个遍历db,逐个遍历db中的bucket,抽取20个key判断是否过期

  • 如果没达到时间上限(25ms)并且过期key比例大于10%,再进行一次抽样,否则结束

FAST模式规则(过期key比例小于10%不执行 ):

  • 执行频率受beforeSleep()调用频率影响,但两次FAST模式间隔不低于2ms

  • 执行清理耗时不超过1ms

  • 逐个遍历db,逐个遍历db中的bucket,抽取20个key判断是否过期

  • 如果没达到时间上限(1ms)并且过期key比例大于10%,再进行一次抽样,否则结束

内存淘汰机制

什么是内存淘汰

通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致大量过期 key 堆积在内存里,然后就 Out of memory 了。怎么解决这个问题呢?答案就是:Redis 内存淘汰机制。

内存淘汰:就是当Redis内存使用达到设置的上限时,主动挑选部分key删除以释放更多内存的流程。Redis会在处理客户端命令的方法processCommand()中尝试做内存淘汰:

C++ 复制代码
int processCommand(client *c) {
    // 如果服务器设置了server.maxmemory属性,并且并未有执行lua脚本
    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;
        }
        // ....
    }
}

内存淘汰策略

可以使用 config get maxmemory-policy 命令,来查看当前 Redis 的内存淘汰策略

C++ 复制代码
127.0.0.1:6379> config get maxmemory-policy
maxmemory-policy: noeviction

Redis 提供 6 种数据淘汰策略:

  1. volatile-lru(least recently used) :从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。(Redis3.0之前,默认的内存淘汰策略)

  2. volatile-ttl :从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰。

  3. volatile-random :从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。

  4. allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key。

  5. allkeys-random :从数据集(server.db[i].dict)中任意选择数据淘汰。

  6. no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。(Redis3.0之后,默认的内存淘汰策略)

4.0 版本后增加以下两种:

  1. volatile-lfu(least frequently used) :从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰。

  2. allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key。

相关推荐
C++忠实粉丝1 小时前
Redis 介绍和安装
数据库·redis·缓存
ClouGence2 小时前
Redis 到 Redis 数据迁移同步
数据库·redis·缓存
苏三说技术2 小时前
Redis 性能优化的18招
数据库·redis·性能优化
Tttian6222 小时前
基于Pycharm与数据库的新闻管理系统(2)Redis
数据库·redis·pycharm
言之。3 小时前
redis延迟队列
redis
hanbarger4 小时前
nosql,Redis,minio,elasticsearch
数据库·redis·nosql
弗罗里达老大爷4 小时前
Redis
数据库·redis·缓存
DT辰白19 小时前
基于Redis的网关鉴权方案与性能优化
数据库·redis·缓存
木子七20 小时前
Redis-十大数据类型
redis
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭1 天前
聊聊volatile的实现原理?
java·jvm·redis