文章目录
1.lookupKey
//寻找指定的键,不存在返回NULL
robj *lookupKey(redisDb *db, robj *key) {
// 查找键空间
dictEntry *de = dictFind(db->dict,key->ptr);
// 节点存在
if (de) {
// 取出值
robj *val = dictGetVal(de);
// 更新时间信息(只在不存在子进程时执行,防止破坏 copy-on-write 机制)
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1)
val->lru = LRU_CLOCK();
// 返回值
return val;
} else {
// 节点不存在
return NULL;
}
}
2.lookupKeyRead
//查找键,还会查看是否过期,以及更新命中与不命中的信息。
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;
}
3.lookupKeyReadOrReply
//查找键,如果存在,返回键,不存在reply
robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply) {
// 查找
robj *o = lookupKeyRead(c->db, key);
// 决定是否发送信息
if (!o) addReply(c,reply);
return o;
}
4.addReply
void addReply(redisClient *c, robj *obj) {
// 为客户端安装写处理器到事件循环
if (prepareClientToWrite(c) != REDIS_OK) return;
// 首先尝试复制内容到 c->buf 中,这样可以避免内存分配
if (sdsEncodedObject(obj)) {
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
// 如果 c->buf 中的空间不够,就复制到 c->reply 链表中
// 可能会引起内存分配
_addReplyObjectToList(c,obj);
} else if (obj->encoding == REDIS_ENCODING_INT)
// 优化,如果 c->buf 中有等于或多于 32 个字节的空间
// 那么将整数直接以字符串的形式复制到 c->buf 中
if (listLength(c->reply) == 0 && (sizeof(c->buf) - c->bufpos) >= 32) {
char buf[32];
int len;
len = ll2string(buf,sizeof(buf),(long)obj->ptr);
if (_addReplyToBuffer(c,buf,len) == REDIS_OK)
return;
}
// 执行到这里,代表对象是整数,并且长度大于 32 位
// 将它转换为字符串
obj = getDecodedObject(obj);
// 保存到缓存中
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
_addReplyObjectToList(c,obj);
decrRefCount(obj);
} else {
redisPanic("Wrong obj->encoding in addReply()");
}
}
还有好几个addReply-xx的函数,基本都很类似,就不多赘述了
5.addReplyErrorFormat
void addReplyErrorFormat(redisClient *c, const char *fmt, ...) {
size_t l, j;
va_list ap;
va_start(ap,fmt);
sds s = sdscatvprintf(sdsempty(),fmt,ap);
va_end(ap);
//删除换行符,否则会影响协议的发送
l = sdslen(s);
for (j = 0; j < l; j++) {
if (s[j] == '\r' || s[j] == '\n') s[j] = ' ';
}
addReplyErrorLength(c,s,sdslen(s));
sdsfree(s);
}
6.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;
}
}
7.dbUnshareStringValue
//只有在引用数量小于等于1以及编码方式不是raw时才能直接修改,否则需要创建一个新的对象
robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
redisAssert(o->type == REDIS_STRING);
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
robj *decoded = getDecodedObject(o);
o = createRawStringObject(decoded->ptr, sdslen(decoded->ptr));
decrRefCount(decoded);
dbOverwrite(db,key,o);
}
return o;
}
8.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;
}
9.selectDb
//切换数据库
int selectDb(redisClient *c, int id) {
// 确保 id 在正确范围内
if (id < 0 || id >= server.dbnum)
return REDIS_ERR;
// 切换数据库(更新指针)
c->db = &server.db[id];
return REDIS_OK;
}
10.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);
}
11.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) {
// rdbSave() 会清空服务器的 dirty 属性
// 但为了确保 FLUSHALL 命令会被正常传播,
// 程序需要保存并在 rdbSave() 调用之后还原服务器的 dirty 属性
int saved_dirty = server.dirty;
rdbSave(server.rdb_filename);
server.dirty = saved_dirty;
}
server.dirty++;
}
12.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);
}
后边还有几个keycommand 基本类似,也就不说了
13.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);
}
14.scanGenericCommand
/*
* 这是 SCAN 、 HSCAN 、 SSCAN 命令的实现函数。
*
* If object 'o' is passed, then it must be a Hash or Set object, otherwise
* if 'o' is NULL the command will operate on the dictionary associated with
* the current database.
*
* 如果给定了对象 o ,那么它必须是一个哈希对象或者集合对象,
* 如果 o 为 NULL 的话,函数将使用当前数据库作为迭代对象。
*
* When 'o' is not NULL the function assumes that the first argument in
* the client arguments vector is a key so it skips it before iterating
* in order to parse options.
*
* 如果参数 o 不为 NULL ,那么说明它是一个键对象,函数将跳过这些键对象,
* 对给定的命令选项进行分析(parse)。
*
* In the case of a Hash object the function returns both the field and value
* of every element on the Hash.
*
* 如果被迭代的是哈希对象,那么函数返回的是键值对。
*/
void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) {
int rv;
int i, j;
char buf[REDIS_LONGSTR_SIZE];
list *keys = listCreate();
listNode *node, *nextnode;
long count = 10;
sds pat;
int patlen, use_pattern = 0;
dict *ht;
/* Object must be NULL (to iterate keys names), or the type of the object
* must be Set, Sorted Set, or Hash. */
// 输入类型检查,类型必须为集合,有序集合,和哈希表
redisAssert(o == NULL || o->type == REDIS_SET || o->type == REDIS_HASH ||
o->type == REDIS_ZSET);
/* Set i to the first option argument. The previous one is the cursor. */
// 设置第一个选项参数的索引位置
// 0 1 2 3
// SCAN OPTION <op_arg> SCAN 命令的选项值从索引 2 开始
// HSCAN <key> OPTION <op_arg> 而其他 *SCAN 命令的选项值从索引 3 开始
i = (o == NULL) ? 2 : 3; /* Skip the key argument if needed. */
/* Step 1: Parse options. */
// 第一步分析选项参数
while (i < c->argc) {
j = c->argc - i;
// COUNT <number>
if (!strcasecmp(c->argv[i]->ptr, "count") && j >= 2) {
if (getLongFromObjectOrReply (c, c->argv[i+1], &count, NULL)
!= REDIS_OK)
{
goto cleanup;
}
if (count < 1) {
addReply(c,shared.syntaxerr);
goto cleanup;
}
i += 2;
// MATCH <pattern>
} else if (!strcasecmp(c->argv[i]->ptr, "match") && j >= 2) {
pat = c->argv[i+1]->ptr;
patlen = sdslen(pat);
/* The pattern always matches if it is exactly "*", so it is
* equivalent to disabling it. */
//如果是*的话,就总是能够匹配
use_pattern = !(pat[0] == '*' && patlen == 1);
i += 2;
// error
} else {
addReply(c,shared.syntaxerr);
goto cleanup;
}
}
/* Step 2: Iterate the collection.
*第二部,迭代这个集合
* Note that if the object is encoded with a ziplist, intset, or any other
* representation that is not a hash table, we are sure that it is also
* composed of a small number of elements. So to avoid taking state we
* just return everything inside the object in a single call, setting the
* cursor to zero to signal the end of the iteration. */
// 如果对象的底层实现为 ziplist 、intset 而不是哈希表,
// 那么这些对象应该只包含了少量元素,
// 为了保持不让服务器记录迭代状态的设计
// 我们将 ziplist 或者 intset 里面的所有元素都一次返回给调用者
// 并向调用者返回游标(cursor) 0
/* Handle the case of a hash table. */
//处理哈希表
ht = NULL;
if (o == NULL) {
// 迭代目标为数据库
ht = c->db->dict;
} else if (o->type == REDIS_SET && o->encoding == REDIS_ENCODING_HT) {
// 迭代目标为 HT 编码的集合
ht = o->ptr;
} else if (o->type == REDIS_HASH && o->encoding == REDIS_ENCODING_HT) {
// 迭代目标为 HT 编码的哈希
ht = o->ptr;
count *= 2; /* We return key / value for this type. */
} else if (o->type == REDIS_ZSET && o->encoding == REDIS_ENCODING_SKIPLIST) {
// 迭代目标为 HT 编码的跳跃表
zset *zs = o->ptr;
ht = zs->dict;
count *= 2; /* We return key / value for this type. */
}
if (ht) {
void *privdata[2];
/* We pass two pointers to the callback: the list to which it will
* add new elements, and the object containing the dictionary so that
* it is possible to fetch more data in a type-dependent way. */
// 我们向回调函数传入两个指针:
// 一个是用于记录被迭代元素的列表
// 另一个是字典对象
// 从而实现类型无关的数据提取操作
privdata[0] = keys;
privdata[1] = o;
do {
cursor = dictScan(ht, cursor, scanCallback, privdata);
} while (cursor && listLength(keys) < count);
} else if (o->type == REDIS_SET) {
int pos = 0;
int64_t ll;
while(intsetGet(o->ptr,pos++,&ll))
listAddNodeTail(keys,createStringObjectFromLongLong(ll));
cursor = 0;
} else if (o->type == REDIS_HASH || o->type == REDIS_ZSET) {
unsigned char *p = ziplistIndex(o->ptr,0);
unsigned char *vstr;
unsigned int vlen;
long long vll;
while(p) {
ziplistGet(p,&vstr,&vlen,&vll);
listAddNodeTail(keys,
(vstr != NULL) ? createStringObject((char*)vstr,vlen) :
createStringObjectFromLongLong(vll));
p = ziplistNext(o->ptr,p);
}
cursor = 0;
} else {
redisPanic("Not handled encoding in SCAN.");
}
/* Step 3: Filter elements. */
//过滤元素
node = listFirst(keys);
while (node) {
robj *kobj = listNodeValue(node);
nextnode = listNextNode(node);
int filter = 0;
/* Filter element if it does not match the pattern. */
if (!filter && use_pattern) {
if (sdsEncodedObject(kobj)) {
if (!stringmatchlen(pat, patlen, kobj->ptr, sdslen(kobj->ptr), 0))
filter = 1;
} else {
char buf[REDIS_LONGSTR_SIZE];
int len;
redisAssert(kobj->encoding == REDIS_ENCODING_INT);
len = ll2string(buf,sizeof(buf),(long)kobj->ptr);
if (!stringmatchlen(pat, patlen, buf, len, 0)) filter = 1;
}
}
/* Filter element if it is an expired key. */
if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1;
/* Remove the element and its associted value if needed. */
if (filter) {
decrRefCount(kobj);
listDelNode(keys, node);
}
/* If this is a hash or a sorted set, we have a flat list of
* key-value elements, so if this element was filtered, remove the
* value, or skip it if it was not filtered: we only match keys. */
if (o && (o->type == REDIS_ZSET || o->type == REDIS_HASH)) {
node = nextnode;
nextnode = listNextNode(node);
if (filter) {
kobj = listNodeValue(node);
decrRefCount(kobj);
listDelNode(keys, node);
}
}
node = nextnode;
}
/* Step 4: Reply to the client. */
addReplyMultiBulkLen(c, 2);
rv = snprintf(buf, sizeof(buf), "%lu", cursor);
redisAssert(rv < sizeof(buf));
addReplyBulkCBuffer(c, buf, rv);
addReplyMultiBulkLen(c, listLength(keys));
while ((node = listFirst(keys)) != NULL) {
robj *kobj = listNodeValue(node);
addReplyBulk(c, kobj);
decrRefCount(kobj);
listDelNode(keys, node);
}
cleanup:
listSetFreeMethod(keys,decrRefCountVoid);
listRelease(keys);
}
15.moveCommand
void moveCommand(redisClient *c) {
robj *o;
redisDb *src, *dst;
int srcid;
if (server.cluster_enabled) {
addReplyError(c,"MOVE is not allowed in cluster mode");
return;
}
/* Obtain source and target DB pointers */
// 源数据库
src = c->db;
// 源数据库的 id
srcid = c->db->id;
// 切换到目标数据库
if (selectDb(c,atoi(c->argv[2]->ptr)) == REDIS_ERR) {
addReply(c,shared.outofrangeerr);
return;
}
// 目标数据库
dst = c->db;
// 切换回源数据库
selectDb(c,srcid); /* Back to the source DB */
/* If the user is moving using as target the same
* DB as the source DB it is probably an error. */
// 如果源数据库和目标数据库相等,那么返回错误
if (src == dst) {
addReply(c,shared.sameobjecterr);
return;
}
/* Check if the element exists and get a reference */
// 取出要移动的对象
o = lookupKeyWrite(c->db,c->argv[1]);
if (!o) {
addReply(c,shared.czero);
return;
}
/* Return zero if the key already exists in the target DB */
// 如果键已经存在于目标数据库,那么返回
if (lookupKeyWrite(dst,c->argv[1]) != NULL) {
addReply(c,shared.czero);
return;
}
// 将键添加到目标数据库中
dbAdd(dst,c->argv[1],o);
// 增加对对象的引用计数,避免接下来在源数据库中删除时 o 被清理
incrRefCount(o);
/* OK! key moved, free the entry in the source DB */
// 将键从源数据库中返回
dbDelete(src,c->argv[1]);
server.dirty++;
addReply(c,shared.cone);
}
16.expireIfNeeded
/*
* 检查 key 是否已经过期,如果是的话,将它从数据库中删除。
*
* 返回 0 表示键没有过期时间,或者键未过期。
*
* 返回 1 表示键已经因为过期而被删除了。
*/
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;
//如果执行EVA客户端,那么now = lua开始调用的时间
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);
}
17.expireGenicCommand
/* This is the generic command implementation for EXPIRE, PEXPIRE, EXPIREAT
* and PEXPIREAT. Because the commad second argument may be relative or absolute
* the "basetime" argument is used to signal what the base time is (either 0
* for *AT variants of the command, or the current time for relative expires).
*
* 这个函数是 EXPIRE 、 PEXPIRE 、 EXPIREAT 和 PEXPIREAT 命令的底层实现函数。
*
* 命令的第二个参数可能是绝对值,也可能是相对值。
* 当执行 *AT 命令时, basetime 为 0 ,在其他情况下,它保存的就是当前的绝对时间。
*
* unit is either UNIT_SECONDS or UNIT_MILLISECONDS, and is only used for
* the argv[2] parameter. The basetime is always specified in milliseconds.
*
* unit 用于指定 argv[2] (传入过期时间)的格式,
* 它可以是 UNIT_SECONDS 或 UNIT_MILLISECONDS ,
* basetime 参数则总是毫秒格式的。
*/
void expireGenericCommand(redisClient *c, long long basetime, int unit) {
robj *key = c->argv[1], *param = c->argv[2];
long long when; /* unix time in milliseconds when the key will expire. */
// 取出 when 参数
if (getLongLongFromObjectOrReply(c, param, &when, NULL) != REDIS_OK)
return;
// 如果传入的过期时间是以秒为单位的,那么将它转换为毫秒
if (unit == UNIT_SECONDS) when *= 1000;
when += basetime;
/* No key, return zero. */
// 取出键
if (lookupKeyRead(c->db,key) == NULL) {
addReply(c,shared.czero);
return;
}
/* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
* should never be executed as a DEL when load the AOF or in the context
* of a slave instance.
*
* 在载入数据时,或者服务器为附属节点时,
* 即使 EXPIRE 的 TTL 为负数,或者 EXPIREAT 提供的时间戳已经过期,
* 服务器也不会主动删除这个键,而是等待主节点发来显式的 DEL 命令。
*
* Instead we take the other branch of the IF statement setting an expire
* (possibly in the past) and wait for an explicit DEL from the master.
*
* 程序会继续将(一个可能已经过期的 TTL)设置为键的过期时间,
* 并且等待主节点发来 DEL 命令。
*/
if (when <= mstime() && !server.loading && !server.masterhost) {
// when 提供的时间已经过期,服务器为主节点,并且没在载入数据
robj *aux;
redisAssertWithInfo(c,key,dbDelete(c->db,key));
server.dirty++;
/* Replicate/AOF this as an explicit DEL. */
// 传播 DEL 命令
aux = createStringObject("DEL",3);
rewriteClientCommandVector(c,2,aux,key);
decrRefCount(aux);
signalModifiedKey(c->db,key);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",key,c->db->id);
addReply(c, shared.cone);
return;
} else {
// 设置键的过期时间
// 如果服务器为附属节点,或者服务器正在载入,
// 那么这个 when 有可能已经过期的
setExpire(c->db,key,when);
addReply(c,shared.cone);
signalModifiedKey(c->db,key);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"expire",key,c->db->id);
server.dirty++;
return;
}
}