Redis的设计与实现(2):如何处理过期键

三、Redis的过期策略

image.png
redis客户端和用户端结构关系
每个db数据库的结构是:

typedef struct redisdb{

    .....
        
      //键空间  
    dict *dict;
        
     //保存键的过期时间
    dict *expires;
        
    .....
}redisdb

info stats 命令的keyspace_hits和keyspace_misses表示查询时命中缓存的次数和没有命中的次数
object idletime 查看key的闲置时间

3.1键的过期时间

expire 设置键的生存时间

pexpire

expireAt

pexpireAt 这两个命令的时间类型是unix时间戳,如:PEXPIREAT book 1388556000000(2014年1月1日零时)指定某个具体的时间

上面这4个命令底层都是转化为pexpireAt来实现的

setex 命令用来设置string类型键的生存周期,原理与上面这个命令一样

ttl 获取带有生存时间key的剩余生存时间,time to live,通过用当前时间与过期词典中的过期时间差来实现

redisdb结构中有一个expires字典保存着所有键的过期时间
image.png
移除过期时间persist,原理就是在过期词典中解除key与过期时间的关联。

3.2键的删除策略

针对过期键有三种删除策略:
1.定时删除:在设置见得过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时立即执行对键的删除操作。

优缺点:

  • 对内存最友好,对CPU时间最不友好。
  • 当过期键过多时,定时删除会占用一部分的CPU时间,影响服务器正常操作的影响时间和吞吐量。
  • 创建定时器需要redis中的时间事件,而当前的时间事件实现方式是无序链表,查找一个时间事件的时间复杂度是O(N),因此创建大量定时器执行删除也不现实。

2.惰性删除:每次从键空间获取键时,先检查键值是否过期,如果过期的话,就删除该键。没有过期,就返回该键。

优缺点:

  • 对CPU的时间最友好的,对内存最不友好。
  • 如果有非常多的过期键,而这些过期键又恰好不会被访问到的话,那么这些过期键就移植占用内存,就会造成内存泄漏。比如日志信息。

3.定期删除:每隔一段时间,程序对数据库进行一次检查,删除里面的过期键。每次扫描的时间为250ms/server.hz,即占用cpu性能的25%用来循环扫描),发现过期数据则删除,删除的key大于扫描数25%则循环扫描,小于25%则扫描下一个区间。(随机抽查,重点检查)

优缺点:

  • 限制删除的时长和频率减少对CPU的影响。
  • 定期删除减少过期键造成的内存泄漏。
  • 执行太频繁或者时间太长,会退化为定时删除,过多的占用CPU时间。
  • 执行时间太少会造成内存的浪费。
  • 需要合理的设置删除时长和频率。

第1,3种是主动删除策略,第2种是被动删除策略。

Redis的过期键删除策略使用了惰性删除和定期删除两种策略,在避免内存浪费和合理使用CPU时间之间取得平衡。

惰性删除策略的实现方式:
redis通过expireIfNeeded函数实现惰性删除,所有读写数据库的命令在执行前都会调用expireIfNeeded函数对键进行检查:

  • 如果键已过期,那么删除键
  • 如果键未过期,那么不做任何操作。

expireIfNeeded就像一个过滤器在命令真正执行前过滤掉过期的键,以get命令为例执行过程如下:
image.png
定期删除策略的实现:
redis通过activeExpireCycle函数实现定期删除。每当redis的周期性操作redis.c/serverCron函数执行时,activeExpireCycle就会被执行,在规定时间内分多次遍历服务器各个数据库,从数据库的expires字典中随机抽查一部分键的过期时间,并删除其中的过期键。
整个过程用伪代码表示:
image.png

总结一下:

  • 每次运行,都从一定数量的数据库中取出一定数量的随机键检查并删除过期键。
  • 全局遍历用current_db记录检查进度。
  • 当所有数据库都被检查一遍后,重置current_db,进行新一轮检查。
内存不足时淘汰策略

在执行每一个命令前,会调用freeMemoryIfNeeded()检测内存是否充足。如果内存不满足新 加入数据的最低存储要求,redis要临时删除一些数据为当前指令清理存储空间。清理数据的策略称为逐出算法。

内存不足时删除数据的策略

针对设置了过期时间的:

  • volatile-LRU:最近最少使用,就是最长时间没使用的删除
  • volatile-LFU:最近使用次数最少,就是删除使用频率最低的
  • volatile-TTL:删除剩余存活时间最少的
  • volatile-Random:随机删除

对所有key:

  • allkeys-LRU
  • allkeys-LFU
  • allkeys-Random
  • no-eviction:不删除,Redis4.0默认

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

1.生成RDB文件

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

2.载入RDB

  • 如果是主服务器,对键进行检查,未过期的键载入到数据库。
  • 如果是从服务器,文件中保存的所有键都会被载入,但是因为主从同步的时候,从服务器的数据库会被清空,所以过期键对载入RDB的从服务器也无影响。

3.AOF文件写入

  • 当某个键已经过期,但还没有被定期删除或者惰性删除,AOF文件不会因为过期键产生任何影响。
  • 因为当一个过期键被删除后,程序会向AOF文件追加一条DEL命令。
  • 以GET message为例,服务器的执行流程是:
    - 删除message
    - 追加del命令到AOF文件
    - 返回空

4.AOF文件重写

  • 在执行AOF重写的过程中,程序会对数据库中的键进行检查,已过期的键不会被保存到重写后的AOF文件里。

5.复制

  • 当服务器运行在复制模式下,从服务器的过期键策略由主服务器控制:
    • 主服务器删除一个过期键后,会显式地向所有从服务器发送一个DEL命令
    • 从服务器在执行客服端发送的读命令时,即使碰到过期键也不会删除,而是当做正常的键返回结果。
    • 从服务器只有在接收到主服务器发来的DEL命令才会删除过期键。这种策略会导致主从服务器数据会有短暂的不一致性,比如有一个过期键,服务端在从服务器获取时能正常获取,在主服务器获取时返回空,存在不一致,但此时再去从服务器获取返回空。

通知
redis 2.8增加的功能。
客服端可以订阅给定的频道和模式,获取键的变化和命令的执行情况。
比如客户端可以订阅针对某个键的所有执行命令,这种通知称之为键空间通知。
还可以订阅某个数据库执行的某个操作,比如订阅数据库执行的DEL命令,这种通知称之为键事件通知。
服务器配置的notify-keyspace-events决定的服务器发送通知的类型:
image.png
发送通知功能由notifyKeyspaceEvent函数实现。

注:内容是从语雀上的学习笔记迁移过来的,主要参考自《Redis的设计与实现》有些参考来源已经无法追溯,侵权私删。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值