Redis之数据库实现源码阅读

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);
}

猜你喜欢

转载自blog.csdn.net/m0_37343985/article/details/83715995
今日推荐