Redis数据库

2018/02/26 Redis

Redis数据库

服务器中的数据库

Redis是内存数据库,除了需要redisObject对基础数据结构封装外,还需要一个结构体对数据库进行封装,用来管理数据库相关数据和实现相关操作,这个结构体就是redisDb。 Redis服务器将所有数据库都保存在服务器状态redis.h/redisServer结构的db数组中,db数组的每个项都是一个redis.h/redisDb结构,每个redisDb结构代表一个数据库:

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

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

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

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

redisDb有两个重要属性——dict和expires,分别是键空间散列表、过期时间散列表。 dict保存了所有的键值对,expires保存了键的过期时间,像scan、move、sort等命令是对redisDb键空间散列表的操作,expire、persist等命令是对redisDb键的过期时间散列表的操作。 redisDb结构体定义如下:

typedef struct redisDb {
    dict *dict;                 /* 键空间字典 */
    dict *expires;              /* key的超时时间字典 */
    dict *blocking_keys;        /* 阻塞的key */
    dict *ready_keys;           /* 准备好的key */
    dict *watched_keys;         /* 执行事务的key */
    int id;                     /* 数据库ID */
    long long avg_ttl;          /* 平均生存时间,用于统计 */
    list *defrag_later;         /* 逐渐尝试逐个碎片整理的key列表*/
} redisDb;

以下是属性说明。

  • dict:键空间散列表,存放所有键值对。
  • expires:过期时间散列表,存放键的过期时间,注意dict和expires中的键都指向同一个键的sds。
  • blocking_keys:处于阻塞状态的键和对应的client。
  • ready_keys:解除阻塞状态的键和对应的client,与blocking_keys属性相对,为了实现需要阻塞的命令设计。
  • watched_keys:watch的键和对应的client,主要用于事务。
  • id:数据库ID。
  • avg_ttl:数据库内所有键的平均生存时间。
  • defrag_later:逐渐尝试逐个碎片整理的key列表。

切换数据库

每个Redis客户端都有自己的目标数据库,每当客户端执行数据库写命令或者数据库读命令的时候,目标数据库就会成为这些命令的操作对象。

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

以下代码示例演示了客户端在0号数据库设置并读取键msg,之后切换到2号数据库并执行类似操作的过程:

redis> SET msg "hello world"
OK
redis> GET msg
"hello world"
redis> SELECT 2
OK
redis[2]> GET msg
(nil)
redis[2]> SET msg"another world"
OK
redis[2]> GET msg
"another world"

在服务器内部,客户端状态redisClient结构的db属性记录了客户端当前的目标数据库,这个属性是一个指向redisDb结构的指针:

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

redisClient.db指针指向redisServer.db数组的其中一个元素,而被指向的元素就是客户端的目标数据库。 通过修改redisClient.db指针,让它指向服务器中的不同数据库,从而实现切换目标数据库的功能——这就是SELECT命令的实现原理。

数据库键空间

Redis是一个键值对(key-value pair)数据库服务器,服务器中的每个数据库都由一个redis.h/redisDb结构表示,其中,redisDb结构的dict字典保存了数据库中的所有键值对,我们将这个字典称为键空间(key space):

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

键空间和用户所见的数据库是直接对应的:

  • 键空间的键也就是数据库的键,每个键都是一个字符串对象。
  • 键空间的值也就是数据库的值,每个值可以是字符串对象、列表对象、哈希表对象、集合对象和有序集合对象中的任意一种Redis对象。

举个例子,如果我们在空白的数据库中执行以下命令:

redis> SET message "hello world"
OK
redis> RPUSH alphabet "a" "b" "c"
(integer)3
redis> HSET book name "Redis in Action"
(integer) 1
redis> HSET book author "Josiah L. Carlson"
(integer) 1
redis> HSET book publisher "Manning"
(integer) 1

那么在这些命令执行之后,数据库的键空间将会是:

  • alphabet是一个列表键,键的名字是一个包含字符串”alphabet”的字符串对象,键的值则是一个包含三个元素的列表对象。
  • book是一个哈希表键,键的名字是一个包含字符串”book”的字符串对象,键的值则是一个包含三个键值对的哈希表对象。
  • message是一个字符串键,键的名字是一个包含字符串”message”的字符串对象,键的值则是一个包含字符串”hello world”的字符串对象。

因为数据库的键空间是一个字典,所以所有针对数据库的操作,比如添加一个键值对到数据库,或者从数据库中删除一个键值对,又或者在数据库中获取某个键值对等,实际上都是通过对键空间字典进行操作来实现的,以下几个小节将分别介绍数据库的添加、删除、更新、取值等操作的实现原理。

添加新键

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

举个例子,在执行以下命令之后:

redis> SET date "2013.12.1"
OK

键空间将添加一个新的键值对,这个新键值对的键是一个包含字符串”date”的字符串对象,而键值对的值则是一个包含字符串”2013.12.1”的字符串对象。

删除键

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

举个例子,在执行以下命令之后:

redis> DEL book
(integer) 1

键book以及它的值将从键空间中被删除

更新键

对一个数据库键进行更新,实际上就是对键空间里面键所对应的值对象进行更新,根据值对象的类型不同,更新的具体方法也会有所不同。

举个例子,在执行以下命令之后:

redis> SET message "blah blah"
OK

键message的值对象将从之前包含”hello world”字符串更新为包含”blah blah”字符串

对键取值

对一个数据库键进行取值,实际上就是在键空间中取出键所对应的值对象,根据值对象的类型不同,具体的取值方法也会有所不同。

举个例子,当执行以下命令时:

redis> GET message
"hello world"

GET命令将首先在键空间中查找键message,找到键之后接着取得该键所对应的字符串对象值,之后再返回值对象所包含的字符串”hello world”

Search

    微信好友

    博士的沙漏

    Table of Contents