美文网首页
10.数据库及过期策略

10.数据库及过期策略

作者: xMustang | 来源:发表于2020-02-23 15:27 被阅读0次

    数据库及过期策略

    1. 服务器中的数据库

    Redis服务器将所有数据库都保存在服务器状态redis.h/redisServer结构的db数组中,db数组的每个项都是一个redis.h/redisDb结构,每个redisDb结构代表一个数据库:

    struct redisServer{
    
        // ...
    
        // 一个数组,保存服务器中的所有数据库
        redisDb *db;
    
        // ...
    }
    

    在初始化服务器时,程序会根据服务器状态的dbnum属性来决定应该创建多少个数据库:

    struct redisServer{
    
        // ...
    
        // 服务器的数据库数量
        int dbnum;
    
        // ...
    };
    

    dbnum属性的值由服务器配置的database选项决定,默认情况下,该选项的值为16,Redis默认会创建16个数据库。

    Redis服务器数据库示例

    2. 切换数据库

    Redis客户端的默认数据库为0号数据库,客户端可以通过执行SELECT命令切换目标数据库。

    SELECT 2   // 切换到2号数据库
    

    客户端状态redisClient结构的db属性记录了客户端当前的目标数据库,这个属性是一个指向redisDb结构的指针。

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

    redisClient.db指针指向redisServer.db数组的其中一个元素,而被指向的元素就是客户端的目标数据库。

    如下图,反应了客户端与服务器端数据库的关系。

    客户端的目标数据库

    3. 数据库键空间

    服务器中的每个数据库都由一个redis.h/redisDb表示。

    typedef struct redisDb{
        // ...
    
        // 数据库键空间,保存数据库中所有的键值对
        dict *dict;
    
        // ...
    } redisDb
    

    3.1 添加新键

    添加一个新键值对到数据库,实际上就是将一个新键值对添加到键空间字典里面,其中键为字符串对象,值为任意一种类型的Redis对象。

    3.2 删除键

    删除数据库中的一个键,实际上就是在键空间里面删除键所对应的键值对对象。

    3.3 更新键

    对一个数据库键进行更新,实际上就是对键空间里面键所对应的值对象进行更新。

    3.4 对键取值

    对一个数据库键进行取值,实际上就是在键空间中取出键所对应的值对象。

    3.5 其他键空间操作

    还有很多针对数据库本身的Redis命令,也是通过对键空间进行处理来完成的,如:

    1. FLUSHDB,通过删除键空间中所有键值对来实现
    2. RANDOMKEY,通过在键空间随机返回一个键来实现
    3. DBSIZE,通过返回键空间中包含的键值对的数量来实现
    4. EXISTS、RENAME、KEYS等命令也是通过对键空间操作实现的

    3.6 读写键空间时的维护操作

    当使用Redis命令对数据库进行读写时,服务器不仅会对键空间执行指定的读写操作,还会执行一些额外的维护操作,其中包括:

    1. 在读取一个键之后(读操作和写操作都要对键进行读取),服务器会根据键是否存在来更新服务器的键空间命中(hit)次数或键空间不命中(miss)次数,这两个值可以在INFO stats命令的keyspace_hits属性和keyspace_misses属性查看。
    2. 在读取一个键之后,服务器会更新键的LRU(最后一次使用)时间,这个值可以用于计算键的闲置时间。
    3. 如果服务器在读取一个键时发现这个键已经过期,那么服务器会先删除这个过期键,再执行余下的其他操作。
    4. 如果有客户端使用WATCH命令监视了某个键,那么服务器在对被监视的键进行修改后,会将这个键标记为脏(dirty),从而让事务程序注意到这个键已经被修改过。
    5. 服务器每次修改一个键之后,都会对脏(dirty)键计数器的值增1,这个计数器会触发服务器的持久化及复制操作。
    6. 如果服务器开启了数据库通知功能,那么在对键进行修改之后,服务器将按配置发送相应的数据库通知。

    4. 设置键的生存时间或过期时间

    1. EXPIRE、PEXPIRE:以秒、毫秒精度为键设置生存时间(Time To Live,TTL)。设置值为几秒、几毫秒。
    2. EXPIREAT、PEXPIREAT:以秒、毫秒精度为键设置生存时间(Time To Live,TTL)。设置值为Unix时间戳。
    3. SETEX:可以在设置一个字符串键的同时为键设置过期时间。
    4. TTL、PTTL:查看键的剩余生存时间。

    4.1 保存过期时间

    typedef struct redisDb{
    
        // ...
    
        // 过期字典,保存键的过期时间
        dict *expires;
    } redisDb;
    

    redisDb中的expires字典保存了数据库中所有键的过期时间,称这个字典为过期字典:

    1. 过期字典的键是一个指针,这个指针指向键空间中的某个键对象。
    2. 过期字典的值是一个long long类型的整数,一个毫秒精度的Unix时间戳。

    下面是一个带有过期字典的数据库示例。

    带有过期字典的数据库示例

    上图中键空间、过期字典中重复出现两次alphabat、book键对象。在实际中,键空间的键和过期字典的键都指向同一个键对象,不会出现任何重复对象,不会浪费任何空间。

    4.2 移除过期时间

    PERSIST:移除一个键的过期时间。

    PERSIST在过期字典中查找给定的键,并解除键和值(过期时间)在过期字典中的关联。

    4.3 计算并返回剩余生存时间

    TTL、PTTL两个命令都是通过计算键的过期时间和当前时间之间的差来实现的。

    4.4 过期键的判定

    Redis检查键是否过期的方法:先取得键的过期时间,检查当前UNIX时间戳是否大于键的过期时间,如果是,那么键已经过期;否则,键未过期。

    另一种检查键过期的方法是:使用TTL、PTTL命令,判断返回的值是否大于等于0。但是Redis并未使用此策略,因为直接访问字典比执行一个命令稍微快一些。

    4.5 过期键删除策略

    过期键有3种不同的删除策略:

    1. 定时删除:在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除策略。(Redis实际未使用该策略)
    2. 惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期,就删除该键,如果没有过期,就返回该键。
    3. 定期删除:每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。

    上面第1种、第3种为主动删除策略,第2种为被动删除策略。

    4.5.1 定时删除(Redis未使用此策略)

    1. 定时删除策略对内存是友好的:可以保证过期键会尽快被删除,并释放过期键所占用的内存。
    2. 定时删除策略对CPU时间是不友好的:在过期键比较多的情况下,删除过期键可能会占用相当一部分CPU时间。

    创建一个定时器,需要用到Redis服务器中的时间事件,而Redis当前时间事件的实现方式是无序链表,查找一个事件的时间复杂度为O(N),并不能高效地处理大量时间事件。

    因此,要让服务器创建大量的定时器,从而实现定时删除策略,在现阶段来说并不现实。

    4.5.2 惰性删除

    1. 惰性删除策略对CPU时间是友好的:程序只会在取出键时才对键进行过期检查。
    2. 惰性删除策略对内存是不友好的:如果一个键已经过期,只要这个过期键不被删除,所占用的内存就不会释放。

    4.5.3 定期删除

    定期删除策略是前两种策略的一种整合和折中:

    1. 定期删除策略每隔一段时间执行一次删除过期键操作,并通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响。
    2. 定期删除策略也能有效地减少因为过期键带来的内存浪费。

    定期删除策略的难点是确定删除操作执行的时长和频率。

    5. Redis的过期键删除策略

    Redis服务器实际使用的是惰性删除、定期删除两种策略。

    5.1 惰性删除策略的实现

    过期键的惰性删除策略由db.c/expireIfNeeded函数实现。

    所有读写数据库的Redis命令在执行之前都会调用expireIfNeeded对输入键进行检查。

    5.2 定期删除策略的实现

    过期键的定期删除策略由redis.c/activeExpireCycle函数实现。

    每当Redis的服务器周期性操作redis.c/serverCron函数执行时,activeExpireCycle就会被调用,它在规定的时间内,分多次遍历服务器中的各个数据库,从数据库的expires字典中随机检查一部分键的过期时间,并删除其中的过期键。在遍历过程中,全局变量current_db会记录activeExpireCycle函数检查的进度。

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

    下面介绍RDB持久化功能、AOF持久化功能、复制功能是如何处理数据库中的过期键的。

    6.1 生成RDB文件

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

    6.2 载入RDB文件

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

    1. 如果服务器以"主服务器模式"运行,那么在载入RDB文件时,程序会对文件中保存的键进行检查,未过期的键会被载入数据库中,过期键会被忽略。
    2. 如果服务器以"从服务器模式"运行,那么在载入RDB文件时,文件中保存的所有键,无论是否过期,都会被载入数据库。不过,因为主从服务器在进行数据同步时,"从服务器"的数据库会被清空。

    6.3 AOF文件写入

    当服务器以AOF持久化模式运行时,如果数据库中的某个键已经过期,但它还没有被惰性删除或者定期删除,那么AOF文件不会因为这个过期键而产生任何影响。

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

    6.4 AOF重写

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

    6.5 复制

    当服务器运行在复制模式下,"从服务器"的过期键删除动作由"主服务器"控制:

    1. 主服务器在删除一个过期键之后,会显式向所有"从服务器"发送一个DEL命令,告知"从服务器"删除这个过期键。
    2. "从服务器"在执行客户端发送的读命令时,即使碰到过期键也不会将过期键删除,而是继续像处理未过期的键一样来处理过期键。
    3. "从服务器"只有在接到"主服务器"发来DEL命令后,才会删除过期键。

    这种由"主服务器"控制"从服务器"统一删除过期键,可以保证主从服务器数据的一致性。

    7. 数据库通知

    这个功能可以让客户端通过订阅给定的频道或者模式,来获知数据库中键的变化,以及数据库中命令的执行情况。

    关注"某个键执行了什么命令"的通知称为"键空间通知"。

    SUBSCRIBE _ _keyspace@0_ _:message
    
    获取0号数据库中针对message键执行的所有命令。
    

    关注"某个命令被什么键执行了"的通知称为"键事件通知"。

    SUBSCRIBE _ _keyevent@0_ _:del
    
    获取0号数据库中所有执行DEL命令的键。
    

    notify-keyspace-events决定了服务器发送通知的类型:

    1. 想让服务器发送所有类型的键空间通知和键事件通知,可将选项值设为AKE。
    2. 想让服务器发送所有类型的键空间通知,可将选项值设为AK。
    3. 想让服务器发送所有类型的键事件通知,可将选项值设为AE。
    4. 想让服务器只发送和字符串有关的键空间通知,可将选项值设为K$。
    5. 想让服务器只发送和列表键有关的键事件通知,可将选项值设为El。

    8.1 发送通知

    发送数据库通知的功能由notify.c/notifyKeyspaceEvent函数实现。

    void notifyKeyspaceEvent(int type, char *event, robj *key,int dbid);
    

    type参数是当前想要发送的通知的类型,程序会根据这个值来判断通知是否就是notifyKeyspaceEvent设定的通知类型,从而决定是否发送通知。

    events、key、dbid分别是事件的名称、产生事件的键、产生事件的数据库号码。

    相关文章

      网友评论

          本文标题:10.数据库及过期策略

          本文链接:https://www.haomeiwen.com/subject/foujqhtx.html