redis的所有数据库都是保存在redisServer结构体的db数组中,db数组中的每个元素都是redisDb结构体代表一个数据库。db.c主要是封装了数据库的底层操作实现,操作dict和expires两个字典。所有数据库的键值队都是保存在dict字典(即内存中),而expires用来保存设置过期时间的键。redis用rdb和aof来进行数据的持久化,仅仅是内存数据备份,当内存不足时,通过内存策略删除部分键值对,不会持久化要删除的的键值队。
数据库的基本结构:
struct redisServer {
……
redisDb *db;//redis server的所有数据裤
int dbnum;//redis server的数据库的个数
……
};
typedef struct redisDb {
dict *dict; /*数据库的所有的键值队*/
dict *expires; /* 设置的过期的key,key-timeout */
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP) */
dict *ready_keys; /* Blocked keys that received a PUSH */
dict *watched_keys; /* 事务中被监视的key*/
struct evictionPoolEntry *eviction_pool; /* pool of keys */
int id; //数据库的id
long long avg_ttl; /* Average TTL, just for stats */
} redisDb;
db->dict的操作
/*<--------------获取key->val------------------->*/
//从db->dict字典中获取key的val,并更新val的lru,用于lru的内存策略
robj *lookupKey(redisDb *db, robj *key);
//封装了lookupKey,增加了过期判断和miss记录,用于读命令的调用
robj *lookupKeyRead(redisDb *db, robj *key) {
robj *val;
//如果key已经过期,从db->dict中删除
if (expireIfNeeded(db,key) == 1) {
//server是master
if (server.masterhost == NULL) return NULL;
//server是slave,client是master,命令为只读
if (server.current_client &&server.current_client != server.master &&
server.current_client->cmd &&server.current_client->cmd->flags & CMD_READONLY)
{
return NULL;
}
}
val = lookupKey(db,key);
//更新server的key的miss和hit
if (val == NULL)
server.stat_keyspace_misses++;
else
server.stat_keyspace_hits++;
return val;//返回key的value
}//封装了lookupKeyRead,当查找不到key是返回客户端指定reply,用于读命令的调用
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply);
//封装了lookupKey,增加了过期判断,用于写命令的调用
robj *lookupKeyWrite(redisDb *db, robj *key) {
expireIfNeeded(db,key);
return lookupKey(db,key);
}//封装了lookupKeyWrite,当查找不到key是返回客户端指定reply,用于写命令的调用
robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply);
/*<--------------获取key->val------------------->*/
//添加键值队到数据库
void dbAdd(redisDb *db, robj *key, robj *val) {
sds copy = sdsdup(key->ptr);
int retval = dictAdd(db->dict, copy, val);
//val是list对象,将key从bloking_keysh转移到ready_keys字典中
if (val->type == OBJ_LIST) signalListAsReady(db, key);
//集群模式下,将key-slot信息记录到slots_to_keys
if (server.cluster_enabled) slotToKeyAdd(key);
}//修改数据库中key的val
void dbOverwrite(redisDb *db, robj *key, robj *val) {
dictEntry *de = dictFind(db->dict,key->ptr);
serverAssertWithInfo(NULL,key,de != NULL);
dictReplace(db->dict, key->ptr, val);
}//删除数据库中key-val
int dbDelete(redisDb *db, robj *key) {
//删除expires中的key信息
if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
if (dictDelete(db->dict,key->ptr) == DICT_OK) {
//集群模式下删除,key-slot的信息
if (server.cluster_enabled) slotToKeyDel(key);
return 1;
} else {
return 0;
}
}//设置val为字符串对象的键值队
void setKey(redisDb *db, robj *key, robj *val) {
if (lookupKeyWrite(db,key) == NULL) {
dbAdd(db,key,val);
} else {
dbOverwrite(db,key,val);
}//增加字符串对象的引用计数
incrRefCount(val);
removeExpire(db,key);//从expires中移除key-timeout
signalModifiedKey(db,key);//触发被监视的键被修改的信息
}
int dbExists(redisDb *db, robj *key);//判断key是否存在数据库
robj *dbRandomKey(redisDb *db);//从数据库随机返回一个key
db->expires的操作
int removeExpire(redisDb *db, robj *key);//从expires中删除key的信息
void setExpire(redisDb *db, robj *key, long long when);//设置key的过期时间
long long getExpire(redisDb *db, robj *key);//获取key的过期时间
//判断设置过期时间的key是否过期,删除过期的key,
int expireIfNeeded(redisDb *db, robj *key) {
mstime_t when = getExpire(db,key);
mstime_t now;
if (when < 0) return 0;
if (server.loading) return 0;
now = server.lua_caller ? server.lua_time_start : mstime();
if (server.masterhost != NULL) return now > when;
//判断key是否过期
if (now <= when) return 0;
//记录server删除的过期健的个数
server.stat_expiredkeys++;
// 过期健的传播
propagateExpire(db,key,server.lazyfree_lazy_expire);
notifyKeyspaceEvent(NOTIFY_EXPIRED,"expired",key,db->id);
//删除过期的键
return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
dbSyncDelete(db,key);
}//过期健传播
void propagateExpire(redisDb *db, robj *key, int lazy) {
robj *argv[2];
argv[0] = lazy ? shared.unlink : shared.del;
argv[1] = key;
incrRefCount(argv[0]);
incrRefCount(argv[1]);
if (server.aof_state != AOF_OFF)
//将过期健以删除命令写入aof文件
feedAppendOnlyFile(server.delCommand,db->id,argv,2);
//传播过期健给slave
replicationFeedSlaves(server.slaves,db->id,argv,2);
decrRefCount(argv[0]);
decrRefCount(argv[1]);
}
在集群模式下,slots_to_keys用于记录每个槽中key的存放信息。这些信息用于槽迁移时候,能准确迁移槽中的键值队。
void slotToKeyAdd(robj *key) ;//将key添加跳跃表slots_to_keys中对应的slot中
void slotToKeyDel(robj *key) ;//从key所在slot删除指定key
void slotToKeyFlush(void);//重置跳跃表的slots_to_keys中的所有信息
unsigned int getKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count);
//从指定的slot随机获取count个key
unsigned int delKeysInSlot(unsigned int hashslot);//删除指定slot中所有的key信息
unsigned int countKeysInSlot(unsigned int hashslot);//获取指定槽中key的数量