后端面试每日一题 Redis 的过期策略和内存淘汰机制

void activeExpireCycle(int type) {

static unsigned int current_db = 0; /* 上次定期删除遍历到的数据库ID */

static int timelimit_exit = 0;

static long long last_fast_cycle = 0; /* 上次执行定期删除的时间点 */

int j, iteration = 0;

int dbs_per_call = CRON_DBS_PER_CALL; // 需要遍历数据库的数量

long long start = ustime(), timelimit, elapsed;

if (clientsArePaused()) return;

if (type == ACTIVE_EXPIRE_CYCLE_FAST) {

if (!timelimit_exit) return;

// ACTIVE_EXPIRE_CYCLE_FAST_DURATION 快速定期删除的执行时长

if (start < last_fast_cycle + ACTIVE_EXPIRE_CYCLE_FAST_DURATION*2) return;

last_fast_cycle = start;

}

if (dbs_per_call > server.dbnum || timelimit_exit)

dbs_per_call = server.dbnum;

// 慢速定期删除的执行时长

timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;

timelimit_exit = 0;

if (timelimit <= 0) timelimit = 1;

if (type == ACTIVE_EXPIRE_CYCLE_FAST)

timelimit = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; /* 删除操作花费的时间 */

long total_sampled = 0;

long total_expired = 0;

for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {

int expired;

redisDb *db = server.db+(current_db % server.dbnum);

current_db++;

do {

// …

expired = 0;

ttl_sum = 0;

ttl_samples = 0;

// 每个数据库中检查的键的数量

if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)

num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;

// 从数据库中随机选取 num 个键进行检查

while (num–) {

dictEntry *de;

long long ttl;

if ((de = dictGetRandomKey(db->expires)) == NULL) break;

ttl = dictGetSignedInteger

// 过期检查,并对过期键进行删除

if (activeExpireCycleTryExpire(db,de,now)) expired++;

if (ttl > 0) {

ttl_sum += ttl;

ttl_samples++;

}

total_sampled++;

}

total_expired += expired;

if (ttl_samples) {

long long avg_ttl = ttl_sum/ttl_samples;

if (db->avg_ttl == 0) db->avg_ttl = avg_ttl;

db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50);

}

if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */

elapsed = ustime()-start;

if (elapsed > timelimit) {

timelimit_exit = 1;

server.stat_expired_time_cap_reached_count++;

break;

}

}

/* 判断过期键删除数量是否超过 25% */

} while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);

}

// …

}

定期删除的执行流程,如下图所示:

img

小贴士:Redis 服务器为了保证过期删除策略不会导致线程卡死,会给过期扫描增加了最大执行时间为 25ms。

以上是 Redis 服务器对待过期键的处理方案,当 Redis 的内存超过最大允许的内存之后,Redis 会触发内存淘汰策略,这和过期策略是完全不同的两个概念,经常有人把二者搞混,这两者一个是在正常情况下清除过期键,一个是在非正常情况下为了保证 Redis 顺利运行的保护策略。

当 Redis 内存不够用时,Redis 服务器会根据服务器设置的淘汰策略,删除一些不常用的数据,以保证 Redis 服务器的顺利运行。

Redis 内存淘汰策略

我们可以使用 config get maxmemory-policy 命令,来查看当前 Redis 的内存淘汰策略,示例代码如下:

127.0.0.1:6379> config get maxmemory-policy

  1. “maxmemory-policy”

  2. “noeviction”

从上面的结果可以看出,当前 Redis 服务器设置的是“noeviction”类型的内存淘汰策略,那么这表示什么含义呢?Redis 又有几种内存淘汰策略呢?

在 4.0 版本之前 Redis 的内存淘汰策略有以下 6 种。

  • noeviction:不淘汰任何数据,当内存不足时,执行缓存新增操作会报错,它是 Redis 默认内存淘汰策略。

  • allkeys-lru:淘汰整个键值中最久未使用的键值。

  • allkeys-random:随机淘汰任意键值。

  • volatile-lru:淘汰所有设置了过期时间的键值中最久未使用的键值。

  • volatile-random:随机淘汰设置了过期时间的任意键值。

  • volatile-ttl:优先淘汰更早过期的键值。

可以看出我们上面示例使用的是 Redis 默认的内存淘汰策略“noeviction”。

而在 Redis 4.0 版本中又新增了 2 种淘汰策略:

  • volatile-lfu,淘汰所有设置了过期时间的键值中最少使用的键值;

  • allkeys-lfu,淘汰整个键值中最少使用的键值。

小贴士:从以上内存淘汰策略中可以看出,allkeys-xxx 表示从所有的键值中淘汰数据,而 volatile-xxx 表示从设置了过期键的键值中淘汰数据。

这个内存淘汰策略我们可以通过配置文件来修改,redis.conf 对应的配置项是“maxmemory-policy noeviction”,只需要把它修改成我们需要设置的类型即可。

需要注意的是,如果使用修改 redis.conf 的方式,当设置完成之后需要重启 Redis 服务器才能生效。

还有另一种简单的修改内存淘汰策略的方式,我们可以使用命令行工具输入“config set maxmemory-policy noeviction”来修改内存淘汰的策略,这种修改方式的好处是执行成功之后就会生效,无需重启 Redis 服务器。但它的坏处是不能持久化内存淘汰策略,每次重启 Redis 服务器之后设置的内存淘汰策略就会丢失。

Redis 内存淘汰算法

内存淘汰算法主要包含两种:LRU 淘汰算法和 LFU 淘汰算法。

LRU( Least Recently Used,最近最少使用)淘汰算法:是一种常用的页面置换算法,也就是说最久没有使用的缓存将会被淘汰。

LRU 是基于链表结构实现的,链表中的元素按照操作顺序从前往后排列,最新操作的键会被移动到表头,当需要进行内存淘汰时,只需要删除链表尾部的元素即可。

Redis 使用的是一种近似 LRU 算法,目的是为了更好的节约内存,它的实现方式是给现有的数据结构添加一个额外的字段,用于记录此键值的最后一次访问时间。Redis 内存淘汰时,会使用随机采样的方式来淘汰数据,它是随机取 5 个值 (此值可配置) ,然后淘汰最久没有使用的数据。

LFU(Least Frequently Used,最不常用的)淘汰算法:最不常用的算法是根据总访问次数来淘汰数据的,它的核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。

LFU 相对来说比 LRU 更“智能”,因为它解决了使用频率很低的缓存,只是最近被访问了一次就不会被删除的问题。如果是使用 LRU 类似这种情况数据是不会被删除的,而使用 LFU 的话,这个数据就会被删除。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

对于很多Java工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。

整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

再分享一波我的Java面试真题+视频学习详解+技能进阶书籍

美团二面惜败,我的凉经复盘(附学习笔记+面试整理+进阶书籍)

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
的学习效果低效漫长且无助。**

整理的这些资料希望对Java开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

再分享一波我的Java面试真题+视频学习详解+技能进阶书籍

[外链图片转存中…(img-Rgy9tRjv-1713584335945)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 17
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值