Redis:删除策略和逐出策略

一、数据库键空间

Redis是一个键值对数据库服务器,服务器的每个数据库都由一个redis.h/redisDb结构表示,其中redisDb结构的dict字典保存了数据库的所有键值对,我们将这个字典称为键空间。

键空间和用户所见的数据库时直接对应的:

  • 键空间的键就是数据库的键,每个键都是一个字符串对象
  • 键空间的值也就是数据库的值,每个值是任意一种Redis对象(五种数据类型)

redisDb结构的 expires 字典保存了数据库中所有键的过期时间,我们称为过期字典:

  • 过期字典的键是一个指针,这个指针指向键空间的某个键对象
  • 过期字典的值是一个long类型的整数,这个整数保存了键所指向的数据库键过期时间(一个毫秒精度的UNIX时间戳)

注意:在实际中,键空间的键和过期字典的键都指向同一个键对象,所以不会出现任何重复的对象,也不会浪费任何空间

1.1 过期键判定

计算并返回剩余生存时间:TTL和PTTL两个命令都是通过计算键的过期时间和当前时间之间的差来实现

  • 检查给定键是否存在于过期字典,如果存在,那么取得键的过期时间
  • 检查当前UNIX时间戳是否大于键的过期时间,如果是的话,那么键已过期

二、过期键删除策略

  • 定时删除:在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作
  • 惰性删除:放任键过期不管,但每次从键空间中获取键时,都检查取得的键是否过期, 如果过期的话,就删除该键;如果没有过期,就返回该键
  • 定期删除:每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键

服务器实际使用的是惰性删除和定期删除

2.1 惰性删除实现

过期键的惰性删除策略有 expireIfNeeded 函数实现,所有读写数据库的Redis命令在执行之前都会调用 expireIfNeeded 函数对输入键进行检查

  • 如果输入键已经过期,那么expireIfNeeded函数将输入键从数据库中删除
  • 如果输入键未过期,那么expireIfNeeded函数不做动作

2.2 定期删除策略实现

Redis服务器周期性操作 serverCron 函数执行时,activeExpireCycle函数就会被调用。activeExpireCycle 函数分多次遍历服务器中的各个数据库(一次可能遍历不完所有数据库),从数据库的过期字典中随机检查一部分键的过期时间,并删除其中的过期键。

# 默认每次检查的数据库数量
DEFAULT_DB_NUMBERS = 16
# 默认每个数据检查的键数量
DEFAULT_KEY_NUMBERS = 20
# 全局变量,记录检查进度
current_db = 0
 
 
def activeExpireCycle():
    # 初始化要检查的数据库数量
    # 如果服务器的数据库数量比DEFAULT_DB_NUMBERS小
    # 那么以服务器的数据库数量为准
    if server.dbnum < DEFAULT_DB_NUMBERS:
        db_numbers = server.dbnum
    else:
        db_numbers = DEFAULT_DB_NUMBERS
    # 遍历各个数据库
    for i in range(db_numbers):
        # 如果current_db的值等于服务器的数据库数量
        # 表示检查程序已经遍历了服务器的所有数据库一次
        # 将current_db重置为0,开始新一轮遍历
        if current_db == server.dbnum:
            current_db = 0
        # 获取当前要处理的数据库
        redisDb = server.db[current_db]
        # 将数据库索引增1,指向下一个要处理的数据库
        current_db += 1
        # 检查数据库键
        for j in range(DEFAULT_KEY_NUMBERS):
            # 如果数据库中没有过期键,那么跳过这个数据库
            if redisDb.expires.size() == 0: break
            # 随机获取一个带有过期时间的键
            key_with_ttl = redisDb.expires.get_random_key()
            # 检查键是否过期,如果过期就删除
            if is_expired(key_with_ttl):
                delete_key(key_with_ttl)
            # 已经达到本次删除操作时间上限,停止处理
            if reach_time_limit(): return
  • current_db用于记录activeExpireCycle函数检查的进度,下一次调用activeExpireCycle时接着上一次的处理进度进行处理
  • 随着activeExpireCycle函数不断执行,服务器中所有的数据库都会被检查一遍,这时将current_db变量置为0,然后开始新的一轮检查工作

三、AOF、RDB、复制 对过期键的处理

3.1 RDB文件

生成RDB文件

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

载入RDB文件

在启动Redis服务器时,如果服务器开启了RDB功能,那么服务器将对RDB文件进行载入

  • 如果服务器以主服务器模式运行,那么载入RDB文件时,程序会对文件中保存的键进行检查(生成RDB文件时有的键可能没过期,但Redis重新启动的时候,这些键到时间过期了),未过期的键会被载入到数据库中,而过期键会被忽略
  • 如果服务器以从服务器模式运行,那么载入RDB文件时,文件中保存的所有键不论是否过期,都会被载入到数据库。(主服务器进行数据同步时,从服务器的数据库就会被清空)

3.2 AOF文件

AOF文件写入

  • 如果数据库的某个键已经过期,但他还没被惰性删除或者定期删除,那么AOF不会因为过期键而更改
  • 当过期键被惰性删除或者定期删除之后,向AOF文件追加一条DEL命令,来显式记录该键已被删除

AOF重写

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

3.3 复制

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

  • 主服务器在删除一个过期键之后,会显式向所有从服务器发送DEL命令,通知从服务器删除这个过期键
  • 从服务器在直接客户端发送的读命令时,即使碰到过期键也不会将过期键删除,而是像处理未过期的键一样处理(例如客户端发送给从服务器 get 过期键的命令,从服务器还是会返回过期键的值给客户端)
  • 从服务器只有在收到主服务器发来的DEL命令,才会删除过期键

四、逐出算法

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

注意:逐出数据的过程不是100%能够清理出足够的可使用的内存空间,如果不成功则反复执行。当对所 有数据尝试完毕后,如果不能达到内存清理的要求,将出现错误信息。

# 最大空用内存,默认时全用
maxmemory 256mb
 
# 每次选取待删除数据的个数
maxmemory-samples 100
 
# 删除策略(逐出算法)
# 可选操作
# 检查易失数据(有过期时间的数据)
# volatile-lru: 挑选最近最少使用的数据淘汰
# volatile-lfu: 挑选最近使用次数最少的数据淘汰
# volatile-ttl: 按将要过期的数据淘汰
# volatile-random: 随机淘汰有过期时间的数据
# 检测全库数据(所有的key,不管有没有过期时间)
# allkeys-lru
# allkeys-lfu
# allkeys-random
# 放弃数据驱逐
# no-enviction
maxmemory-policy volatile-lru
 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值