背景

阿里云Redis作为一个高性能的内存NoSQL数据库,其容量受到最大内存限制的限制。

用户在使用阿里云Redis时,除了对性能,稳定性有很高的要求外,对内存占用也比较敏感。在使用过程中,有些用户会觉得自己的线上实例内存占用比自己预想的要大。

用阿里云Redis请领取阿里云幸运券:点我领取

事实上,实例中的内存除了保存原始的键值对所需的开销外,还有一些运行时产生的额外内存,包括:

  1. 垃圾数据和过期Key所占空间
  2. 字典渐进式Rehash导致未及时删除的空间
  3. Redis管理数据,包括底层数据结构开销,客户端信息,读写缓冲区等
  4. 主从复制,bgsave时的额外开销
  5. 其它

本系列文章主要分析这些在Redis中产生的原因,带来的影响和规避的方式。

本文主要分析第一项Redis过期策略对内存的影响。

Redis过期数据清理策略

过期数据清理时机

为了防止一次性清理大量过期Key导致Redis服务受影响,Redis只在空闲时清理过期Key。

具体Redis逐出过期Key的时机为:

  1. 访问Key时,会判断Key是否过期,逐出过期Key;
  robj *lookupKeyRead(redisDb *db, robj *key) {robj *val;expireIfNeeded(db,key);val = lookupKey(db,key);...return val;
}
  1. CPU空闲时在定期serverCron任务中,逐出部分过期Key;
        aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL)int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {...databasesCron();...}void databasesCron(void) {/* Expire keys by random sampling. Not required for slaves+ as master will synthesize DELs for us. */if (server.active_expire_enabled && server.masterhost == NULL)activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);...}
  1. 每次事件循环执行的时候,逐出部分过期Key;
        void aeMain(aeEventLoop *eventLoop) {eventLoop->stop = 0;while (!eventLoop->stop) {if (eventLoop->beforesleep != NULL)eventLoop->beforesleep(eventLoop);aeProcessEvents(eventLoop, AE_ALL_EVENTS);}}void beforeSleep(struct aeEventLoop *eventLoop) {.../* Run a fast expire cycle (the called function will return- ASAP if a fast cycle is not needed). */if (server.active_expire_enabled && server.masterhost == NULL)activeExpireCycle(ACTIVE_EXPIRE_CYCLE_FAST);...}

过期数据清理算法

Redis过期Key清理的机制对清理的频率和最大时间都有限制,在尽量不影响正常服务的情况下,进行过期Key的清理,以达到长时间服务的性能最优.

Redis会周期性的随机测试一批设置了过期时间的key并进行处理。测试到的已过期的key将被删除。具体的算法如下:

  1. Redis配置项hz定义了serverCron任务的执行周期,默认为10,即CPU空闲时每秒执行10次;
  2. 每次过期key清理的时间不超过CPU时间的25%,即若hz=1,则一次清理时间最大为250ms,若hz=10,则一次清理时间最大为25ms;
  3. 清理时依次遍历所有的db;
  4. 从db中随机取20个key,判断是否过期,若过期,则逐出;
  5. 若有5个以上key过期,则重复步骤4,否则遍历下一个db;
  6. 在清理过程中,若达到了25%CPU时间,退出清理过程;

这是一个基于概率的简单算法,基本的假设是抽出的样本能够代表整个key空间,redis持续清理过期的数据直至将要过期的key的百分比降到了25%以下。这也意味着在长期来看任何给定的时刻已经过期但仍占据着内存空间的key的量最多为每秒的写操作量除以4.

  • 由于算法采用的随机取key判断是否过期的方式,故几乎不可能清理完所有的过期Key;
  • 调高hz参数可以提升清理的频率,过期key可以更及时的被删除,但hz太高会增加CPU时间的消耗;Redis作者关于hz参数的一些讨论

代码分析如下:

void activeExpireCycle(int type) {.../* We can use at max ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC percentage of CPU time* per iteration. Since this function gets called with a frequency of* server.hz times per second, the following is the max amount of* microseconds we can spend in this function. */// 最多允许25%的CPU时间用于过期Key清理// 若hz=1,则一次activeExpireCycle最多只能执行250ms// 若hz=10,则一次activeExpireCycle最多只能执行25mstimelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;...// 遍历所有dbfor (j = 0; j < dbs_per_call; j++) {int expired;redisDb *db = server.db+(current_db % server.dbnum);/* Increment the DB now so we are sure if we run out of time* in the current DB we'll restart from the next. This allows to* distribute the time evenly across DBs. */current_db++;/* Continue to expire if at the end of the cycle more than 25%* of the keys were expired. */do {...// 一次取20个Key,判断是否过期if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;while (num--) {dictEntry *de;long long ttl;if ((de = dictGetRandomKey(db->expires)) == NULL) break;ttl = dictGetSignedIntegerVal(de)-now;if (activeExpireCycleTryExpire(db,de,now)) expired++;}if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */long long elapsed = ustime()-start;latencyAddSampleIfNeeded("expire-cycle",elapsed/1000);if (elapsed > timelimit) timelimit_exit = 1;}if (timelimit_exit) return;/* We don't repeat the cycle if there are less than 25% of keys* found expired in the current DB. */// 若有5个以上过期Key,则继续直至时间超过25%的CPU时间// 若没有5个过期Key,则跳过。} while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);}
}

Redis数据逐出策略

数据逐出时机

// 执行命令
int processCommand(redisClient *c) {.../* Handle the maxmemory directive.**First we try to free some memory if possible (if there are volatile* keys in the dataset). If there are not the only thing we can do* is returning an error. */if (server.maxmemory) {int retval = freeMemoryIfNeeded();...}...
}

数据逐出算法

在逐出算法中,根据用户设置的逐出策略,选出待逐出的key,直到当前内存小于最大内存值为主.

可选逐出策略如下:

  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用 的数据淘汰
  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数 据淘汰
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据 淘汰
  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  • no-enviction(驱逐):禁止驱逐数据

具体代码如下

int freeMemoryIfNeeded() {...// 计算mem_usedmem_used = zmalloc_used_memory();.../* Check if we are over the memory limit. */if (mem_used <= server.maxmemory) return REDIS_OK;// 如果禁止逐出,返回错误if (server.maxmemory_policy == REDIS_MAXMEMORY_NO_EVICTION)return REDIS_ERR; /* We need to free memory, but policy forbids. */mem_freed = 0;mem_tofree = mem_used - server.maxmemory;long long start = ustime();latencyStartMonitor(latency);while (mem_freed < mem_tofree) {int j, k, keys_freed = 0;for (j = 0; j < server.dbnum; j++) {// 根据逐出策略的不同,选出待逐出的数据long bestval = 0; /* just to prevent warning */sds bestkey = NULL;struct dictEntry *de;redisDb *db = server.db+j;dict *dict;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;/* volatile-random and allkeys-random policy */if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM ||server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_RANDOM){de = dictGetRandomKey(dict);bestkey = dictGetKey(de);}/* volatile-lru and allkeys-lru policy */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 = dictGetKey(de);/* When policy is volatile-lru we need an additional 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);o = dictGetVal(de);thisval = estimateObjectIdleTime(o);/* Higher idle time is better candidate for deletion */if (bestkey == NULL || thisval > bestval) {bestkey = thiskey;bestval = thisval;}}}/* volatile-ttl */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 = dictGetKey(de);thisval = (long) dictGetVal(de);/* Expire sooner (minor expire unix timestamp) is better* candidate for deletion **/if (bestkey == NULL || thisval < bestval) {bestkey = thiskey;bestval = thisval;}}}/* Finally remove the selected key. **/// 逐出挑选出的数据if (bestkey ) {...delta = (long long) zmalloc_used_memory();dbDelete(db,keyobj);delta -= (long long) zmalloc_used_memory();mem_freed += delta;...}}...}...return REDIS_OK;
}

相关最佳实践

  • 不要放垃圾数据,及时清理无用数据
    实验性的数据和下线的业务数据及时删除;
  • key尽量都设置过期时间
    对具有时效性的key设置过期时间,通过redis自身的过期key清理策略来降低过期key对于内存的占用,同时也能够减少业务的麻烦,不需要定期手动清理了.
  • 单Key不要过大
    给用户排查问题时遇到过单个string的value有43M的,也有一个list 100多万个大成员占了1G多内存的。这种key在get的时候网络传输延迟会比较大,需要分配的输出缓冲区也比较大,在定期清理的时候也容易造成比较高的延迟. 最好能通过业务拆分,数据压缩等方式避免这种过大的key的产生。
  • 不同业务如果公用一个业务的话,最好使用不同的逻辑db分开
    从上面的分析可以看出,Redis的过期Key清理策略和强制淘汰策略都会遍历各个db。将key分布在不同的db有助于过期Key的及时清理。另外不同业务使用不同db也有助于问题排查和无用数据的及时下线.

阿里云Redis数据过期和淘汰策略解答相关推荐

  1. Redis缓存过期和淘汰策略

    题记: 文章内容输出来源:拉勾教育Java高薪训练营. 本篇文章是 Redis 学习课程中的一部分笔记. Redis缓存过期和淘汰策略 Redis性能高: 官方数据 读:110000次/s 写:810 ...

  2. redis 转义字符_一份完整的阿里云 Redis 开发规范,值得收藏!

    来源:yq.aliyun.com/articles/531067 本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明. 键值设计 命令使用 客户端使用 相关工具 通过本文的介绍可以减 ...

  3. Redis的过期键删除策略和内存淘汰机制

    一.过期键的判定 通过过期字典,程序可以用以下步骤检查一个给定键的过期时间: (1)检查给定键是否存在于过期字典:如果存在就取出来过期时间: (2)检查当前的UNIX时间戳是否大于键的过期时间,如果是 ...

  4. 关于Redis数据过期策略

    前言 在项目中某场景下,需要频繁去设置redis数据的过期时间,因此去了解了下redis数据过期策略.原文地址:关于Redis数据过期策略 一.Redis中key的的过期时间 通过EXPIRE key ...

  5. 阿里云 Redis 开发规范

    摘要:本文介绍了在使用阿里云Redis的开发规范,从键值设计.命令使用.客户端使用.相关工具等方面进行说明,通过本文的介绍可以减少使用Redis过程带来的问题. 一.键值设计 1. key名设计 (1 ...

  6. 阿里云Redis开发规范

    本文作者:carlosfu 原文链接:https://yq.aliyun.com/articles/531067 摘要: 本文介绍了在使用阿里云Redis的开发规范,从键值设计.命令使用.客户端使用. ...

  7. 收藏 | 阿里云Redis开发规范

    原文链接:https://yq.aliyun.com/articles/531067 摘要: 本文介绍了在使用阿里云Redis的开发规范,从键值设计.命令使用.客户端使用.相关工具等方面进行说明,通过 ...

  8. 阿里云Redis开发规范[转]

    一.键值设计 1. key名设计 (1)[建议]: 可读性和可管理性 以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id ugc:video:1 (2)[建议]:简洁性 ...

  9. 【转载】一份完整的阿里云 Redis 开发规范,值得收藏!

    来源:yq.aliyun.com/articles/531067 作者:付磊-起扬 本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明. 键值设计 命令使用 客户端使用 相关工具 通 ...

最新文章

  1. STM32 电机教程 23 - ST MCLIB实战之基于stm32f13c8tx构建FOC工程
  2. 猫、狗与Java的多态
  3. android gpu 视频编码,android – 编码器崩溃对Adreno GPU编码从Surface
  4. 传递给系统调用的数据区域太小怎么解决_一口气说出“分布式追踪系统”原理!...
  5. 九年级计算机上册教学总结,九年级信息技术教学工作总结
  6. 华为发布MetaAAU 能耗降低30% 性能节能双提升
  7. API/POSIX/C库的区别与联系
  8. 字符串中索引位置是什么意思_女孩子左手中指戴戒指什么意思 不同位置各有不同...
  9. Linux ls按时间排列
  10. 元宇宙里过节,英伟达快速打造「冬日仙境」,占地 16 万平米!
  11. Linux 误删除文件恢复
  12. SharepointDesigner创建一个工作流
  13. ffmpeg从视频文件中提取音频数据
  14. jQuery实现表格行的动态增加与删除(改进版)
  15. 5M1E分析法—质量管理纵向无死角
  16. CNVD-2022-03672/CNVD-2022-10270:向日葵简约版/向日葵个人版for Windows命令执行漏洞复现及修复建议
  17. JSON格式的文件转换对象存入数据库
  18. 张小龙:通过微信谈产品
  19. 中国数据标注公司排名前十强有哪些?
  20. 数据中心交换机横向虚拟化集群漫谈

热门文章

  1. 白嫖的冷知识项目教程,教你如何做一个抖音冷知识账号,多种变现方式
  2. 学习日记——基于LiteOS Studio的智慧物流案例开发(2020.2.20)
  3. vr全景图加载并播放
  4. 计算机动漫课程建设方案,动漫课程实施方案范文
  5. HTTP 临时重定向302与307的区别与联系
  6. HTTP的303、307状态码
  7. 十二星座程序猿,谁赚钱最多?
  8. ai背景合成_哪个小姐姐是假的?Yann LeCun说合成人脸并不难分辨
  9. ZXing3.3.3 生成二维码带logo
  10. 【Twomon SE】让你的ipad 变成电脑副屏 提高代码效率