您当前的位置:首页 > 电脑百科 > 数据库 > Redis

Redis 的过期策略是如何实现的?

时间:2019-10-16 10:45:24  来源:  作者:

背景

链接:https://juejin.im/post/5da3dc4c518825647c513aa1

来源:掘金

为了减少占用内存空间,通常会对放到 redis 中的键通过 expire 设置一个过期时间,那 Redis 是怎么实现对过期键删除的呢?

设置过期时间

设置过期时间的四种方式

# 将 key 的过期时间设置为 ttl 秒
expire <key> <ttl> 
# 将 key 的过期时间设置为 ttl 毫秒
pexpire <key> <ttl>
# 将 key 的过期时间设置为 timestamp 指定的秒数时间戳
expire <key> <timestamp>
# 将 key 的过期时间设置为 timestamp 指定的毫秒数时间戳
pexpire <key> <timestamp>复制代码

其中前三种方式都会转化为最后一种方式来实现过期时间

Redis 的过期策略是如何实现的?

 


Redis 的过期策略是如何实现的?

 

保存过期时间

我们看下 redisDb 的结构

typedef struct redisDb {
 dict *dict; /* The keyspace for this DB */
 dict *expires; /* Timeout of keys with a timeout set */
 ...
}复制代码

可见在 redisDb 结构的 expire 字典(过期字典)保存了所有键的过期时间

过期字典的键是一个指向键空间中的某个键对象的指针

过期字典的值保存了键所指向的数据库键的过期时间

Redis 的过期策略是如何实现的?

 

注意

图中过期字段和键空间中键对象有重复,实际中不会出现重复对象,键空间的键和过期字典的键都指向同一个键对象

过期键的判断

通过查询过期字典,检查下面的条件判断是否过期

  1. 检查给定的键是否在过期字典中,如果存在就获取键的过期时间
  2. 检查当前 UNIX 时间戳是否大于键的过期时间,是就过期,否则未过期

过期键的删除策略

惰性删除

在取出该键的时候对键进行过期检查,即只对当前处理的键做删除操作,不会在其他过期键上花费 CPU 时间

缺点:对内存不友好,如果一哥键过期了,但会保存在内存中,如果这个键还不会被访问,那么久会造成内存浪费,甚至造成内存泄露

如何实现?

就是在执行 Redis 的读写命令前都会调用 expireIfNeeded 方法对键做过期检查

如果键已经过期,expireIfNeeded 方法将其删除

如果键未过期,expireIfNeeded 方法不做处理

Redis 的过期策略是如何实现的?

 

对应源码 db.c/expireIfNeeded 方法

int expireIfNeeded(redisDb *db, robj *key) {
 // 键未过期返回0
 if (!keyIsExpired(db,key)) return 0;
 // 如果运行在从节点上,直接返回1,因为从节点不执行删除操作,可以看下面的复制部分
 if (server.masterhost != NULL) return 1;
 // 运行到这里,表示键带有过期时间且运行在主节点上
 // 删除过期键个数
 server.stat_expiredkeys++;
 // 向从节点和AOF文件传播过期信息
 propagateExpire(db,key,server.lazyfree_lazy_expire);
 // 发送事件通知
 notifyKeyspaceEvent(NOTIFY_EXPIRED,
 "expired",key,db->id);
 // 根据配置(默认是同步删除)判断是否采用惰性删除(这里的惰性删除是指采用后台线程处理删除操做,这样会减少卡顿)
 return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
 dbSyncDelete(db,key);
}复制代码

补充

我们通常说 Redis 是单线程的,其实 Redis 把处理网络收发和执行命令的操作都放到了主线程,但 Redis 还有其他后台线程在工作,这些后台线程一般从事 IO 较重的工作,比如刷盘等操作。

上面源码中根据是否配置 lazyfreelazyexpire(4.0版本引进) 来判断是否执行惰性删除,原理是先把过期对象进行逻辑删除,然后在后台进行真正的物理删除,这样就可以避免对象体积过大,造成阻塞,后面会在深入研究下 Redis 的 lazyfree 原理 源码位置 lazyfree.c/dbAsyncDelete 方法

定期删除

定期策略是每隔一段时间执行一次删除过期键的操作,并通过限制删除操作执行的时长和频率来减少删除操作对CPU 时间的影响,同时也减少了内存浪费

Redis 默认会每秒进行 10 次(redis.conf 中通过 hz 配置)过期扫描,扫描并不是遍历过期字典中的所有键,而是采用了如下方法

  1. 从过期字典中随机取出 20 个键
  2. 删除这 20 个键中过期的键
  3. 如果过期键的比例超过 25% ,重复步骤 1 和 2

为了保证扫描不会出现循环过度,导致线程卡死现象,还增加了扫描时间的上限,默认是 25 毫秒(即默认在慢模式下,如果是快模式,扫描上限是 1 毫秒)

对应源码 expire.c/activeExpireCycle 方法

void activeExpireCycle(int type) {
 ...
 do {
 ...
 if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)
 // 选过期键的数量,为 20
 num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;
 while (num--) {
 dictEntry *de;
 long long ttl;
 // 随机选 20 个过期键
 if ((de = dictGetRandomKey(db->expires)) == NULL) break;
 ...
 // 尝试删除过期键 
 if (activeExpireCycleTryExpire(db,de,now)) expired++;
 ...
 }
 ...
 // 只有过期键比例 < 25% 才跳出循环
 } while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);
 }
 ...
}复制代码

补充

因为 Redis 在扫描过期键时,一般会循环扫描多次,如果请求进来,且正好服务器正在进行过期键扫描,那么需要等待 25 毫秒,如果客户端设置的超时时间小于 25 毫秒,那就会导致链接因为超时而关闭,就会造成异常,这些现象还不能从慢查询日志中查询到,因为慢查询只记录逻辑处理过程,不包括等待时间。

所以我们在设置过期时间时,一定要避免同时大批量键过期的现象,所以如果有这种情况,最好给过期时间加个随机范围,缓解大量键同时过期,造成客户端等待超时的现象

Redis 过期键删除策略

Redis 服务器采用惰性删除和定期删除这两种策略配合来实现,这样可以平衡使用 CPU 时间和避免内存浪费

AOF、RDB 和复制功能对过期键的处理

RDB

生成 RDB 文件

在执行 save 命令或 bgsave 命令创建一个新的 RDB文件时,程序会对数据库中的键进行检查,已过期的键就不会被保存到新创建的 RDB文件中

载入 RDB 文件

主服务器:载入 RDB 文件时,会对键进行检查,过期的键会被忽略

从服务器:载入 RDB文件时,所有键都会载入。但是会在主从同步的时候,清空从服务器的数据库,所以过期的键载入也不会造成啥影响

AOF

AOF 文件写入

当过期键被惰性删除或定期删除后,程序会向 AOF 文件追加一条 del 命令,来显示的记录该键已经被删除

AOF 重写

重启过程会对键进行检查,如果过期就不会被保存到重写后的 AOF 文件中

复制

从服务器的过期键删除动作由主服务器控制

主服务器在删除一个过期键后,会显示地向所有从服务器发送一个 del 命令,告知从服务器删除这个过期键

从服务器收到在执行客户端发送的读命令时,即使碰到过期键也不会将其删除,只有在收到主服务器的 del 命令后,才会删除,这样就能保证主从服务器的数据一致性

疑问点?

  1. 如果主从服务器链接断开怎么办?
  2. 如果发生网络抖动,主服务器发送的 del 命令没有传递到从服务器怎么办?

其实上面两个问题 Redis 开发者已经考虑到了,只是主从复制涉及到的知识点还挺多,下面我就简单的说下解决的思路,后面会再分享一篇主从复制的文件

首先看疑问点1-如果主从服务器链接断开怎么办?

Redis 采用 PSYNC 命令来执行复制时的同步操作,当从服务器在断开后重新连接主服务器时,主服务器会把从服务器断线期间执行的写命令发送给从服务器,然后从服务器接收并执行这些写命令,这样主从服务器就会达到一致性,那主服务器如何判断从服务器断开链接的过程需要哪些命令?主服务器会维护一个固定长度的先进先出的队列,即复制积压缓冲区,缓冲区中保存着主服务器的写命令和命令对应的偏移量,在主服务器给从服务器传播命令时,同时也会往复制积压缓冲区中写命令。从服务器在向主服务器发送 PSYNC 命令时,同时会带上它的最新写命令的偏移量,这样主服务器通过对比偏移量,就可以知道从服务器从哪里断开的了

然后,我们再来看疑问点2-如果发生网络抖动,主服务器发送的 del 命令没有传递到从服务器怎么办?

其实主从服务器之间会有心跳检测机制,主从服务器通过发送和接收 REPLCONF ACK 命令来检查两者之间的网络连接是否正常。当从服务器向主服务器发送 REPLCONF ACK 命令时,主服务器会对比自己的偏移量和从服务器发过来的偏移量,如果从服务器的偏移量小于自己的偏移量,主服务器会从复制积压缓冲区中找到从服务器缺少的数据,并将数据发送给从服务器,这样就达到了数据一致性

小结

本文主要分析了 Redis 的过期策略是采用惰性删除和定期删除两种策略配合完成,然后简单看了两种策略的源码和是怎么实现的。最后介绍了 Redis 在进行 RDB 、 AOF 和主从复制操作时,如何对过期键进行处理,特别介绍了主从复制在发生主从链接断开和网络抖动命令丢失是如何处理的,希望大家看完能有收获



Tags:Redis 过期策略   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
在日常开发中,我们使用 Redis 存储 key 时通常会设置一个过期时间,但是 Redis 是怎么删除过期的 key,而且 Redis 是单线程的,删除 key 会不会造成阻塞。要搞清楚这些,就要了解 R...【详细内容】
2020-07-18  Tags: Redis 过期策略  点击:(69)  评论:(0)  加入收藏
为了减少占用内存空间,通常会对放到 Redis 中的键通过 expire 设置一个过期时间,那 Redis 是怎么实现对过期键删除的呢?...【详细内容】
2019-10-16  Tags: Redis 过期策略  点击:(135)  评论:(0)  加入收藏
▌简易百科推荐
来源: my.oschina.net/xiaomu0082/blog/2990388首先说下问题现象:内网sandbox环境API持续1周出现应用卡死,所有api无响应现象刚开始当测试抱怨环境响应慢的时候 ,我们重启一下应...【详细内容】
2021-12-08  Java识堂    Tags:Redis   点击:(18)  评论:(0)  加入收藏
我不知道为什么你会选择对特定数量的“错误”(或警告)如此具体。听起来您正在寻找将要发布到 Yahoo! 的某些文章的内容。 Insider (N Foos to Blah for the BlahBlah)。那说:...【详细内容】
2021-12-07  富集云科技有限公司    Tags:Redis   点击:(14)  评论:(0)  加入收藏
目录 一、背景 二、步骤 0.理论支持 1、获取数据 2、结果 3、分析数据并评估大小 三、关于repl-backlog-size 一、背景 repl-backlog-size控制这个环形缓冲区. ​ 主从断...【详细内容】
2021-11-05  弈秋的美好生活    Tags:redis   点击:(41)  评论:(0)  加入收藏
Redis 性能测试是通过同时执行多个命令实现的。1,Redis-benchmarkRedis性能命令:redis性能命令格式: redis-benchmark [option] [option value] redis 性能测试工具可选参数如...【详细内容】
2021-11-02  川石信息    Tags:Redis   点击:(41)  评论:(0)  加入收藏
1 概述数据结构和内部编码 无传统关系型数据库的 Table 模型schema 所对应的db仅以编号区分。同一 db 内,key 作为顶层模型,它的值是扁平化的。即 db 就是key的命名空间。 key...【详细内容】
2021-11-01  JavaEdge    Tags:Redis   点击:(28)  评论:(0)  加入收藏
普通java中使用引用Java redis 驱动,即可连接:import redis.clients.jedis.Jedis; public class RedisTestJava { public static void main(String[] args) { //连...【详细内容】
2021-10-13  faesuite    Tags:Redis   点击:(34)  评论:(0)  加入收藏
Redis常用的数据结构有 string list set zset hashstringstring 是 Redis 的基本的数据类型,一个 key 对应一个 value。string 类型是二进制安全的,Redis的string可以包含任...【详细内容】
2021-10-12  语霖    Tags:Redis   点击:(36)  评论:(0)  加入收藏
列表类型可以存储一组按插入顺序排序的字符串,它非常灵活,支持在两端插入、弹出数据,可以充当栈和队列的角色。> LPUSH fruit apple(integer) 1> RPUSH fruit banana(integer)...【详细内容】
2021-09-17  深夜敲代码    Tags:Redis   点击:(54)  评论:(0)  加入收藏
Redis持久化意义 是做灾难恢复,数据恢复,也可以归类到高可用的一个环节里面去,比如你的redis整个挂了,然后redis就不可用了,你要做的事情是让redis变得可用,尽快变得可用 大量的请...【详细内容】
2021-08-12  小李说IT    Tags:Redis   点击:(77)  评论:(0)  加入收藏
当查询Redis中没有的数据时,该查询会下沉到数据库层,同时数据库层也没有该数据,当这种情况大量出现或被恶意攻击时,接口的访问全部透过Redis访问数据库,而数据库中也没有这些数据...【详细内容】
2021-07-30  随便t    Tags:缓存穿透   点击:(91)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条