一.服务器中的数据库

        Redis服务器将所有数据库都保存在服务器状态redis.h/redisServer结构的db数组中,db数组的每一个项都是一个redis.h/redisDb结构,每一个RedisDb结构代表一个数据库。

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

typedef struct redisDb {
    dict *dict;                 /* The keyspace for this DB */
    dict *expires;              /* Timeout of keys with a timeout set */
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP)*/
    dict *ready_keys;           /* Blocked keys that received a PUSH */
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    int id;                     /* Database ID */
    long long avg_ttl;          /* Average TTL, just for stats */
    unsigned long expires_cursor; /* Cursor of the active expire cycle. */
    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;

struct redisServer {
    /* General */
    pid_t pid;                  /* Main process pid. */
    pthread_t main_thread_id;         /* Main thread id */
    char *configfile;           /* Absolute config file path, or NULL */
    char *executable;           /* Absolute executable file path. */
    char **exec_argv;           /* Executable argv vector (copy). */
    int dynamic_hz;             /* Change hz value depending on # of clients. */
    int config_hz;              /* Configured HZ value. May be different than
                                   the actual 'hz' field value if dynamic-hz
                                   is enabled. */
    mode_t umask;               /* The umask value of the process on startup */
    int hz;                     /* serverCron() calls frequency in hertz */
    int in_fork_child;          /* indication that this is a fork child */
    //数据库数组
    redisDb *db;
    ...
    ...
    int dbnum;                      /* Total number of configured DBs */
    ...
    ...
};

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

apifox redis多个库_数据库

 二. 切换数据库

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

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

apifox redis多个库_apifox redis多个库_02

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

typedef struct client {
    uint64_t id;            /* Client incremental unique ID. */
    connection *conn;
    int resp;               /* RESP protocol version. Can be 2 or 3. */
    //记录当前正在使用的数据库
    redisDb *db;            /* Pointer to currently SELECTed DB. */
    ...
    ...
}client;

        client.db指针指向redisServer.db数组的其中一个元素,而被指向的元素就是客户端的目标数据库。

apifox redis多个库_redis_03

         select的实现原理:通过修改client.db指针,让它指向服务器不同的数据库,从而实现切换目标数据库的功能。

        目前,Redis仍然没有可以返回客户端目标数据库的命令,虽然redis-cli客户端在输入符旁边提示当前所使用的目标数据库。

        但是,你在其他语言的客户端中执行redis命令,并且该客户端好像没有向redis-cli那样显示目标数据库的号码,在多次切换数据库之后,你可能会忘记当前使用的哪个数据库。

        为了避免误操作,在执行Redis命令,特别向FLUSHDB这样的危险命令前,最好先执行一个select命令,显示的切换到指定数据库,然后再执行别的命令。

三.数据库的键空间

        Redis是一个键值对数据库服务器,服务器中的每一个数据库是由一个redis.h/redisDb结构表示。其中redisDb结构中的dict字典保存了数据库中的所有键值对,我们简称这个字典为键空间。

typedef struct redisDb {
    //键空间
    dict *dict;                 /* The keyspace for this DB */
    dict *expires;              /* Timeout of keys with a timeout set */
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP)*/
    dict *ready_keys;           /* Blocked keys that received a PUSH */
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    int id;                     /* Database ID */
    long long avg_ttl;          /* Average TTL, just for stats */
    unsigned long expires_cursor; /* Cursor of the active expire cycle. */
    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;

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

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

         举个例子:

127.0.0.1:6379> set message "hello world"
OK
127.0.0.1:6379> rpush alphabet "a" "b" "c"
(integer) 3
127.0.0.1:6379> hset book name "Redis in Action"
(integer) 1
127.0.0.1:6379> hset book author "Josiah L. Carlson" publisher "Manning"
(integer) 2
127.0.0.1:6379>
  • messge是一个字符串键,键的名字是一个包含字符串messge的字符串对象,键的值是一个包含字符串"hello world"的字符串对象。
  • alphabet是一个列表键,键的名字是一个包含字符串alphabet的字符串对象,键的值是一个包含三个元素的列表对象。
  • book是一个哈希键,键的名字是一个包含字符串book的字符串对象,键的值是一个包含三个键值对的哈希表对象。

apifox redis多个库_数据库_04

        3.1 数据库的增删查改 

        因为数据库的键是一个字典,所以对数据库的增删查改,实际是对字典的增删查改。

                 3.1.1添加新建

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

        比如:

127.0.0.1:6379> set date "2023.12.1"
OK

         添加一个新键值对,键是一个值为date字符串的字符串对象,键的值为2023.12.1字符串的字符串对象。

apifox redis多个库_数据库_05

                3.1.2 删除键 

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

127.0.0.1:6379> del book
(integer) 1

apifox redis多个库_数据库_06

                3.1.3 更新键 

键对应的值对象进行更新根据值对象的类型不同,更新的具体方法也会不同。

127.0.0.1:6379> get message
"hello world"
127.0.0.1:6379> set message "blah blah"
OK
127.0.0.1:6379> get message
"blah blah"

        上面是对数据库键message对应的值"hello world"字符串修改成了"blah blah"字符串。

127.0.0.1:6379> hset book page 320
(integer) 1
127.0.0.1:6379>

        上面是修改数据库键book对应的值哈希对象,新加一个节点键为page,值为320。 

apifox redis多个库_字符串_07

                3.1.4 对键取值 

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

127.0.0.1:6379> get message
"hello world"

        get命令首先在键空间(client.db)中查找键message,找到键后,去到该键对应的字符串对象,之后返回值对象所包含的字符串"hello world"

apifox redis多个库_apifox redis多个库_08

127.0.0.1:6379> lrange alphabet 0 -1
1) "a"
2) "b"
3) "c"
127.0.0.1:6379>

        lrange命令首先在键空间(client.db)中查找键alphabet,找到键后,去到该键对应的列表对象值,之后返回列表对象中包含的三个字符串对象的值。

apifox redis多个库_服务器_09

                3.1.5 其他键空间操作

        除了增删查改操作外,还有针对数据库本身的Redis命令, 也是通过键空间进行处理完成的。

  • FLUSHDB:通过删除键空间的所有键值对实现的
  • RANDOMKEY:通过在键空间随机返回一个键实现的
  • DSSIZE:返回键空间中包含的键值对的数量

        其他命令exits,rename,keys等,都是通过对键空间进行操作来实现的。

                3.1.6 读写键空间的维护操作

        当使用Redis命令对数据库进行读写时,服务器不仅会对键空间执行指定的读写操作,还会执行额外的维护操作。

  • 在读取一个键时(读操作和写操作都会对键进行读取),服务器会更具键是否存在来更新服务器的键的空间命中(hit)次数或键的空间不命中次数,可以通过命令info stats命令的keyspace_hits属性和keyspce_misses属性查看。
  • 在读取一个键后。服务器会更新一个键的LRU(最后一次使用)时间,这个值可以用于计算键的闲置时间。使用OBJECT IDLETIME <key>命令来查看键key的闲置时间。
  • 如果服务器在读取一个键发现该键已经过期,那么服务器会先删除这个过期键,然后执行余下操作。
  • 如果客户端使用watch监视一个键,当服务器对监视的键进行修改后,会把这个键标记为脏(dirty),从而让事务注意到这个键已经被修改过。
  • 服务器每修改一个键之后,都会对脏(dirty)键计数器的值增1。这个计数器会触发服务器的持久化以及复制操作
  • 如果服务器开启了数据库通知功能,那么在对键进行修改后,服务器将按配置发送相应的数据库通知。