redis-数据库

数据库

服务器中的数据库

Redis服务器将所有数据库保存在redisServer结构中的db数组中

1
2
3
struct redisServer {
redisDb *db;
}

在初始化服务器时,程序会根据服务器状态的dbnum属性来决定应该创建多少个数据库,但是一般默认为16个

切换数据库

每个Redis客户端都可以指定使用的数据库,默认使用0号数据库,使用select 1可以选择1号数据库,以此类推,在服务器内部,存在一个数据结构来记录客户端使用的数据库

1
2
3
typedef struct redisClient [
redisDb *db;
] redisClient

该指针指向了redisServer.db数组中的某一个元素

数据库键空间

Redis是一个键值对数据库服务器,服务器中的每个数据库都由redisDb结构表示,其内部维护了一个dict字典

1
2
3
typedef struct redisDb {
dict *dict;
}

键空间和用户所见的数据库的键值对是直接对应的,即键空间的键就是字符串对象,键空间的值就是不同的redis对象

具体来说,set message “hello, world!” 就是在数据库的字典中创建了一个键为message的字符串,值为 “hello, world!”字符串的键值对

读写键空间时会发生一些维护操作:

  • 当读取一个键之后,会记录命中、不命中次数
  • 更新LRU时间
  • 如果读键时发现这个键已经过期了,则删除
  • 如果有客户端使用watch命令监视了某个键时,当服务器修改了这个键,会把这个键标记为脏
  • 服务器每次修改一个键之后,都会对脏键计数器的值增1,这个脏键计数器会触发服务器的持久化和复制操作
  • 如果服务器开启了数据库通知功能,对键进行修改后,服务器将按配置发送相应的数据库通知

过期键

1
2
3
typedef struct redisDb {
dict *expires;
}

在redisDb中维护了一个过期字典,过期字典指向设置了过期时间的对象,并且包含了一个过期时间

Redis过期删除策略是定期删除 + 惰性删除, 具体来说每个一段时间从过期字典中抽取键来检查是否过期并删除,并且在访问一个键时先检查是否过期进行删除

AOF RDB 复制对过期键的处理

  1. 生成RDB文件
    在执行SAVE命令或者BGSAVE命令会创建一个新的RDB文件,程序对数据库中的键进行检查,已过期的键不会被保存到新创建的RDB文件
  2. 载入RDB文件
  • 如果服务器以主服务器模式运行,在载入RDB文件时,程序会对文件中保存的键进行检查,未过期的键会被载入到数据库中,而过期的则会被忽略
  • 如果服务器以从服务器模式运行,在载入RDB文件时无论是否过期都会全部载入,但是从服务器还会进行主从同步, 这里不直接删除还是考虑和主服务器的数据一致性问题
  1. AOF写入
    当服务器以AOF持久化模式运行时,如果数据库中的某个键已经过期,AOF文件不会有其他任何的影响,因为在过期时被删除会追加一条命令DEL命令,如果还没有被删除更不会产生影响
  2. AOF重写
    在执行AOF重写时,已过期的键不会被保存到重写后的AOF文件
  3. 复制
    当服务器运行在复制模式下时,从服务器的过期删除动作由主服务器控制,简单来说,但主服务器执行DEL操作时会把该命令发送给从服务器。进一步说,从服务器不控制过期键,即从服务器发生了某一个key已经过期了但是不会执行删除操作,当客户端get时仍会返回,只有接收主服务器的del命令后才会删除,这保证了主从服务器之间的一致性,但是存在数据准确性问题

数据库通知

分为键空间通知:某个键被执行了哪些操作,键事件通知:某个命令被哪些键执行了

1
2
3
4
5
6
7
8
9
10
11
subscribe __keyspace@0__:message // 指的是订阅了message的键空间通知

"message"
"__keyspace@0__:message"
"set" // 指的是对message这个键执行了set操作

subscrible __keyevenet@0__:del

"message"
"__keyevent@0__:del"
"key" // 指的是del操作被key执行了

服务器配置的notify-keyspace-evenets选项决定了服务器所发送通知的类型:

  • 所有类型的键空间通知和键事件通知:AKE
  • 所有类型的键空间通知:AK
  • 所有类型的键事件通知:AE
  • 只和字符串有关的键空间通知: K$
  • 之和列表键有关的键事件通知: E1

发送数据库通知的功能是通过notifyKeyspaceEvent函数实现的
void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid)
event keys dbid分别是事件的名称 产生事件的键 产生事件的数据库号码

在前文提到,对键的读写操作有额外的操作比如数据库通知,具体来说以sadd命令为例,
saddcommand中有一段代码:

1
2
notifyKeyspaceEvent(REDIS_NOTIFY_SET,“sadd”, c->argv[1], c->db->id
);

具体看通知函数的实现,这里简单用语言描述;

  • 如果给定的通知不是服务器允许发送的通知,则直接返回
  • 发送键空间通知
  • 发送键事件通知
    当然上述发送对应的通知需要判断配置设定