Redis设计与实现(六)数据库结构、过期键删除策略

一、数据库结构

redis中所有的数据库都存在redisServer结构中:

struct redisServer{
    //数据库数组,保存所有数据库
    redisDb *db;
    //数据库数量
    int dbnum;
}

每个数据库为一个redisDb结构,保存在redisServer中,dbnum为该服务器的初始时的数据库数量,可在配置文件中的database选项配置,默认16。

1.1 数据库的切换

每个连接的redis客户端都会使用一个目标数据库,该库默认为0号数据库,可通过SELECT 编号命令切换数据库。

redisClient结构的db属性记录了当前使用哪个数据库:

typedef struct redisClient{
    //客户端当前正在使用的数据库
    redisDb *db;
}redisClient;

该db指针指向redisServer中数据库数组中的某个元素,切换时只要将指针指向的数据库元素切换下就可以了。

在这里插入图片描述

1.2 redisDb

typedef struct redisDb{
    dict *dict;
    ......
}

redisDb,即我们的数据的数据包结构,内部使用到了一个字典,前面有讲过可以到前面看看。

说明数据库内部均以键值对的方式保存数据。

字典中的key就是数据库的键,每个键都是字符串对象;

字典中的值,就可以是前面讲到的几个对象:字符串对象、列表对象、哈希表对象、集合和有序集合对象的任意一个。

对数据库中的键值对的增加修改查询和删除等都对应着对应数据结构的增删改查操作。

1.3 读写键的维护

  • 读取一个键后,会根据键是否存在,更新服务器的键空间命中次数和未命中次数。

    可在info stats命令中的keyspace_hits和keyspace_misses属性查看:

    127.0.0.1:6379> info stats
    # Stats
    total_connections_received:1255
    total_commands_processed:2992
    instantaneous_ops_per_sec:0
    total_net_input_bytes:282106
    total_net_output_bytes:351144
    instantaneous_input_kbps:0.00
    instantaneous_output_kbps:0.00
    rejected_connections:0
    sync_full:2
    sync_partial_ok:0
    sync_partial_err:0
    expired_keys:0
    expired_stale_perc:0.00
    expired_time_cap_reached_count:0
    evicted_keys:0
    keyspace_hits:3 # 命中
    keyspace_misses:19 # 未命中
    pubsub_channels:0
    pubsub_patterns:0
    latest_fork_usec:300
    migrate_cached_sockets:0
    slave_expires_tracked_keys:0
    active_defrag_hits:0
    active_defrag_misses:0
    active_defrag_key_hits:0
    active_defrag_key_misses:0
    
    
  • 访问一个键后,会更新redisObject结构中的lru属性,表示访问该键的最后时间,当服务器内存不够用时,会通过该属性计算空闲时间(当前时间-lru的时间),空闲较长的键会被回收。

  • 如果读取某键已经过期,则会先删除该键,再执行后面的操作

  • 如果使用WATCH命令监控某键,则该键如果被修改,服务器会将该键标记为脏,事务程序就会注意到该键被修改,从而事务中该键的更新就会失败。

  • 服务器每修改一个键,都会对脏键计数器+1,该计数器会触发持久化以及复制操作(后期会讲)

  • 如果开启了数据库通知通能,则对键修改后,服务器会按配置发送响应数据库通知。

二、键的生存/过期时间原理

  • expire <key> <ttl> 设置key的生存时间 。 单位 :s
  • pexpire <key> <ttl> 同上, 单位:ms
  • expireat <key> <timestamp> 设置key的过期时间为指定的时间戳,单位s
  • pexpireat <key> <timestamp> 同上,单位:ms

四个命令底层最终都会转化调用pexpireat命令。

2.1过期时间的保存

键的过期时间,也是以字典的结构保存在redisDb结构中:

typedef struct redisDb{
    dict *expires;
}

其中,字典的key为设置过期时间的key,值是该key的过期时间。

这里过期时间的key,指向了对应键空间的key,因此不会浪费任何空间。

因此PERSIST移除过期时间,就是在expires字典中找到目标key,然后删除;

TTL计算剩余时间,就是找到目标key,然后获取对应的过期时间的值,减去当前时间就可以得到。

2.2 过期时间的判定

  1. 检查给定键是否存在于过期字典,如果存在,则取得其过期时间
  2. 检查当前UNIX时间戳是否大于过期时间,如果大于,则键已经过期,否则,键未过期

三、过期键删除策略

3.1定时删除【主动】

即,设置过期键的同时,创建一个定时器,让定时器在键的过期时间来临时立即执行删除操作。

优点

对内存友好,能够在键过期的同时,立即删除回收内存。

缺点

对CPU不友好。

如果过期键非常多,则会耗费大量的CPU执行删除键的命令,但如果此时有许多客户端命令请求,就不能即时执行客户端命令,降低吞吐量和响应时间。

且创建定时器需要用到redis的时间事件(由无序链表实现),所以查找事件的复杂度为O(N),也不适合处理大量的时间事件。

3.2惰性删除【被动】

过期也不管,但每次获取键,都要判断其是否过期,如果过期就删除,否则就直接使用。

优点

对CPU友好,只有在取到过期键,且键已经过期时,才会当当前这一个键进行删除,不会再其他时候删除过期键,也不会在删除无关的过期键上花费CPU时间。

缺点

容易造成内存泄露。

如果有很多的过期键,而这些键一直都没有被访问,则这些过期键就永远不会被删除,占用大量内存,造成内存泄露。

3.3定期删除【主动】

每隔一段时间对数据库进行一次检查,删除过期键。

该策略是对上面两个的折中。

  • 在删除键时,会通过限制删除操作执行的时长和频率减少对CPU时间的影响。
  • 定期的删除,可以避免内存泄露。

定期删除的难点是:

  1. 如果删除操作太频繁,或执行时间太长,定期删除策略会退化成定时删除
  2. 如果删除操作太少,或执行时间太短,定期删除策略会和惰性删除一样出现内存浪费。

四、Redis中的过期键删除策略

redis使用的是惰性删除和定期删除两个策略,可以在合理使用CPU时间和避免内存空间间取得平衡。

4.1 惰性删除的实现

  • 所有读写命令执行前都会调用expireIfNeeded函数

    1. 如果键过期,则删除键,则执行读写命令
    2. 如果键没过期,则直接执行读写命令
  • 每个命令的实现函数都必须处理键是否存在的逻辑

    get,命令为例:

    1. 如果键存在,则判断键是否过期,过期就删除过期键,返回空;没过期就直接返回对应的值;
    2. 如果键不存在,则直接返回空

4.2 定期删除策略的实现

定期删除策略由activeExpireCycle函数实现,每当redis周期性操作serverCron函数时,就会调用一次activeExpireCycle

该函数每次运行会从一定数量的数据库取出一定数量的随机键检查,并删除过期键。

函数中有一个全局变量current_db记录当前检查的进度,下次调用时会接着该进度继续检查;如当前检查第1个数据库时返回了,那么下次就从第2个数据库继续进行检查。

current_db扫描完全部的数据库,会重置为0,开始新一轮的检查。

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

5.1RDB

  1. 执行SAVEBGSAVE命令创建新的RDB文件时,过期的键不会保存到新的RDB文件中;
  2. 载入RDB文件时,如果以主服务器启动,则RDB中过期的键不会加载到数据库中
  3. 载入RDB文件时,如果以从服务器启动,则RDB中所有的键都会加载到数据库中,但在主从同步时会删除从服务器的所有键,所以不必担心过期键的影响

5.2 AOF

  • AOF写入

    1. 如果键尚未进行惰性删除或定时删除,则AOF文件不会产生任何印象;

    2. 如果键被惰性删除或定时删除,则删除后,程序会向AOF文件追加一条DEL命令,显式记录该键已经被删除。

  • AOF重写

    同RDB类似,重写时,过期的键不会被写入。

5.3 复制

复制模式下,过期键删除动作由主服务器控制。

  • 主服务器删除一个过期键后,会显示地给所有从服务器发送一个DEL命令通知,从服务器收到命令后,会在从服务器中删除过期键;
  • 从服务器如果遇到已经过期的键,不会进行删除,而是当做未过期一样继续执行相关命令。

因为从服务的删除过期键完全听取于主服务器,所以可以保证主从服务器数据的一致性。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值