Redis 数据库及相关命令实现
1. 数据库管理命令
数据库管理的命令如下表格所示:redis keys命令详解
命令 | 描述 |
---|---|
FLUSHDB | 清空当前数据库的所有key |
FLUSHALL | 清空整个Redis服务器的所有key |
DBSIZE | 返回当前数据库的key的个数 |
DEL key [key …] | 删除一个或多个键 |
EXISTS key | 检查给定key是否存在 |
SELECT id | 切换到指定的数据库 |
RANDOMKEY | 从当前数据库中随机返回(不删除)一个 key 。 |
KEYS pattern | 查找所有符合给定模式pattern的key |
SCAN cursor [MATCH pattern] [COUNT count] | 增量式迭代当前数据库键 |
LASTSAVE | 返回最近一次成功将数据保存到磁盘上的时间,以 UNIX 时间戳格式表示。 |
TYPE key | 返回指定键的对象类型 |
SHUTDOWN | 停止所有客户端,关闭 redis 服务器(server) |
RENAME key newkey | 重命名指定的key,newkey存在时覆盖 |
RENAMENX key newkey | 重命名指定的key,当且仅当newkey不存在时操作 |
MOVE key db | 移动key到指定数据库 |
EXPIREAT key timestamp | 为 key 设置生存时间,EXPIREAT 命令接受的时间参数是 UNIX 时间戳 |
EXPIRE key seconds | 以秒为单位设置 key 的生存时间 |
PEXPIRE key milliseconds | 以毫秒为单位设置 key 的生存时间 |
PEXPIREAT key milliseconds-timestamp | 以毫秒为单位设置 key 的过期 unix 时间戳 |
TTL key | 以秒为单位返回 key 的剩余生存时间 |
PTTL key | 以毫秒为单位返回 key 的剩余生存时间 |
2. 数据库的实现
2.1数据库的结构
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) */
// 保存着 处于阻塞状态的键,value为NULL
dict *ready_keys; /* Blocked keys that received a PUSH */
// 事物模块,用于保存被WATCH命令所监控的键
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
// 当内存不足时,Redis会根据LRU算法回收一部分键所占的空间,而该eviction_pool是一个长为16数组,保存可能被回收的键
// eviction_pool中所有键按照idle空转时间,从小到大排序,每次回收空转时间最长的键
struct evictionPoolEntry *eviction_pool; /* Eviction pool of keys */
// 数据库ID
int id; /* Database ID */
// 键的平均过期时间
long long avg_ttl; /* Average TTL, just for stats */
} redisDb;
- blocking_keys 和 ready_keys 使用于在列表类型的阻塞命令(BLPOP等),详细内容看:Redis 列表键命令实现
- watched_keys 是用于事物模块。
- eviction_pool 是Redis在内存不足情况下,要回收内存时所使用。
- dict 和 expires 和 id是本篇主要讨论的。
Redis服务器和客户端也都保存有数据库的信息,下面截取出来:
typedef struct client {
redisDb *db; /* Pointer to currently SELECTed DB. */
} client;
struct redisServer {
redisDb *db;
int dbnum; /* Total number of configured DBs */
};
Redis服务器在初始化时,会创建一个长度为dbnum(默认为16)个 redisDb类型数组,客户端登录时,默认的数据库为0号数据库。当执行SELECT index
命令后,就会切换数据库。我们用两个客户端,表示如下图:
SELECT index
命令非常简单,源码如下:
// 切换数据库
int selectDb(client *c, int id) {
// id非法,返回错误
if (id < 0 || id >= server.dbnum)
return C_ERR;
// 设置当前client的数据库
c->db = &server.db[id];
return C_OK;
}
2.2 数据库的键值对字典
Redis是一个key-value数据库服务器,它将所有的键值对都保存在 redisDb 结构中的 dict 字典成员中(Redis 字典结构源码剖析)。
键值对字典的键,就是数据库的key,每一个key都是字符串的对象。
键值对字典的值,就是数据库的value,每一个value可以是字符串的对象,列表对象,哈希表对象,集合对象和有序集合对象中的任意一种。
数据库对键对象的删除操作,会连带值对象也一并删除,因此再有一些操作中,例如RENAME等命令,中间步骤会使用删除原有键,常常需要对值对象的引用计数加1,保护值对象不被删除,当新的键被设置后,则对值对象的引用计数减1。
我们向一个数据库中添加几个键,并且用图表示出来:
- 红色代表键对象,有 RAW编码的字符串对象,哈希对象。将结构简化表示,重点关注引用计数。
- 蓝色代表值对象,完成结构如图所示。
数据库每次根据键名找到值对象时,是分为以读操作 lookupKeyRead() 或写操作 lookupKeyWrite() 的方式取出的,而这两种有一定的区别,下面展示源码:
- lookupKey()函数
读操作 lookupKeyRead() 或写操作 lookupKeyWrite()都会调用这个底层的函数,这个函数非常简单,就是从键值对字典中先找到键名对应的键对象,然后取出值对象。
// 该函数被lookupKeyRead()和lookupKeyWrite()和lookupKeyReadWithFlags()调用
// 从数据库db中取出key的值对象,如果存在返回该对象,否则返回NULL
// 返回key对象的值对象
robj *lookupKey(redisDb *db, robj *key, int flags) {
// 在数据库中查找key对象,返回保存该key的节点地址
dictEntry *de = dictFind(db->dict,key->ptr);
if (de) { //如果找到
robj *val = dictGetVal(de); //取出键对应的值对象
/* Update the access time for the ageing algorithm.
* Don't do it if we have a saving child, as this will trigger
* a copy on write madness. */
// 更新键的使用时间
if (server.rdb_child_pid == -1 &&
server.aof_child_pid == -1 &&
!(flags & LOOKUP_NOTOUCH))
{
val->lru = LRU_CLOCK();
}
return val; //返回值对象
} else {
return NULL;
}
- lookupKeyRead()函数
lookupKeyRead()函数调用了lookupKeyReadWithFlags()函数,后者其实就判断了一下当前键是否过期,如果没有过期,更新 misses 和 hits 信息,然后就返回值对象。
还有就是两个宏:
- define LOOKUP_NONE 0 //zero,没有特殊意义
- define LOOKUP_NOTOU