One shot Soul Redis 过期删除策略和内存消除策略

小夏 游戏 更新 2024-01-31

使用Redis缓存时,您可以为缓存数据设置过期时间,提高缓存命中率,并根据过期策略删除缓存数据。

Redis 的缓存过期删除策略有两种类型:延迟删除和定期删除。

在 Redis 中,设置了过期时间的密钥将存储在 Redis 的过期字典中。 当你对密钥进行读写操作时,会先检查过期的字典中是否存在密钥,如果存在,则获取密钥的过期时间,并与系统当前时间进行对比

如果过期时间大于当前时间,则密钥尚未过期。

如果过期时间小于或等于当前时间,则确定密钥已过期。

过时的词典

过期的字典存储在 RedisDB 结构中,如下所示:

typedef struct redisdb redisdb;
哪里:

过期字典的键指向 Redis 数据库中的键。

过期字典的值为long long类型的整数,用于存储密钥对应的过期时间(timestamp,单位:毫秒)。

如图所示

延迟删除处理逻辑,如图所示

过程:对密钥执行读写操作,以确定过期的字典中是否存在该密钥

如果不存在,则正常执行读写操作。

如果是这样,请将密钥过期时间与系统的当前时间进行比较,以确定密钥是否已过期

如果密钥的过期时间小于或等于当前时间(过期),则删除该密钥。

如果密钥过期时间长于当前时间(未过期),系统将执行读/写操作。

定期删除处理逻辑,如图所示

操作步骤:Redis以100ms的频率从过期的字典中随机抽取20个密钥,检查是否有过期的密钥

如果密钥的过期时间小于或等于当前时间,则表示密钥已过期。

如果密钥过期时间大于当前时间,则密钥尚未过期。

如果存在过期密钥,请确定过期密钥数量是否超过提取数量的 25%,或者周期性删除周期是否超过 25 毫秒

如果执行周期超过25%或执行时长超过25ms,则从过期字典中随机抽取20个密钥进行检查,循环至过期密钥数量不超过25%或执行时长不超过25ms。

如果执行时间不超过 25 毫秒,则删除过期的密钥。

如果没有过期密钥,请等待 100 毫秒,然后继续从过期的字典中随机选择 20 个密钥进行检查。

注意:Redis在主线程上会定期删除,如果大量密钥同时过期,会影响Redis的读写效率。

redis2.在版本 8 之后,可以通过修改配置文件来重新开发conf 中的 hz 参数(默认值为 10,即每秒 10 次),用于调整定时删除的定时任务的执行频率。

参数 Hz:Hz 的值范围为 1 500,默认值为 10,不建议超过 100,增加 Hz 的值会占用更多的 CPU 资源,只有当请求延迟很低时,才能考虑将 Hz 的值增加到 100。

Redis 过期删除策略采用延迟删除 + 定期删除作为 CPU 资源和内存消耗之间的权衡

如果使用延迟删除,未访问的密钥将存储在内存中,从而造成内存资源的浪费。

如果只使用周期性删除,当大量密钥同时过期时,会占用大量的CPU资源,影响读写操作的性能和系统吞吐量。

过期删除策略是一种不精确的删除策略,对于那些长时间未访问且多次定期删除的密钥,它们将始终存储在内存中,这可能会导致 Redis 的内存耗尽。

在配置文件redis中conf,最大内存由参数 maxmemory 设置(默认为 unlimited,该参数的值通常设置为物理内存的 3 4)。

当Redis内存超过maxmemory参数设置的最大内存时,触发内存消除策略。 内存消除策略是使用 maxmemory-policy 参数设置的,如下所示:

volatile-lru:设置了过期时间的密钥,通过LRU算法淘汰(lru:最近最少使用,表示使用时间最长)。

allkeys-lru:根据 LRU 算法(环境中使用的常见策略)消除所有密钥(包括有和没有过期时间的密钥)。

volatile-lfu:根据 LFU 算法淘汰有过期时间的密钥(lfu:最不常用)。

allkeys-LFU:基于LFU算法消除所有密钥。

volatile-random:设置了过期时间的密钥,通过随机消除方法进行淘汰。

allkeys-random:根据随机消除方法消除所有键。

volatile-ttl:对于设置了过期时间的密钥,丢弃即将过期的密钥(次要 TTL)。

noeviction:不排除任何数据,当使用的内存空间超过maxmemory参数设置的值时,写入数据到Redis时会返回错误(默认策略,一般不使用)。

可以看出,除了特殊的noeviction和volatile-TTL策略外,其他六种策略可以分为两类:

从 volatile- 开始的策略是清除根据特定算法设置了过期时间的密钥。

从 allkeys- 开始的策略是根据特定算法清除所有键。

redis.conf 中与内存消除策略相关的参数:

maxmemory:内存使用上限,默认值为0,表示内存使用上限不受限制。

maxmemory-policy:内存消除策略,默认策略为 noeviction。

maxmemory-samples:随机样本数,默认值为 5。

内存停用策略选择:

如果数据是幂等的(即某些数据的访问频率更高,而其余数据的访问频率较低),建议您选择 allkeys-lru 或 allkeys-lfu 策略。

如果数据是均匀分布的(即所有数据的访问概率大致相等),建议您选择 allkeys-random 策略。

如果您需要设置不同的TTLS来确定数据过期的顺序,建议您选择Volatile-TTL策略。

如果有些数据需要长时间存储,有些数据可以清除,建议选择 volatile-lru 或 volatile-random 策略。

注意:由于设置过期时间会消耗额外的内存,因此您可以选择 allkeys-lru 策略,以防止 Redis 因确定密钥的过期时间而消耗额外的内存。

要查看 Redis 使用的内存停用策略,请参阅 config get maxmemory-policy。

修改内存消除策略

config set maxmemory-policy Policy:立即生效,重启并恢复为默认值。

修改配置文件 Redisconf Policy中的参数maxmemory-policy:重启生效。

redis - 大致的 LRU 计算执行过程:

随机选择n个key(n由参数maxmemory-samples设置,默认值为5),找到时间最长的未访问密钥(时间通过比较RedisObject中的LRU属性与系统当前时间确定),并删除该密钥。

如果系统内存再次超过内存使用限制,并且仍然超过内存使用限制,则上述过程将继续。

LRU 算法中的缺陷

LRU算法只关注数据的访问时间或顺序,忽略了访问次数的值,在剔除数据的过程中可能会剔除热点数据。

如图所示

图中时间线从左到右,数据a b c在同一时间段内被多次访问。 数据C为最近访问的数据,按照LRU算法排列的数据热度为C>B>A,数据实热度为B>A>C。

为什么 Redis 不只使用 LRU 算法?

如果使用 LRU 算法(基于 hashmap + 双链表:稍后将共享一个 LRU 算法),则需要消耗额外的内存来存储双链表节点中的上一个和下一个指针,从而减少 Redis 的存储空间。

为什么maxmemory-samples参数的默认值为5?

当提取次数为 5 时,执行效率非常接近标准 LRU 算法,虽然提取次数设置为 5 时可以更接近 LRU 算法,但会消耗更多的 CPU 资源,因此提取次数的默认值是 LRU 算法的执行效率和 CPU 资源消耗权衡的结果。

LFU(full name least used used)代表最近使用频率最低的,与关键访问次数有关,其核心思想是,如果一个数据在不久的将来被频繁访问,那么未来被重新访问的概率会很高,访问频率较低的数据将来不会再次使用的概率会很高。

注意:访问LFU算法的频率不能等同于访问次数。 如图所示

图中,数据 A 被访问了 5 次,数据 B 和 C 各被访问了 4 次

如果数据热度值由访问次数决定,则该值为 a>b=c。

如果考虑到时效性,更接近当前时间的访问更有价值,则数据热度值为:c>b>a。

因此,LFU算法一般具有时间衰减函数来参与热值的计算,同时考虑接入时间的影响。

LFU算法实现

LFU 算法的实现不使用额外的数据结构,而是重用 RedisObject 数据结构的 LRU 字段。

redisobject 数据结构:

typedef struct redisobject robj;
其中,LRU算法和LFU算法在LRU字段的使用方式存在差异

在LRU算法中,LRU字段用于记录密钥的访问时间戳,因此可以根据LRU字段中记录的值,将长时间未访问的密钥与当前时间进行比对。

在 LFU 算法中,LRU 字段分为两段,最高 16 位存储 LDT(最后递减时间)和最低 8 位存储逻辑计数器 (LOGC)。

LFU算法中的LRU字段,如图所示:

ldt:密钥的访问时间戳。

logc:用于记录密钥的访问频率(即流行度值),logc值越小,访问频率越低,被淘汰的可能性越大,新密钥的logc初始值为5(目的是防止新密钥立即被淘汰, 并且对新密钥的访问次数可能小于未访问的旧密钥的访问次数)。

LFU接入频率(逻辑计数器)算法实现:

#define lfu_init_val 5/* logarithmically increment a counter. the greater is the current counter value * the less likely is that it gets really implemented. saturate it at 255. */uint8_t lfulogincr(uint8_t counter)
哪里:

如果计数器小于或等于 LFU init val,则一旦访问并命中数据,计数器的概率接近 100%,并增加 1。

如果计数器大于 lfu init val,则需要先计算两者之间的差值,然后作为分母的一部分参与计算递增概率。

随着计数器值的增加,增加的概率逐渐衰减,并且多次访问可能不会将其值增加 1。

当计数器值达到 255 时,不再执行值递增过程。

为了适应各种业务数据的特点,Redis 在 LFU 算法的实现中引入了两个可调参数:

lfu-decay-time:时间衰减因子,单位为分钟,默认值为1,该参数值越高,衰减越慢。

lfu-log-factor:递增衰减因子,默认值为10,该参数值越高,递增概率越低。

阅读推荐]更多精彩内容,如:

Redis 系列。

数据结构和算法。

NACOS系列。

MySQL系列。

JVM 系列。

卡夫卡系列。

并发编程系列。

请移至【南秋】个人主页参考。 内容不断更新。

关于作者]热爱科技、热爱生活的老宝贝,专注J**A领域,关注【南秋同学】带你一起学习、共同成长

相似文章

    One-shot soul:基于Skywalking实现业务链路追踪、监控、告警

    SkyWalking 是一个开源的应用程序性能监控系统,包括链路跟踪 日志监控 分析和告警处理。Skywalking支持SpringBoot SpringCloud Dubbo等主流框架,非侵入式,通信方式采用GRPC,性能良好,实现方式为J A代理 探针 Skywalking 相对于 Sleuth...

    One Shot into the Soul JVM 类加载机制

    JVM 组件如图所示 JVM 的主要组件包括运行时数据区 执行引擎 本地接口和本地方法库。本文主要分享 JVM 的类加载机制。在 j a 中,每个类或接口都由编译器编译以生成相应的。类文件。类加载机制是指将要编译和生成的内容。将类文件 二进制数据 加载到内存中,并对数据进行验证 解析和初始化。最后,...

    俄罗斯导弹一击中灵魂,美国就坐不住了

    乌克兰 爆料,泽连斯基指责俄军发动 恐怖袭击 向乌克兰第聂伯罗彼得罗夫斯克州投掷导弹,其中一枚恰好击中了乌克兰 局大楼,造成数十名乌克兰军官死亡,可谓是一箭射穿心脏。泽连斯基声称俄罗斯将付出代价。两天后,莫斯科再次遭到袭击。月日,莫斯科市长索比亚宁表示,当天凌晨,三架无人机对莫斯科发动袭击,但幸运的...

    哈马斯接连发射火箭弹,10天内炸毁了180辆以色列军车

    在联合国大会通过一项决议,呼吁加沙地带立即实现人道主义停火之际,中东的紧张局势继续加剧。该决议得到个国家的支持,反映了国际社会对当前冲突的普遍态度。然而,在这次投票中,我们也看到个国家投了反对票。哈马斯和巴勒斯坦常驻联合国代表里亚德 曼苏尔都对该决议表示欢迎,他强调希望以色列按照联合国秘书长和安全理...

    抓住每一个机会抓住“1000万”

    有一位女士可能被电信欺骗了。接近中午,下午。河西区某银行工作人员。紧急拨通河西区反诈骗中心 说张大亮 化名 焦急地来到银行。坚持将万元共转入个账户。银行工作人员立即提高了警惕。并立即寻求帮助。收到预警后。公安河西分局犯罪侦查支队。与陈堂庄派出所人民警察辅警一起。第一时间赶到现场处置 当警察到达时。我...