Redis-数据库杂谈

437 阅读7分钟

服务器中的数据库

Redis数据库将所有的数据库状态都保存在一个redisServer结构的db数组中,db数组中的每个项都是redisDB结构,其中的每个redisDB都代表一个数据库。

struct redisServer {
    redisDB *db; // 一个数组,保存redis的所有数据库
    int dbnum; // 服务器的数据库数量
}

dbnum属性的值是可以由服务器来进行配置的,默认是16,所以Redis默认有16个数据库。

切换数据库

每个Redis客户端都有自己对应的数据库,每当客户端执行命令的时候就会将这个命令操作的就是对应的数据库。
默认的情况,Redis会使用0号数据库,用户可以通过使用SELECT命令来进行选择。

每个客户端都保存了自己当前使用的数据库,并通过制作来进行指向。

typedef struct redisClient {
    redisDB *db; // 记录的当前用户使使用的数据库
}

数据库键空间

Redis是一个键值对数据库服务器,在每个数据库结构(redisDB)中有一个dict字典保存了数据库中的所有键值对,这个就是键空间(key space):

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

这个键空间就是存储键值对地方,当我们执行相应的命令时,那么这些键值对就会写入到这个键空间中。

键的过期时间

redisDb结构中的expires字典保存了数据库中所有键的过期时间,这个就是过期字典:

  • 过期字典的键是一个指针,指向键空间中的某个键对象
  • 过期字典的值是一个long long类型的整数,这个整数保存了这个键的过期时间-一个毫秒精度的UNIX时间戳
typedef struct redisDb {
    dict* dict;//数据库键空间,保存了这个数据库中的所有键值对
    dict *expires; //过期字典,保存了键的过期时间
}

过期键的判断

过期键的判定流程:

  • 检查给定的键是否有过期时间,如果有,那么取出过期时间
  • 检查当前的UNIX时间是否大于键的过期时间,如果是的话,那么键已经过期,如果不是,那么键未过期

过期键的删除策略

当Redis的一个键过期了,那么什么时候这个键会被删除?
Redis具有三种过期键的删除策略:

  • 定时删除, 设置键过期时间的时候,同时创建一个定时器,让定时器在键过期的时候立马对这个键进行删除。
  • 惰性删除, 不管键是否过期,每次去取键的时候,都会检查这个键是否过期,如果过期就执行删除,没有过期就进行返回
  • 定时删除, 每隔一段时间,就来对键进行一个检查,删除里面的过期键,至于要删除多少键,以及检查多少个数据库,这个是由算法来进行决定的

定时删除

定时删除策略是对内存最好的,通过时间定时器可以在键过期的时候就立马对这个键进行删除,释放空间。但是这样也有一个缺点,就是对CPU的时间并不是很友好,但过期的键过多的时候删除过期键会占用很大一部分CPU的时间。
而且创建一个定时任务的时候需要用到的是Redis服务器中的时间事件,而当前时间事件的实现方式是无序链表,查找一个事件的时间是O(N)并不能高效的处理大量的时间事件。

惰性删除

惰性删除是对CPU最友好的,但也是对内存最不友好的,当一个键已经过期,如果这个就键只要不被访问,那么这个键会一直保存在数据库中,它占用的内存就不会被释放,大量的过期键的占用对于Redis服务器的内存是一种垃圾的占用。

定时删除

相对于前面两种删除方法过于明显的缺陷,定时删除是一种比较中和的方式。

  • 定时删除每隔一段时间来进行删除,通过减少删除频率来减少删除操作对于CPU时间的影响
  • 定时的删除要可以减少过期键对于内存空间的占用 但定时删除的策略的难点是确定删除操作的执行时间和执行频率。

Redis的过期键删除策略

前面讨论了三种过期键的删除策略,Redis实际使用的惰性删除和定时删除两种策略,通过这两种策略的配合,服务器便可以很好平衡CPU和内存空间之间的关系

惰性删除实现

惰性删除策略实际上是使用expireIfNeed函数实现的,所有Redis的读写命令在执行前都会调用expireIfNeed函数对输入键进行检查:

  • 如果这个键已经过期,那么expireIfNeed函数将这个键进行删除
  • 如果这个键未过期,那么expireIfNeed函数不做动作 expireIfNeed函数就像是一个过滤器一样,在命令真正执行之前过滤掉所有过期的键。 由于expireIfNeed函数会将过期的键进行删除,那么命令执行的时候就会存在键不存在的情况,这样在每个命令执行的时候都需要考虑键存在和不存在的情况:
  • 当键存在的时候,命令按照键的存在的情况来执行
  • 当键不存在或键因为过期而被expireIfNeed函数删除的时候,命令按照键不存在的情况来进行执行

定时删除策略的实现

定时删除策略是由activeExpireCycle函数实现,每当Redis的服务器周期性操作serverCron函数执行时,activeExpireCycle函数就会被调用,它可以在规定的时间内,分多次遍历服务器中的各个数据库,从数据库expires字典中随机检查一部分键的过期时间,并删除其中的过期键。流程:

  • 函数每次运行时,都从一定数量的数据库中取出一定的随机键进行检查
  • 全局变量current_db会记录当前activeExpireCycle函数检查的进度,并在下一次activeExpireCycle函数调用的时候,接着上次的进度进行处理,比如说,如果当前activeExpireCycle函数在遍历10号数据库时返回了,那么下次activeExpireCycle函数执行时,将从11号数据库开始查找并删除过期键
  • 随着activeExpireCycle函数的不断执行,服务器中的所有数据库都会被检查一遍,这个时候将current_db变量重置为0,然后再次开始新一轮的检查。

AOF,RDB对过期键的处理

RDB

当我们使用SAVE命令或者BGSAVE命令创建一个新的RDB文件时,数据库会对过期键进行过滤,过期的键并不会保存在RDB文件中。
当Redis服务器载入RDB文件的时候:

  • 如果服务器是以主服务器来进行运行的,那么在载入的时候也会对过期的键进行过滤
  • 如果是从服务器,那么无论是否过期都会载入到数据库中

AOF

对于AOF来说,如果在某个过期键并没有被过期或者惰性删除,那么这个键是不回造成任何的影响的,但当这个过期键被删除之后,那么这个键就会被追加一条DEL命令,来表示这个键被删除。
在AOF重写的过程中,也会对过期的键进行过滤。

复制

但服务器处于复制模式下时,从服务器的过期键删除是由主服务器控制的。

  • 主服务器删除一个键之后,会显示的发送一条DEL命令给从服务器,告知从服务器删除这个过期键
  • 从服务器执行命令的时候,就算遇到过期的键也不会进行删除操作,而是继续进行处理,像未过期一样
  • 从服务器只有接收到主服务器发送的DEL命令之后,才会将这个过期的键进行删除。 这种统一,中心化的过期删除策略可以保证主从服务器可以达到数据的一致性。