Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Redis淘汰删除策略

Redis淘汰删除策略

原创
作者头像
一生何求
修改于 2019-12-23 03:24:43
修改于 2019-12-23 03:24:43
1.5K00
代码可运行
举报
文章被收录于专栏:短信防刷短信防刷
运行总次数:0
代码可运行

Redis数据库作为缓存系统使用通常只保留部分的热点数据。当热点数据超过Redis设置的内存总大小时就需要删除陈旧的数据,为键空间设计一套高效的过期策略将使得应用程序的内存需求更可控。本文介绍Redis的键淘汰策略以及在Redis内部是如何实现的。

Redis6种淘汰Key策略

Redis中通过maxmemory参数来设定内存的使用上限,当Redis使用内存达到设定的最大值的时候,会根据配置文件中的策略选取要删除的key来删除,从而给新的键值留出空间;

目前Redis提供了6种的淘汰策略(默认的是noeviction):

  • noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。这里特别注意的一点:当redis内存耗尽时,Redis会开始删除那些设置了过期时间的键,即使该键仍然有剩余时间。
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。

其中Redis的LRU算法,其中有几点需要注意的地方。首先,Redis的LRU算法时不准确的。因为Redis并不会自动选择真正最少使用的键或者最早访问的键,而是默认会选择5个键的样本,并驱逐当中最少使用的那个。如果需要增加LRU的精确性,我们可以通过更改redis.conf文件中的maxmemory-samples指令,或者再运行时通过config set maxmemory-samples命令进行设置,增大该值可以提升RedisLRU的准确性,效果更接近真实的LRU,但是副作用时消耗更多的cpu资源。

如何查看当前redis实例的key淘汰策略:

CONFIG GET maxmemory-policy

Redis的过期键删除策略

常见过期键的删除策略总结

上面介绍了redis的6种Key淘汰策略。如果一个Key满足淘汰条件需要删除 ,那么它是在什么时候会被删除的呢? 通常有三种不同的方式:

1、定时删除:在设置Key的过期的同时,创建一个定时器(timer),让定时器在键的过期来临时,立即执行对键的删除操作。

  • 优点:对内存时最友好的,通过使用定时器,定时删除策略可以保证过期键会尽可能的被快速删除,并释放过期键所占用的内存。
  • 缺点:在过期键比较多的情况下,删除过期键中一行为可能会占用相当一部分cpu时间,将cpu时间用在删除和当前任务无关的过期键上,无疑是对服务器的响应时间和吞吐量造成影响。因此,要让服务器创建大量的定时器从而实现定时删除策略,从cpu资源利用上讲并不现实。

2、惰性删除:放任键过期不管,但是每次从键空间获取键时,都检查获得的键时否过期,如果过期的话,就删除该键;若谷没有过期,就返回该键。

  • 优点:cpu友好,程序只会在取出键时才对键的过期时间进行检查,这保证了删除过期键的操作只会在非作不可的情况下进行,并且删除的目标只限于当前处理的键,因此该策略不会再删除其他无关的过期键上浪费cpu资源。
  • 缺点:内存不友好。在使用惰性删除是,如果数据库中有非常多的过期键,而这些过期键又恰好没有被访问到的话,那么他们永远也不会被删除,导致无用的拉起数据占用着内存,有内存泄漏的问题。

3、定期删除:每隔一段时间,程序就对数据库进行一次检查,删除里面过期键。至于需要删除多少过期键,以及需要检查多少个键,则有算法决定。 定期删除策略时以上两种策略的整合和折中:

  • a.定期删除策略每隔一段时间执行一次删除过期操作,并通过限制删除执行的时长和频率来减少删除操作对cpu时间的影响
  • b.通过定期删除过期键,定期删除策略有效地减少了因为过期键带来的内存浪费。

Redis过期键删除的实现

Redis通过配合使用定期删除和惰性删除量种策略,合理使用cpu时间和避免浪费内存空间之间取得了平衡。这里对Redis服务器中定期删除和惰性删除的具体实现进行说明。

用于存储过期Key的底层数据结构

redis底层数据结构是由dict以及expires两个字典构成,通过下一张图可以非常清晰了解到redis中带过期时间的key-value的存储结构,可以更加深刻认识到redis的内存管理机制。

  • 过期字典的键是一个指针,这个指针指向键空间中的某个键对象
  • 过期字典的值是一个long long 类型的整数,这个整数保存了键所指向的数据库键的过期时间---一个毫秒精度的unix时间戳

typedef struct redisDB {

// ...

// 过期字典,保存着键的过期时间

dict* expires;

// ...

}

惰性策略的实现

过期键的惰性策略有db.c/expireIfNeeded函数实现,所有读写数据库的Redis命令在执行之前都会调用expireIfNeeded

函数进行Key的检查,如果输入键已经过期,那么该函数将会删除Key,否则什么也不做。具体代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int freeMemoryIfNeeded(void) {

    size_t mem_used, mem_tofree, mem_freed;

    int slaves = listLength(server.slaves);

    // 计算占用内存大小时,并不计算slave output buffer和aof buffer,

// 因此maxmemory应该比实际内存小,为这两个buffer留足空间。

    mem_used = zmalloc_used_memory();

    if (slaves) {

        listIter li;

        listNode *ln;

        listRewind(server.slaves,&li);

        while((ln = listNext(&li))) {

            redisClient *slave = listNodeValue(ln);

            unsigned long obuf_bytes = getClientOutputBufferMemoryUsage(slave);

            if (obuf_bytes > mem_used)

                mem_used = 0;

            else

                mem_used -= obuf_bytes;

        }

    }

    if (server.appendonly) {

        mem_used -= sdslen(server.aofbuf);

        mem_used -= sdslen(server.bgrewritebuf);

    }

// 判断已经使用内存是否超过最大使用内存,如果没有超过就返回REDIS_OK,

    if (mem_used <= server.maxmemory) return REDIS_OK;

// 当超过了最大使用内存时,就要判断此时redis到底采用何种内存释放策略,根据不同的策略,采取不同的清除算法。

// 首先判断是否是为no-enviction策略,如果是,则返回REDIS_ERR,然后redis就不再接受任何写命令了。

    if (server.maxmemory_policy == REDIS_MAXMEMORY_NO_EVICTION)

        return REDIS_ERR;

    // 计算需要清理内存大小

    mem_tofree = mem_used - server.maxmemory;

    mem_freed = 0;



    while (mem_freed < mem_tofree) {

        int j, k, keys_freed = 0;

        for (j = 0; j < server.dbnum; j++) {

            long bestval = 0;

            sds bestkey = NULL;

            struct dictEntry *de;

            redisDb *db = server.db+j;

            dict *dict;

// 1、从哪个字典中剔除数据

            // 判断淘汰策略是基于所有的键还是只是基于设置了过期时间的键,

// 如果是针对所有的键,就从server.db[j].dict中取数据,

// 如果是针对设置了过期时间的键,就从server.db[j].expires(记录过期时间)中取数据。

if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||

                server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM)

            {

                dict = server.db[j].dict;

            } else {

                dict = server.db[j].expires;

            }

            if (dictSize(dict) == 0) continue;

// 2、从是否为随机策略

// 是不是random策略,包括volatile-random 和allkeys-random,这两种策略是最简单的,就是在上面的数据集中随便去一个键,然后删掉。

            if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM ||

                server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_RANDOM)

            {

                de = dictGetRandomKey(dict);// 从方法名猜出是随机获取一个dictEntry

                bestkey = dictGetEntryKey(de);// 得到删除的key

            }

// 3、判断是否为lru算法

// 是lru策略还是ttl策略,如果是lru策略就采用lru近似算法

// 为了减少运算量,redis的lru算法和expire淘汰算法一样,都是非最优解,

// lru算法是在相应的dict中,选择maxmemory_samples(默认设置是3)份key,挑选其中lru的,进行淘汰

            else if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||

                server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)

            {

                for (k = 0; k < server.maxmemory_samples; k++) {

                    sds thiskey;

                    long thisval;

                    robj *o;

                    de = dictGetRandomKey(dict);

                    thiskey = dictGetEntryKey(de);

                    /* When policy is volatile-lru we need an additonal lookup

                    * to locate the real key, as dict is set to db->expires. */

                    if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)

                        de = dictFind(db->dict, thiskey); //因为dict->expires维护的数据结构里并没有记录该key的最后访问时间

                    o = dictGetEntryVal(de);

                    thisval = estimateObjectIdleTime(o);

                    /* Higher idle time is better candidate for deletion */

// 找到那个最合适删除的key

// 类似排序,循环后找到最近最少使用,将其删除

                    if (bestkey == NULL || thisval > bestval) {

                        bestkey = thiskey;

                        bestval = thisval;

                    }

                }

            }

// 如果是ttl策略。

// 取maxmemory_samples个键,比较过期时间,

// 从这些键中找到最快过期的那个键,并将其删除

            else if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_TTL) {

                for (k = 0; k < server.maxmemory_samples; k++) {

                    sds thiskey;

                    long thisval;

                    de = dictGetRandomKey(dict);

                    thiskey = dictGetEntryKey(de);

                    thisval = (long) dictGetEntryVal(de);

                    /* Expire sooner (minor expire unix timestamp) is better candidate for deletion */

                    if (bestkey == NULL || thisval < bestval) {

                        bestkey = thiskey;

                        bestval = thisval;

                    }

                }

            }

// 根据不同策略挑选了即将删除的key之后,进行删除

            if (bestkey) {

                long long delta;

                robj *keyobj = createStringObject(bestkey,sdslen(bestkey));

                // 发布数据更新消息,主要是AOF 持久化和从机

                propagateExpire(db,keyobj); //将del命令扩散给slaves

// 注意, propagateExpire() 可能会导致内存的分配,

// propagateExpire() 提前执行就是因为redis 只计算

// dbDelete() 释放的内存大小。倘若同时计算dbDelete()

// 释放的内存和propagateExpire() 分配空间的大小,与此

// 同时假设分配空间大于释放空间,就有可能永远退不出这个循环。

// 下面的代码会同时计算dbDelete() 释放的内存和propagateExpire() 分配空间的大小

                /* We compute the amount of memory freed by dbDelete() alone.

                * It is possible that actually the memory needed to propagate

                * the DEL in AOF and replication link is greater than the one

                * we are freeing removing the key, but we can't account for

                * that otherwise we would never exit the loop.

                *

                * AOF and Output buffer memory will be freed eventually so

                * we only care about memory used by the key space. */

// 只计算dbDelete() 释放内存的大小

                delta = (long long) zmalloc_used_memory();

                dbDelete(db,keyobj);

                delta -= (long long) zmalloc_used_memory();

                mem_freed += delta;

                server.stat_evictedkeys++;

                decrRefCount(keyobj);

                keys_freed++;

                /* When the memory to free starts to be big enough, we may

                * start spending so much time here that is impossible to

                * deliver data to the slaves fast enough, so we force the

                * transmission here inside the loop. */

                // 将从机回复空间中的数据及时发送给从机

                if (slaves) flushSlavesOutputBuffers();

            }

        }//在所有的db中遍历一遍,然后判断删除的key释放的空间是否足够,未能释放空间,且此时redis 使用的内存大小依旧超额,失败返回

        if (!keys_freed) return REDIS_ERR; /* nothing to free... */

    }

    return REDIS_OK;

}

从源码分析中可以看到redis在使用内存中超过设定的阈值时是如何将清理key-value进行内管管理,其中涉及到redis的存储结构。

总结出的具体流程见图1

图1、调用expireIfNeeded来删除过期键
图1、调用expireIfNeeded来删除过期键

例如图2的GET命令的执行过程中,命令需要判断键是否存在以及键时否过期,然后根据判断来执行合适的操作。

GET命令的执行过程
GET命令的执行过程

定期删除策略的实现

过期键的定期删除策略由redis.c/activeExpireCycle函数实现,每当Redis的服务器周期性操作redis.c/serverCron函数执行时,activeExpireCycle函数就会被调用,它在规定的时间内,分多次遍历服务器的各个数据库,从数据库的expires字典中随机检查一部分的过期键,并删除其中的过期键。

伪代码描述如下:

#默认每次检查的数据库数量

DEFAULT_DB_NUMBERS = 16

#默认每个数据库检查的键数量

DEFAULT_KEY_NUMBERS = 20

#全局变量,记录检查进度

current_db = 0

def activeExpireCycle():

#初始化要检查的数据库数量

#如果服务器的数据库数量比DEFAULT_DB_NUMBERS小,那么以服务器的数据库数量为准

if server.dbnum < DEFAULT_DB_NUMBERS:

db_numbers = server.dbnum

else:

db_numbers = DEFAULT_DB_NUMBERS

for _ in range(db_numbers):

#获取当前要处理的数据库

if current_db == server.dbnum:

current_db = 0

redisDb = server.db[current_db]

current += 1

#检查数据库的j键,一次轮询最多检查DEFAULT_KEY_NUMBERS的键

for j in range(DEFAULT_KEY_NUMBERS):

#如果当前数据库没有一个带过期时间的key,那么跳过该数据库

if redisDb.expires.size() == 0:

break

#随机取一个过期时间的键

key_with_ttl = redis.expires.get_random_key()

#检查是否过期,如果过期就删除它

if is_expired(key_with_ttl):

delete_key(key_with_ttl)

#已经到达时间上限,停止继续处理余下的检测

if reach_time_limit():

return

对于定期删除策的工作模式可以总结如下:

  • 函数每次运行时,都从一定数量的数据库中取出一定数量的随机键进行检查,并删除其中过期键。
  • 全局变量 current_db会记录当前activeExpireCycle函数检查的进度,并在下一次activeExpireCycle函数调用时,接着上次的进度进行处理。比如,如果当前activeExpireCycle函数遍历10号数据库返回了,那么下次activeExpireCycle函数执行时,将从11号数据库开始查找并删除过期键。
  • 随着activeExpireCycle函数的不断执行,服务器中的所有数据库都会被检查一遍,这时函数将current_db重新置0,然后再开始新一轮的检查。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Redis内存淘汰和过期删除策略原理分析
Redis是一个内存键值对数据库,所以对于内存的管理尤为重要。Redis内部对于内存的管理主要包含两个方向,过期删除策略和数据淘汰策略。 思考:
政采云前端团队
2023/10/25
5130
Redis内存淘汰和过期删除策略原理分析
redis过期删除机制(redis过期策略和删除策略)
在Redis中,内存的大小是有限的,所以为了防止内存饱和,需要实现某种键淘汰策略。主要有两种方法,一种是当Redis内存不足时所采用的内存释放策略。另一种是对过期键进行删除的策略,也可以在某种程度上释放内存。
全栈程序员站长
2022/08/01
2.2K0
Redis源码剖析之内存淘汰策略(Evict)
Redis作为一个成熟的数据存储中间件,它提供了完善的数据管理功能,比如之前我们提到过的数据过期和今天我们要讲的数据淘汰(evict)策略。在开始介绍Redis数据淘汰策略前,我先抛出几个问题,帮助大家更深刻理解Redis的数据淘汰策略。
xindoo
2021/02/28
3980
Redis源码剖析之内存淘汰策略(Evict)
Redis数据淘汰算法
众所周知,Redis的所有数据都存储在内存中,但是内存是一种有限的资源,所以为了防止Redis无限制的使用内存,在启动Redis时可以通过配置项 maxmemory 来指定其最大能使用的内存容量。例如可以通过以下配置来设置Redis最大能使用 1G 内存:
用户7686797
2020/08/25
2.3K0
Redis 中的过期删除策略和内存淘汰机制
Redis 中 key 的过期删除策略 内存碎片如何产生 碎片率的意义 如何清理内存碎片 内存淘汰触发的最大内存 有哪些内存淘汰策略 内存淘汰算法 LRU LFU 1、定时删除 2、惰性删除 3、定期删除 Redis 中过期删除策略 从库是否会脏读主库创建的过期键 前言 Redis 中 key 的过期删除策略 内存淘汰机制 为什么数据删除后内存占用还是很高 总结 参考 Redis 中 key 的过期删除策略 ◆ 前言 Redis 中的 key 设置一个过期时间,在过期时间到的时候,Redis 是如何清除这个
IT大咖说
2022/04/22
9330
Redis 中的过期删除策略和内存淘汰机制
Redis系列之淘汰策略介绍
由于Redis内存是有大小的,当内存快满的时候,又没有过期数据,这个时候就会导致内存被占满,内存满了,自然就不能再放入新的数据。所以,就需要Redis的淘汰策略来保证可用性。
SmileNicky
2024/12/23
3740
Redis系列之淘汰策略介绍
Redis过期--淘汰机制的解析和内存占用过高的解决方案
Redis在我们平时的开发或者练习的时候,往往很容易忽略一个问题,那就是我们的Redis内存占满的问题。但是在真是的商业开发中,Redis的实际占满是真正会存在这样的问题的。那么如果Redis在某一刻占满内存,我们又没有对它进行相应的设置它会出现什么情况呢?会不会导致我们整个因为使用Redis而整个业务垮掉?
码农编程进阶笔记
2021/07/20
6450
Redis 删除、淘汰策略
以 CPU 定时执行的方式换 Redis 内存(因为会使用轮询的方式一直耗用 CPU 资源),及时性不高,但是内存不会浪费
BUG弄潮儿
2021/07/22
5000
走近源码:Redis如何清除过期key
业务组的同学告诉我说很多用户的帐号今天被强制下线。我们的帐号系统正常的逻辑是用户登录一次后,token的有效期可以维持一天的时间。现在的问题是用户大概每10分钟左右就需要重新登录一次。这种情况一般有两种原因:1、token生成时出问题。2、验证token时出现问题。
Jackeyzhe
2020/04/08
1.1K0
走近源码:Redis如何清除过期key
【愚公系列】2023年04月 Java教学课程 126-Redis的数据删除与淘汰策略
Redis是一种内存级数据库,所有数据均存放在内存中,内存中的数据可以通过TTL指令获取其状态
愚公搬代码
2023/04/08
5100
【愚公系列】2023年04月 Java教学课程 126-Redis的数据删除与淘汰策略
详解 Redis 内存管理机制和实现
Redis是一个基于内存的键值数据库,其内存管理是非常重要的。本文内存管理的内容包括:过期键的懒性删除和过期删除以及内存溢出控制策略。
Bug开发工程师
2019/11/12
5290
详解 Redis 内存管理机制和实现
Redis是一个基于内存的键值数据库,其内存管理是非常重要的。本文内存管理的内容包括:过期键的懒性删除和过期删除以及内存溢出控制策略。
程序员历小冰
2019/10/30
2K0
详解 Redis 内存管理机制和实现
Redis 的过期策略和内存淘汰机制有什么区别?
Redis 和 MySQL 是面试绕不过的两座大山,他们一个是关系型数据库的代表(MySQL),一个是键值数据库以及缓存中间件的一哥。尤其 Redis 几乎是所有互联网公司都在用的技术,比如国内的 BATJ、新浪、360、小米等公司;国外的微软、Twitter、Stack Overflow、GitHub、暴雪等公司。我从业了十几年,就职过 4、5 家公司,有的公司用 MySQL、有的用 SQL Server、甚至还有的用 Oracle 和 DB2,但缓存无一例外使用的都是 Redis,从某种程度上来讲 Redis 是普及率最高的技术,没有之一。
码农架构
2021/02/23
7760
Redis 的过期策略和内存淘汰机制有什么区别?
一个今日头条的面试题——LRU原理和Redis实现
很久前参加过今日头条的面试,遇到一个题,目前半部分是如何实现 LRU,后半部分是 Redis 中如何实现 LRU。
Java架构
2018/09/20
2.1K0
一个今日头条的面试题——LRU原理和Redis实现
【Redis08】删除策略与逐出算法
那些有效期到了的数据,Redis并不是真的一到期立刻就把它删了,因为删除数据相比于其他客户端命令并不那么重要,这些数据会暂留在内存中,最终根据Redis的删除策略删除
JuneBao
2022/10/26
7640
Redis详解(1)——为什么我们一定要了解Redis
从我第一次使用Redis来帮助加速算法运行速度开始,就把Redis应用在了各个项目中,每次带来的体验都非常得好,python多进程+Redis的使用帮助我把单进程运行十几个小时的程序加速到了只需要10分钟左右,也帮助我把本来需要运行十几分钟的项目加速到了几十秒就能运行结束,同时我也喜欢Redis项目本身的小巧和精致。所以在这里计划写些关于Redis的介绍,计划总共写两篇,第一篇主要介绍Redis的整体的一些设计和思想,第二篇会主要介绍Redis集群的一些研究,希望能帮助大家熟悉认识Redis,并鼓励在你的项目中能尝试使用Redis。本篇主要会涉及到如下内容:
eedalong
2019/12/01
1.1K0
Redis详解(1)——为什么我们一定要了解Redis
Redis学习笔记(三)redis 的键管理
Redis 是一个键值对(key-value pair)的数据库服务器,其数据保存在 src/server.h/redisDb 中(网上很多帖子说在 redis.h 文件中,但是 redis 6.x版本目录中都没有这个文件。redisDb 结构应该在 server.h文件中)
落寞的鱼丶
2022/02/07
4350
Redis 过期键删除策略
过期键的惰性删除策略由db.c/expireIfNeeded函数实现,所有读写数据库的Redis命令在执行之前都会调用expireIfNeeded函数对输入键进行检查:
好好学java
2020/03/18
1.2K0
2022年Redis最新面试题第6篇 – Redis淘汰策略「建议收藏」
1)、关于定期删除, Redis默认会每隔100ms就随机选取一些已经过期了的key,检查其是否过期,如果已经过期就删除。
全栈程序员站长
2022/11/01
7090
Redis键过期策略、内存淘汰策略详解
除了string独有设置过期时间的方法,其他类型都需要依靠expire方法设置时间,若:
JavaEdge
2022/11/30
1.2K0
Redis键过期策略、内存淘汰策略详解
相关推荐
Redis内存淘汰和过期删除策略原理分析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档