lookupKey:查找指定的键,如果存在返回对应的值
robj *lookupKey(redisDb *db, robj *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. */
// 更新时间信息(只在不存在子进程时执行,防止破坏 copy-on-write 机制)
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1)
val->lru = LRU_CLOCK();
// 返回值
return val;
} else {
// 节点不存在
return NULL;
}
}
lookupKeyRead:首先会检查key是否过期,然后从数据库中取出该值,并且更新命中/不明中信息
robj *lookupKeyRead(redisDb *db, robj *key) {
robj *val;
// 检查 key 释放已经过期
expireIfNeeded(db,key);
// 从数据库中取出键的值
val = lookupKey(db,key);
// 更新命中/不命中信息
if (val == NULL)
server.stat_keyspace_misses++;
else
server.stat_keyspace_hits++;
// 返回值
return val;
}
lookupKeyWrite
robj *lookupKeyWrite(redisDb *db, robj *key) {
// 删除过期键
expireIfNeeded(db,key);
// 查找并返回 key 的值对象
return lookupKey(db,key);
}
lookupKeyReadOrReply:为执行读取操作而从数据库中查找返回 key 的值。
robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply) {
// 查找
robj *o = lookupKeyRead(c->db, key);
// 决定是否发送信息
if (!o) addReply(c,reply);
return o;
}
lookupKeyWriteOrReply:为执行写入操作而从数据库中查找返回 key 的值。
robj *lookupKeyWriteOrReply(redisClient *c, robj *key, robj *reply) {
robj *o = lookupKeyWrite(c->db, key);
if (!o) addReply(c,reply);
return o;
}
dbAdd:尝试将键值对 key 和 val 添加到数据库中。
void dbAdd(redisDb *db, robj *key, robj *val) {
// 复制键名
sds copy = sdsdup(key->ptr);
// 尝试添加键值对
int retval = dictAdd(db->dict, copy, val);
// 如果键已经存在,那么停止
redisAssertWithInfo(NULL,key,retval == REDIS_OK);
// 如果开启了集群模式,那么将键保存到槽里面
if (server.cluster_enabled) slotToKeyAdd(key);
}
setKey:高层次的 SET 操作函数。
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);
// 发送键修改通知
signalModifiedKey(db,key);
}
dbExists:检查键 key 是否存在于数据库中,存在返回 1 ,不存在返回 0 。
int dbExists(redisDb *db, robj *key) {
return dictFind(db->dict,key->ptr) != NULL;
}
dbRandomKey:随机从数据库中取出一个键,并以字符串对象的方式返回这个键。
robj *dbRandomKey(redisDb *db) {
dictEntry *de;
while(1) {
sds key;
robj *keyobj;
// 从键空间中随机取出一个键节点
de = dictGetRandomKey(db->dict);
// 数据库为空
if (de == NULL) return NULL;
// 取出键
key = dictGetKey(de);
// 为键创建一个字符串对象,对象的值为键的名字
keyobj = createStringObject(key,sdslen(key));
// 检查键是否带有过期时间
if (dictFind(db->expires,key)) {
// 如果键已经过期,那么将它删除,并继续随机下个键
if (expireIfNeeded(db,keyobj)) {
decrRefCount(keyobj);
continue; /* search for another key. This expired. */
}
}
// 返回被随机到的键(的名字)
return keyobj;
}
}
dbDelete:从数据库中删除给定的键,键的值,以及键的过期时间。
int dbDelete(redisDb *db, robj *key) {
/* Deleting an entry from the expires dict will not free the sds of
* the key, because it is shared with the main dictionary. */
// 删除键的过期时间
if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
// 删除键值对
if (dictDelete(db->dict,key->ptr) == DICT_OK) {
// 如果开启了集群模式,那么从槽中删除给定的键
if (server.cluster_enabled) slotToKeyDel(key);
return 1;
} else {
// 键不存在
return 0;
}
}
emptyDb:清空服务器的所有数据。
long long emptyDb(void(callback)(void*)) {
int j;
long long removed = 0;
// 清空所有数据库
for (j = 0; j < server.dbnum; j++) {
// 记录被删除键的数量
removed += dictSize(server.db[j].dict);
// 删除所有键值对
dictEmpty(server.db[j].dict,callback);
// 删除所有键的过期时间
dictEmpty(server.db[j].expires,callback);
}
// 如果开启了集群模式,那么还要移除槽记录
if (server.cluster_enabled) slotToKeyFlush();
// 返回键的数量
return removed;
}
selectDb:将客户端的目标数据库切换为 id 所指定的数据库
int selectDb(redisClient *c, int id) {
// 确保 id 在正确范围内
if (id < 0 || id >= server.dbnum)
return REDIS_ERR;
// 切换数据库(更新指针)
c->db = &server.db[id];
return REDIS_OK;
}
signalModifiedKey:每当数据库中的键被改动时, signalModifiedKey() 函数都会被调用。
void signalModifiedKey(redisDb *db, robj *key) {
touchWatchedKey(db,key);
}
signalFlushedDb:每当一个数据库被清空时, signalFlushDb() 都会被调用。
void signalFlushedDb(int dbid) {
touchWatchedKeysOnFlush(dbid);
}
flushdbCommand:清空客户端指定的数据库
void flushdbCommand(redisClient *c) {
server.dirty += dictSize(c->db->dict);
// 发送通知
signalFlushedDb(c->db->id);
// 清空指定数据库中的 dict 和 expires 字典
dictEmpty(c->db->dict,NULL);
dictEmpty(c->db->expires,NULL);
// 如果开启了集群模式,那么还要移除槽记录
if (server.cluster_enabled) slotToKeyFlush();
addReply(c,shared.ok);
}
flushallCommand:清空服务器中的所有数据库
void flushallCommand(redisClient *c) {
// 发送通知
signalFlushedDb(-1);
// 清空所有数据库
server.dirty += emptyDb(NULL);
addReply(c,shared.ok);
// 如果正在保存新的 RDB ,那么取消保存操作
if (server.rdb_child_pid != -1) {
kill(server.rdb_child_pid,SIGUSR1);
rdbRemoveTempFile(server.rdb_child_pid);
}
// 更新 RDB 文件
if (server.saveparamslen > 0) {
/* Normally rdbSave() will reset dirty, but we don't want this here
* as otherwise FLUSHALL will not be replicated nor put into the AOF. */
// rdbSave() 会清空服务器的 dirty 属性
// 但为了确保 FLUSHALL 命令会被正常传播,
// 程序需要保存并在 rdbSave() 调用之后还原服务器的 dirty 属性
int saved_dirty = server.dirty;
rdbSave(server.rdb_filename);
server.dirty = saved_dirty;
}
server.dirty++;
}
delCommand:删除客户端指定的键
void delCommand(redisClient *c) {
int deleted = 0, j;
// 遍历所有输入键
for (j = 1; j < c->argc; j++) {
// 先删除过期的键
expireIfNeeded(c->db,c->argv[j]);
// 尝试删除键
if (dbDelete(c->db,c->argv[j])) {
// 删除键成功,发送通知
signalModifiedKey(c->db,c->argv[j]);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,
"del",c->argv[j],c->db->id);
server.dirty++;
// 成功删除才增加 deleted 计数器的值
deleted++;
}
}
// 返回被删除键的数量
addReplyLongLong(c,deleted);
}
existsCommand:检查一个键是否存在
void existsCommand(redisClient *c) {
// 检查键是否已经过期,如果已过期的话,那么将它删除
// 这可以避免已过期的键被误认为存在
expireIfNeeded(c->db,c->argv[1]);
// 在数据库中查找
if (dbExists(c->db,c->argv[1])) {
addReply(c, shared.cone);
} else {
addReply(c, shared.czero);
}
}
selectCommand:修改客户端的当前数据库
void selectCommand(redisClient *c) {
long id;
// 不合法的数据库号码
if (getLongFromObjectOrReply(c, c->argv[1], &id,
"invalid DB index") != REDIS_OK)
return;
if (server.cluster_enabled && id != 0) {
addReplyError(c,"SELECT is not allowed in cluster mode");
return;
}
// 切换数据库
if (selectDb(c,id) == REDIS_ERR) {
addReplyError(c,"invalid DB index");
} else {
addReply(c,shared.ok);
}
}
randomkeyCommand:随即返回一个键
void randomkeyCommand(redisClient *c) {
robj *key;
// 随机返回键
if ((key = dbRandomKey(c->db)) == NULL) {
addReply(c,shared.nullbulk);
return;
}
addReplyBulk(c,key);
decrRefCount(key);
}
keysCommand:返回和客户端指定的模式一致的所有键
void keysCommand(redisClient *c) {
dictIterator *di;
dictEntry *de;
// 模式
sds pattern = c->argv[1]->ptr;
int plen = sdslen(pattern), allkeys;
unsigned long numkeys = 0;
void *replylen = addDeferredMultiBulkLength(c);
// 遍历整个数据库,返回(名字)和模式匹配的键
di = dictGetSafeIterator(c->db->dict);
allkeys = (pattern[0] == '*' && pattern[1] == '\0');
while((de = dictNext(di)) != NULL) {
sds key = dictGetKey(de);
robj *keyobj;
// 将键名和模式进行比对
if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) {
// 创建一个保存键名字的字符串对象
keyobj = createStringObject(key,sdslen(key));
// 删除已过期键
if (expireIfNeeded(c->db,keyobj) == 0) {
addReplyBulk(c,keyobj);
numkeys++;
}
decrRefCount(keyobj);
}
}
dictReleaseIterator(di);
setDeferredMultiBulkLength(c,replylen,numkeys);
}
removeExpire:移除键 key 的过期时间
int removeExpire(redisDb *db, robj *key) {
/* An expire may only be removed if there is a corresponding entry in the
* main dict. Otherwise, the key will never be freed. */
// 确保键带有过期时间
redisAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);
// 删除过期时间
return dictDelete(db->expires,key->ptr) == DICT_OK;
}
setExpire:将键 key 的过期时间设为 when
void setExpire(redisDb *db, robj *key, long long when) {
dictEntry *kde, *de;
/* Reuse the sds from the main dict in the expire dict */
// 取出键
kde = dictFind(db->dict,key->ptr);
redisAssertWithInfo(NULL,key,kde != NULL);
// 根据键取出键的过期时间
de = dictReplaceRaw(db->expires,dictGetKey(kde));
// 设置键的过期时间
// 这里是直接使用整数值来保存过期时间,不是用 INT 编码的 String 对象
dictSetSignedIntegerVal(de,when);
}
getExpire:返回给定 key 的过期时间。
long long getExpire(redisDb *db, robj *key) {
dictEntry *de;
/* No expire? return ASAP */
// 获取键的过期时间
// 如果过期时间不存在,那么直接返回
if (dictSize(db->expires) == 0 ||
(de = dictFind(db->expires,key->ptr)) == NULL) return -1;
/* The entry was found in the expire dict, this means it should also
* be present in the main dict (safety check). */
redisAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);
// 返回过期时间
return dictGetSignedIntegerVal(de);
}
propagateExpire:将过期时间传播到附属节点和 AOF 文件。
void propagateExpire(redisDb *db, robj *key) {
robj *argv[2];
// 构造一个 DEL key 命令
argv[0] = shared.del;
argv[1] = key;
incrRefCount(argv[0]);
incrRefCount(argv[1]);
// 传播到 AOF
if (server.aof_state != REDIS_AOF_OFF)
feedAppendOnlyFile(server.delCommand,db->id,argv,2);
// 传播到所有附属节点
replicationFeedSlaves(server.slaves,db->id,argv,2);
decrRefCount(argv[0]);
decrRefCount(argv[1]);
}
expireIfNeeded:检查 key 是否已经过期,如果是的话,将它从数据库中删除。
int expireIfNeeded(redisDb *db, robj *key) {
// 取出键的过期时间
mstime_t when = getExpire(db,key);
mstime_t now;
// 没有过期时间
if (when < 0) return 0; /* No expire for this key */
/* Don't expire anything while loading. It will be done later. */
// 如果服务器正在进行载入,那么不进行任何过期检查
if (server.loading) return 0;
/* If we are in the context of a Lua script, we claim that time is
* blocked to when the Lua script started. This way a key can expire
* only the first time it is accessed and not in the middle of the
* script execution, making propagation to slaves / AOF consistent.
* See issue #1525 on Github for more information. */
now = server.lua_caller ? server.lua_time_start : mstime();
/* If we are running in the context of a slave, return ASAP:
* the slave key expiration is controlled by the master that will
* send us synthesized DEL operations for expired keys.
*
* Still we try to return the right information to the caller,
* that is, 0 if we think the key should be still valid, 1 if
* we think the key is expired at this time. */
// 当服务器运行在 replication 模式时
// 附属节点并不主动删除 key
// 它只返回一个逻辑上正确的返回值
// 真正的删除操作要等待主节点发来删除命令时才执行
// 从而保证数据的同步
if (server.masterhost != NULL) return now > when;
// 运行到这里,表示键带有过期时间,并且服务器为主节点
/* Return when this key has not expired */
// 如果未过期,返回 0
if (now <= when) return 0;
/* Delete the key */
server.stat_expiredkeys++;
// 向 AOF 文件和附属节点传播过期信息
propagateExpire(db,key);
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_EXPIRED,
"expired",key,db->id);
// 将过期键从数据库中删除
return dbDelete(db,key);
}