DB结构体
  Redis默认有16个数据库,存储数据前必须先通过SELECT INDEX来指定DB(默认index为0,DB结构体对应server.h/redisDb),DB主要存储并维护键值对信息。值得注意的是Redis目前没有命令可以获取当前正在操作的库,所以比较好的做法是每次操作前select。

typedef struct redisDb {dict *dict;                 /* The keyspace for this DB */dict *expires;              /* Timeout of keys with a timeout set */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 */struct evictionPoolEntry *eviction_pool;    /* Eviction pool of keys */int id;                     /* Database ID */long long avg_ttl;          /* Average TTL, just for stats */
} redisDb;

dict字典又称键空间,存储了所有键值对,值是五大类型之一;expires存储的是设置了过期时间的key与其失效时间的键值对;

Key读写
  当用户读取某个Key时,如果dict中不存在该Key,则miss次数+1并返回客户端空;否则hit次数+1并且更新键的最后一次使用时间,通过这个值可以计算出key的闲置时间,闲置时间的长短又会影响回收策略,如果Key已失效则同时清除dict与expires中的键值对,并向Slave发送DEL命令。通过info stats可以查看hist和miss等信息,通过Object idletime key可以查看key的闲置时间。
  当写入某个Key时,如果dict中已经存在该key则进行对应值的更新,否则存入新的键值对。对设置了过期时间的Key写入时同时也存入一份到expires字典当中,删除时同样也清除expires中对应键值对。

Key过期时间设置
   redis有四个命令expire、pexpire、expireat、pexpireat可以绑定key的过期时间(persist可以解除绑定)。expire和pexpire设置key可以存活的时间,expireat和pexpireat 设置key失效的时间点,前者以秒为单位,后者以毫秒为单位,前三个命令底层使用的都是pexpireat。

Key过期清理策略

  1. 定时清理:在设置k/v对时,同时开启一个定时器,或将失效时间点注入某个定时器,在有效时间到达后移除该键值对。这种方式一般能尽快释放被占用的内存,但对CPU性能消耗很严重,特别是在大量数据或大量连续请求的情况下,根本没这么多CPU资源拿来消耗,实际中并不常见。
  2. 惰性清理:在客户端请求某个key时才检测其有效性。这种方式不会像定时清理一样大量消耗CPU资源,但对于内存却可能产生内存泄漏,如果客户端永远不请求那些失效的key,那么这些key就会一直驻留在内存中,永远无法释放(flush等操作除外)。
  3. 定期清理:周期性的检测key的有效性(不一定是全量key),这种方式即不会大量消耗CPU资源,也不会导致内存泄漏,但key基本上不会被及时清理,实际中清理的时机与频率并不容易确定。

Redis综合采用了惰性清理与定期清理策略,关于惰性清理在上方的已经说明了,这里分析一下定期清理的源码,在3.2.8版本中源码位于server.c/activeExpireCycle,这个函数通过周期性调用serverCron().databasesCron()来间接调用,频率默认为
#define CONFIG_DEFAULT_HZ   10    /* Time interrupt calls/sec. */ 每秒10次,源码如下:

/**type=ACTIVE_EXPIRE_CYCLE_FAST表示快清理; type=ACTIVE_EXPIRE_CYCLE_SLOW表示慢清理;*/
void activeExpireCycle(int type) {static unsigned int current_db = 0; /* 当前清理的DB */static int timelimit_exit = 0;      /* 执行时长是否已到 */static long long last_fast_cycle = 0; /* 上次运行时间. */int j, iteration = 0;int dbs_per_call = CRON_DBS_PER_CALL; /* 默认请理DB数量:16*/long long start = ustime(), timelimit;if (clientsArePaused()) return;if (type == ACTIVE_EXPIRE_CYCLE_FAST) {/* 上次快清理尚未结束,本次不开启 */if (!timelimit_exit) return;if (start < last_fast_cycle + ACTIVE_EXPIRE_CYCLE_FAST_DURATION*2) return;last_fast_cycle = start;}/* 确定DB数量 */if (dbs_per_call > server.dbnum || timelimit_exit)dbs_per_call = server.dbnum;/* 慢清理的执行时长: 1000000 * 25/10/100 = 25000 == 25毫秒 */    timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;timelimit_exit = 0;if (timelimit <= 0) timelimit = 1;/* 快清理执行时长:1毫秒 */if (type == ACTIVE_EXPIRE_CYCLE_FAST)timelimit = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; /* in microseconds. *//* 迭代DB */for (j = 0; j < dbs_per_call; j++) {int expired;redisDb *db = server.db+(current_db % server.dbnum);/* DB索引加1,指向下一个要处理的DB */current_db++;/* 从当前DB中随机抽取最多20个key* 1:获取每个key的ttl,算出平均ttl* 2:清理失效的key,并记录失效key的个数,如果失效key的个数不超过1/4(少于5个),则认为当前DB暂不需要清理,执行下个DB的处理。* 3:每执行16次,则检测是否执行时长已到,执行时间已达上限则退出本次清理。等待下次周期性调用*/do {unsigned long num, slots;long long now, ttl_sum;int ttl_samples;if ((num = dictSize(db->expires)) == 0) {db->avg_ttl = 0;break;}slots = dictSlots(db->expires);now = mstime();if (num && slots > DICT_HT_INITIAL_SIZE &&(num*100/slots < 1)) break;expired = 0;ttl_sum = 0;ttl_samples = 0;/* 随机抽取最多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 (ttl > 0) {                   ttl_sum += ttl;ttl_samples++;}}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);}/* 每执行16次检测一次执行时长 */iteration++;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;/* 失效个数>1/4则继续,否则结束对当前DB的清理. */} while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);}
}

基本上这个函数的工作模式可以概括如下: 
1)迭代一定数量的DB,最大数量16。
2)从每个DB中随机抽取最多20个key
         * 1:获取每个key的ttl,用于统计求平均ttl
         * 2:清理失效的key,并记录失效key的个数,如果失效key的个数不超过1/4(少于5个),则认为当前DB暂不需要清理,执行下个DB的处理。
         * 3:为了防止长时间的阻塞,每执行16次检测是否执行时长已到,执行时间已达上限则退出本次清理,等待下次周期性调用。但当DB数量不足16个时,此步不会被执行,此时即使执行时长已到,也不会结束,直到最后清理完成才会终止。

RDB和AOF对过期Key的处理
  
RDB和AOF重写本质上都是存储当前内存中的数据,只不过RDB以数据文件的形式存储,而重写AOF则是以命令形式存储,但是对于内存中那些已经失效的Key,两者都不会存储,对于RDB来说只需要保存可用数据,没必要在加载后再进行清理无效数据,对于AOF来说只需要保存最小指令集,也没必要保存写入和删除两条指令。但正常的AOF对于失效Key,会追加一条Del指令到aof文件中。

主从节点对过期的Key的处理
  对于从节点而言由于它的数据来源于主节点,为了保持与主节点状态一致,在Master发送删除指令之前,它不会采用任何策略来清理失效的key, 但针对失效Key的所有请求都会返回空。之所以返回空,是因为从节点在key失效但尚未接收到或丢失主节点del命令的情况下,需要与主节点保持一致的响应。

总结:

  • Redis数据库主要由dict和expires两个字典构成,其中dict字典负责保存键值对,键是一个字符串,值是五大类型之一;expires字典则负责保存键与过期时间对。所以对键的操作都是建立在字典操作之上
  • Redis使用惰性删除和定期删除两种策略来删除过期的键
  • 一个失效键被删除之后,会追加一条DEL到现有AOF文件中,但对于RDB或AOF重写都不会包含已经过期的键
  • 主节点删除某个键时,会向所有从节点发送一条DEL命令;对于从节点来说,为了保持与主节点的一致性,在未接收到DEL命令前不会采用任何策略删除它,但对失效key的所有请求都返回空。

Redis(四):Key读写及过期策略相关推荐

  1. Redis系列(三)--过期策略

    制定Redis过期策略,是整个Redis缓存策略的关键之一,因为内存来说,公司不可能无限大,所以就要对key进行一系列的管控. 文章结构: (1)理解Redis过期设置API(命令与Java描述版本) ...

  2. redis同步效率秒_redis过期策略、内存淘汰策略、持久化方式、主从复制

    一.Redis的过期策略以及内存淘汰策略: 1.过期策略:定期删除+惰性删除: ①定期删除:redis默认每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果有过期就删除.注意这里 ...

  3. Redis的持久化机制、过期策略、淘汰策略

    文章目录 一.持久化机制 1. RDB机制 2. AOF机制 RDB的优缺点 AOF的优缺点 持久化机制选择 二.过期策略 常见过期策略 Redis过期策略 1. 定期删除 2. 惰性删除 RDB对过 ...

  4. Redis(五)Redis内存维护方案(过期策略及淘汰策略)

    目录 内存维护解决方案 过期策略 内存淘汰策略 内存维护解决方案 在Redis的配置文件中redis.conf 中memeory managment # maxmemory <bytes> ...

  5. 【Redis扩展篇(一)】过期策略

    Redis过期策略 0. 前言 Redis所有的数据结构都可以设置过期时间,时间一到就会被自动删除.但是会不会因为统一时间太多的key过期,导致Redis执行执行出现卡顿.因为Redis是单线程的,收 ...

  6. Redis和Memcached:数据类型 过期策略 持久策略 虚拟内存 Value大小

    1.Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash.bitmaps.hyperloglog.geo  2.虚拟内存Redis当物理内存用完时,可以将一些很久没用到的v ...

  7. Redis 更新key值导致过期时间失效问题

    场景再现 首先,往redis里面存一个key, 然后,设置超时时间为300s, 如下图所示 紧接着,更新name的值, 问题来了,重新设置了name的值之后,这个key的过期时间是多少呢? A 过期时 ...

  8. Redis[5] key的过期时间删除策略、实现lru算法、持久化配置

    文章目录 Redis[5] key的过期时间删除策略.持久化配置 **Redis6的key过期时间删除策略** Redis服务器实际使用的是惰性删除和定期删除两种策略:通过配合使用这两种删除策略,服务 ...

  9. Redis学习笔记--Redis数据过期策略详解==转

    本文对Redis的过期机制简单的讲解一下 讲解之前我们先抛出一个问题,我们知道很多时候服务器经常会用到redis作为缓存,有很多数据都是临时缓存一下,可能用过之后很久都不会再用到了(比如暂存sessi ...

  10. php redis hset过期时间,详解Redis中数据过期策略

    相信大家对Redis中数据过期有点了解,本文主要介绍了Redis中的数据过期策略,文中通过示例代码介绍的很详细,相信对大家的理解和学习具有一定的参考借鉴价值,有需要的朋友可以参考借鉴,希望能帮助到大家 ...

最新文章

  1. 基于PyTorch框架的多层全连接神经网络实现MNIST手写数字分类
  2. 最短路算法模板--SPFA
  3. 远程桌面mstsc命令参数的使用
  4. Python查找包含指定字符串的所有文件
  5. NodeJs将项目上传至服务器
  6. 项目经理需要具备的技能
  7. jpg/png格式的图片转换成eps格式
  8. 【渝粤题库】陕西师范大学201931 唐诗研究 作业
  9. PHPStorm+Xdebug配置(phpStudy)
  10. linux 误删文件恢复
  11. 学习安卓的简单心得,以及LinearLayout的简单使用
  12. android微信小程序自动填表_微信“填表”类小程序,你可能根本没用对
  13. 余淼杰老师 经济学原理复习笔记(宏观1) 2020-12-14
  14. java.io.IOException: Incomplete output stream
  15. 利用sEMG能量高斯分布特性提取动作信号的方法
  16. nexus5x刷最新android M
  17. 设置CCS的license
  18. 大数据仓库技术实训任务3
  19. 全球及中国汽车仿真硬件在环测试行业研究及十四五规划分析报告
  20. 电话主叫号码信息的识别及实现CID

热门文章

  1. 机器学习之加州房价预测(一)
  2. 实战HTML:部分美团首页静态界面
  3. 温暖(warmth)
  4. python怎么算积分_蒙特卡洛方法求定积分及python实现(转)
  5. 车载以太网交换机功能和应用案例汇总, 适用于AVB/TSN, 802.1AS(gPTP时钟同步)
  6. 不玩手机的步步高玩大数据:一条短信让你多买一只澳洲大龙虾
  7. 通过添加css样式cursor属性,改变鼠标的外形,变成放大镜
  8. 查看GitHub仓库大小的几种方法
  9. 实习期间工作、学习、成长、收获总结
  10. matlab2016a配置vs2013编译器