Redis学习篇之数据库实现和过期策略相关

参考:Redis设计与实现
事先声明:本文为对该书的总结。并参考 https://segmentfault.com/a/1190000016951866 作者:java3y

数据库相关

Redis服务器的服务器状态由redis.h/redisServer结构来表示,其中的redisDb数组代表当前服务器中的所有数据库

struct redisServer{  

    //redisDb数组,表示服务器中所有的数据库
    redisDb *db;  
    
    //服务器中数据库的数量
    int dbnum;  
  
}; 

其中的dbnum代表当前服务器的数据库数量,这个数量默认为16。我们可以在redis.conf配置文件中看到它,并且可以进行修改
在这里插入图片描述
当redis客户端连接redis服务器的时候默认选择的是0号数据库,客户端可以通过select命令进行切换
在这里插入图片描述
Redis的客户端用redisClient结构来表示,其中的db指针指向了redisServer中db数组中的一个元素,代表当前客户端正在使用的数据库。

typedef struct redisClient{  
   
    //客户端当前所选数据库
    redisDb *db;  
     
}redisClient;

如图所示:
在这里插入图片描述

select命令的实现原理就是将redisClient中的db指针指向了redisServer中db数组的不同位置。

我们知道redis是一种key-value数据库,所有数据都由一个键和一个value组成,键总是一个字符串对象,value可以是不多的对象类型,字符串、列表、哈希表、集合、有序集合。这些对象类型根据存储的内容不同对应着底层不同的数据结构实现。

在数据库结构redis.h/redisDb中的dict属性中保存着该数据库所有的键值对,expires保存着所有键的过期时间。
他们两个都是字典类型(相当于java中的map 里面存着键值对

typedef struct redisDb { 
    // ....
    int id;         // 数据库ID标识
    dict *dict;     // 键空间,存放着所有的键值对              
    dict *expires;  // 过期哈希表,保存着键的过期时间                          
    long long avg_ttl;  // 数据库内所有键的平均TTL(生存时间)     
    // ......
} redisDb;

在这里插入图片描述

可以从图中看到dist字典里存着一些键 这些都是字符串对象类型,他们分别指向不同类型的值对象。
比如

  • alphabat对应的值为列表类型对象
  • book对应的值为哈希类型对象
  • message对应的值也为一个字符串类型对象

他们对应的命令为

rpush alphabet "a" "b" "c"
hmset book name ... author... publisher...
set message "hello world"

当我们对相应的键进行 增删改查时 就是对字典中的键空间进行操作
提示: 我们可以用TYPE命令返回一个键对应值的对象类型
在这里插入图片描述

键的过期策略相关

客户端可以用通过expire命令或者pexpire给键设置生存时间
在这里插入图片描述
可以看到5秒生存时间后 wmh这个键过期了

expire命令默认的时间单位为秒
pexpire命令默认的时间单位为毫秒

客户端还可以用expireat 和pexpireat 这两个命令用来给键设置过期时间(UNIX时间戳)
如果当前时间到了指定的时间戳 那么这个键就会过期

在这里插入图片描述
expireat命令默认的时间戳单位为秒
pexpireat命令默认的时间戳单位为毫秒

虽然我们有4个命令来决定键啥时候过期
但其中的expire、pexpire、expireat 都是由pexpireat命令来实现的。

比如 expire命令会先被转换为pexpire (将秒转换为毫秒)
接着将pexpire转换为pexpireat(将生存时间加上当前时间戳)

ttl 命令将返回该键剩余生存时间以秒为单位, 对应的 pttl 命令 单位为毫秒

redisDb数据库结构中的expires字典中保存着当前数据库中的键和它的过期时间戳
如果当前键在expires字典中对应的时间戳值小于当前时间戳说明键已过期

过期键删除策略

当键过期redis该什么时候将他删除呢?

通常有3种过期删除策略

  • 定时删除:设置键的同时,开启一个定时器监视键的过期时间,到期时将他删除
    优势:对内存友好,可以保证键过期了就会被删除,不会占用太多内存
    劣势:对cpu不友好,在键很多的情况下,定时器会占用大量的CPU时间,导致不能专注于接收请求,对服 务器的吞吐量和响应时间有很大影响。另外redis中创建定时器需用到时间事件(无序链表),查找一个事件的时间复杂度为O(n),并不能高效大量处理时间事件。时间事件我现在还没学。。

  • 惰性删除:只有当获取键的时候才会检查键是否过期然后将它删除,否则放任不管
    优势:对CPU相当友好,根本不会浪费CPU时间来对无关过期键的检测,只有在获取的时候才会判断一下有没有过期然后将它删除。
    劣势:对内存相当不友好,会保留很多过期键在内存中,因为不可能每次都访问到过期键,太多过期键占用内存会导致内存泄漏。垃圾数据占用内存。

  • 定期删除:每隔一段时间对数据库进行一次检查,删除过期的键,删除的时间和多少由对应的算法决定
    本策略是上面两种策略的中和但也有难点
    优势:每隔一段时间执行对过期键的删除,并限制执行时长和频率来减少对CPU的影响
    定期删除键,对内存也不会有太大的浪费
    劣势:对执行时长和间隔清理的时间难以把控,执行太久或频率太高,会占用大量CPU的时间
    执行时间太短或频率太低,又会浪费内存

Redis采用的过期策略

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

  • 惰性删除策略的实现

    db.c/expireIfNeeded函数实现
    Redis在执行命令之前都会调用expireIfNeeded函数检查该键。
    如果过期,expireIfNeeded删除该键,没过期则啥都不做。
    这个函数保证了命令不会接触到过期键,相当于一个过滤器。
    当命令发现该键对应的值为null时,可能刚被expireIfNeeded删除,也可能真的不存在
    它只需要返回一个空回复就好。

  • 定期删除策略的实现

    redis.c/activeExpireCycle函数实现
    该函数执行时会在规定的时间内,分多次遍历服务器的各个数据库,从数据库中的expires字典中随机检查
    一部分键的过期时间并删除过期键

虽然采用了这两种策略但还是不可避免的会出现过期而没有及时删除的键。还是有一定情况下内存会不足,并且我们需要保证redis有限的内存中保存的都是热点数据,这时会采用 redis 内存淘汰机制。

我们可以设置内存最大使用量,当内存使用量超出时,会施行数据淘汰策略
Redis的内存淘汰机制有以下几种:
在这里插入图片描述

Redis采用allkeys-lru淘汰策略,将最近最少使用的数据淘汰

AOF、RDB对过期键的处理

RDB持久化对过期键的策略:

  • 执行SAVE或者BGSAVE命令创建出的RDB文件,程序会对数据库中的过期键检查,已过期的键不会保存在RDB文件中。
  • 载入RDB文件时,程序同样会对RDB文件中的键进行检查,过期的键会被忽略。从服务器无论过期不过期都会载入到数据库

AOF持久化对过期键的策略:

  • 如果数据库的键已过期,但还没被惰性/定期删除,AOF文件不会因为这个过期键产生任何影响(也就说会保留),当过期的键被删除了以后,会追加一条DEL命令来显示记录该键被删除了
  • 重写AOF文件时,程序会对RDB文件中的键进行检查,过期的键会被忽略。

复制模式:

  • 主服务器在删除过期建后会显示的向所有从服务器发送DEL命令,告知从服务删除
  • 从服务器就算发现建已过期也不会删除,还是会继续将过期值返回给客户端,只会等待主服务器发来的DEL命令才会i删除

由主服务器来控制从服务器统一删除键保证数据一致性。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值