深入了解Redis之Redis数据库的实现

Redis这个key-value的存储系统,由于其高性能,支持主从复制和丰富的数据结构等特性,目前已经广泛应用于我们的各个场景之中。所以了解整理其相关的一些内容,用以加深我们对其的了解,方便之后更好的使用它实现我们的各种功能。

数据结构与对象

Redis底层的实现,主要用到的数据结构有简单动态字符串(SDS)、双端链表、字典、压缩列表、整数集合等。但REdis没有直接用这些数据结构来实现数据库的设计。而是基于这些数据结构创建了一个对象系统。

使用这种对象系统的好处

  • 对不同的使用场景,可以为对象设置多种不同的数据结构实现,从而可以优化对象在不同场景下的使用效率
  • 基于对象方便实现基于引用计数技术的内存回收机制

键值对的具体组成:

  • 键 => 自字符串对象
    • 字符串对象
    • 列表对象
    • 哈希对象
    • 集合对象
    • 有序集合对象

数据库的实现

Redis服务器的所有数据库都保存在”redisSeriver”结构中的“db” 的数组中。而db的结构则主要由“dict”和“expires”另个字典构成,dict字典负责保存键值对,而expires字典负责保存键的过期时间。

redisSeriver结构

struct redisSeriver{
  //...

  redisDb *db;

  //...
};

redisDb结构

typeed struct redisDb{
  //...

  //数据库键值字典,保存数据库中所有键值对(又称之为,键空间)
  dict *dict;

  //过期字典,保存键的过期时间
  dict *expires;

} redisDb;

服务器保存键值对的方法

键值字典和用户所见的数据库是直接对应的

  • 该字典里的键就是我们数据库的键(每个键都是一个字符串对象)
  • 字典的值就是数据库的值,每个值可以是字符串对象、列表对象、哈希表对象、集合对象和有序集合对象中的任一种对象。

相应的操作

  • 当添加新键值对时

    实际就是将一个新键值对添加到该“dict”键值字典中,其键为字符串对象,值为任意的一个Redis对象

  • 删除键

    实际上就是在该“dict”键值字典中删除键对应的键值对对象

  • 更新键

  • 对该键取值

当我们对这些键值进行这些读写操作的时候,redis还会执行一些额外的(维护)操作。这些操作有

  • 当读取一个键后,会更新服务器键空间命中次数(hit)或不命中次数(miss)
  • 读取一个键后,还会更新该键的LRU(最后一次使用时间)
  • 当读取一个键时,发现该键已经过期,那么出返回结果还需要删除这个键
  • 如果有客户端watch这个键,那么操作过这个键之后还需要将这个键标记为“dirty”,让watch这个键的客户端知道该键已经被修改过
  • 当每次修改过一个键之后,都会对“dirty”计数器的值增1,以便触发服务器的持久化以及复制操作。
  • 如果服务器开启了数据库通知的功能,那么修改了键之后,还需要按配置发送相应的通知。

服务器保存键值对过期时间的方法

过期时间是一个UNIX时间戳,当键的过期时间来临时,服务器就会自动从数据库中删除这个键。

redis有四个不同的命令来设置键的过期时间

  • EXPIRE (秒级)
  • PEXPIRE (毫秒级)
  • EXPIREAT (秒级)
  • PEXPIREAT (毫秒级)

实际上,EXPIRE、PEXPIRE、EXPIREAT三个命令最终都是使用PEXPIREAT命令来实现的。

redisDb结构中,expires字典保存了数据库中所有键的过期时间。

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

服务器删除过期键值对的方法

过期键的判定

  • 1、检查给定的键是否存在于过期字典,如果存在,那么取得键的过期时间
  • 2、检查当前UNIX时间戳是否大于取得的键的过期时间,如果大于的,那么该键已过期;否则该键未过期。

一般的常见的过期键删除机制

  • 定时删除,在设置键的时候设定一个定时器,让其在定时器结束时执行对键的删除操作
  • 惰性删除,设置后不管,当需要再次获取该键时,先检查该键是否已经过期,如果过期的话,执行删除操作,没有过期的话,返回键值
  • 定期删除,每隔一段时间,对所有键检查一次,有过期的则主动删除

Redis采用的过期键删除机制(惰性+定期)

  • 惰性删除策略的实现

    redis在所有读写数据库的Redis命令之前都会执行一个叫”expireIfNeeded”的函数对要操作的键进行检查

    • 如果要操作的键已经过期,那么”expireIfNeeded”函数将该键从数据库中删除
    • 如果未过期,那么”expireIfNeeded”函数不做操作

注:对所操作键存在与否的判断先与是否过期的判断

img

  • 定期删除策略的实现

    redis中会有一个周期性运行的操作函数“serverCron”。过期键的定期删除是由一个叫“activeExpireCycle”的函数实现。当serverCron函数运行时,activeExpireCycle 函数就会被调用。它会在规定的时间内分多次遍历服务器中各个数据库,从数据库的expires字典中随机检查一部分键的过期时间,并删除其中的过期键。

redis集群中过期键的删除

在Redis集群中 当主服务器删除一个过期键之后,它会向所有从服务器发送一条DEL命令,而从服务器即使发现过期键也不会自作主张的将其删除,而是等待主节点发来DEL命令,显式的进行删除。这种统一的、中心化的过期键策略可以保证主从服务器数据一致。