Redis数据库核心:annotated_redis_source中db.c模块的完整分析

Redis数据库核心:annotated_redis_source中db.c模块的完整分析

【免费下载链接】annotated_redis_source 带有详细注释的 Redis 2.6 源码 【免费下载链接】annotated_redis_source 项目地址: https://gitcode.com/gh_mirrors/an/annotated_redis_source

Redis作为高性能的内存数据库,其核心功能实现依赖于多个关键模块。在带有详细注释的Redis 2.6源码项目annotated_redis_source中,src/db.c模块承担着数据库核心操作的重任,包括键值对管理、过期策略、命令处理等关键功能。本文将深入剖析这一模块的实现细节,帮助开发者理解Redis数据库的底层工作原理。

一、db.c模块的核心功能概览

src/db.c模块是Redis数据库层的核心实现,主要负责以下功能:

  • 键值对管理:实现键的查找、添加、删除、更新等基础操作
  • 过期策略:处理键的过期时间设置与自动清理
  • 数据库操作:支持切换数据库、清空数据库等管理功能
  • 命令实现:提供DEL、EXISTS、TYPE等核心命令的处理逻辑

该模块通过与字典(dict)数据结构紧密配合,实现了高效的键值存储与访问。代码中大量使用了Redis自定义的robj(Redis对象)结构,为不同类型的数据提供统一的操作接口。

二、键值对管理的核心实现

2.1 键查找机制

lookupKey函数是数据库查找操作的基础,其实现如下:

robj *lookupKey(redisDb *db, robj *key) {
    dictEntry *de = dictFind(db->dict,key->ptr);
    if (de) {
        robj *val = dictGetVal(de);
        if (server.rdb_child_pid == -1 && server.aof_child_pid == -1)
            val->lru = server.lruclock;
        return val;
    } else {
        return NULL;
    }
}

该函数通过调用dictFind从数据库的字典结构中查找键,平均时间复杂度为O(1)。值得注意的是,当没有子进程执行RDB或AOF持久化时,函数会更新值对象的LRU时间,这是Redis内存淘汰机制的基础。

2.2 键的添加与更新

dbAdddbOverwrite函数分别处理新键添加和旧键更新:

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

void dbOverwrite(redisDb *db, robj *key, robj *val) {
    struct dictEntry *de = dictFind(db->dict,key->ptr);
    redisAssertWithInfo(NULL,key,de != NULL);
    dictReplace(db->dict, key->ptr, val);
}

dbAdd负责添加新键,会复制键的字符串表示并调用dictAdd将键值对存入字典。dbOverwrite则用于更新已有键的值,通过dictReplace实现原子性替换。在集群模式下,还会调用SlotToKeyAdd将键添加到对应的哈希槽。

2.3 键删除操作

dbDelete函数处理键的删除,同时清理过期时间记录:

int dbDelete(redisDb *db, robj *key) {
    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;
    }
}

函数首先从过期字典中删除键的过期记录,然后从主字典中删除键值对。返回值表示删除操作是否成功执行。

三、过期策略的实现机制

3.1 过期时间管理

Redis通过expires字典单独存储键的过期时间,setExpiregetExpire函数分别负责设置和获取过期时间:

void setExpire(redisDb *db, robj *key, long long when) {
    dictEntry *kde, *de;
    kde = dictFind(db->dict,key->ptr);
    redisAssertWithInfo(NULL,key,kde != NULL);
    de = dictReplaceRaw(db->expires,dictGetKey(kde));
    dictSetSignedIntegerVal(de,when);
}

long long getExpire(redisDb *db, robj *key) {
    dictEntry *de;
    if (dictSize(db->expires) == 0 ||
       (de = dictFind(db->expires,key->ptr)) == NULL) return -1;
    redisAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);
    return dictGetSignedIntegerVal(de);
}

过期时间以毫秒级Unix时间戳存储,这种设计使得过期检查非常高效。

3.2 过期键的删除策略

expireIfNeeded函数实现了Redis的惰性删除策略:

int expireIfNeeded(redisDb *db, robj *key) {
    long long when = getExpire(db,key);
    if (when < 0) return 0;
    if (server.loading) return 0;
    if (server.masterhost != NULL) {
        return mstime() > when;
    }
    if (mstime() <= when) return 0;
    server.stat_expiredkeys++;
    propagateExpire(db,key);
    return dbDelete(db,key);
}

该函数在每次访问键时被调用,只有当键确实过期时才会执行删除操作。对于主从架构,过期删除只在主节点执行,然后通过DEL命令同步到从节点,保证数据一致性。

四、核心命令的实现分析

4.1 DEL命令

delCommand函数实现了删除一个或多个键的功能:

void delCommand(redisClient *c) {
    int deleted = 0, j;
    for (j = 1; j < c->argc; j++) {
        if (dbDelete(c->db,c->argv[j])) {
            signalModifiedKey(c->db,c->argv[j]);
            server.dirty++;
            deleted++;
        }
    }
    addReplyLongLong(c,deleted);
}

函数遍历所有输入键,调用dbDelete进行删除,并通过signalModifiedKey通知相关模块(如发布订阅、持久化)键已被修改。

4.2 KEYS命令

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

该实现使用字典迭代器遍历所有键,通过stringmatchlen函数进行模式匹配,并过滤掉已过期的键。需要注意的是,在大数据量下使用KEYS命令可能会阻塞Redis服务器,生产环境中建议使用SCAN命令替代。

4.3 EXPIRE命令族

expireGenericCommand是EXPIRE、PEXPIRE等命令的通用实现:

void expireGenericCommand(redisClient *c, long long basetime, int unit) {
    robj *key = c->argv[1], *param = c->argv[2];
    long long when;
    if (getLongLongFromObjectOrReply(c, param, &when, NULL) != REDIS_OK)
        return;
    if (unit == UNIT_SECONDS) when *= 1000;
    when += basetime;
    if (dictFind(c->db->dict,key->ptr) == NULL) {
        addReply(c,shared.czero);
        return;
    }
    if (when <= mstime() && !server.loading && !server.masterhost) {
        // 处理已过期的情况,直接删除键
        redisAssertWithInfo(c,key,dbDelete(c->db,key));
        server.dirty++;
        // 生成DEL命令进行传播
        // ...
        addReply(c, shared.cone);
        return;
    } else {
        setExpire(c->db,key,when);
        addReply(c,shared.cone);
        signalModifiedKey(c->db,key);
        server.dirty++;
        return;
    }
}

该函数统一处理相对时间和绝对时间的过期设置,当设置的过期时间已过时,会直接删除键并生成DEL命令进行传播,保证主从数据一致性。

五、数据库管理功能

5.1 数据库切换

selectDb函数实现了数据库切换功能:

int selectDb(redisClient *c, int id) {
    if (id < 0 || id >= server.dbnum)
        return REDIS_ERR;
    c->db = &server.db[id];
    return REDIS_OK;
}

Redis支持多个数据库(默认16个),通过客户端状态中的db指针实现快速切换。在集群模式下,SELECT命令被禁用,因为集群模式下数据库编号必须为0。

5.2 数据库清空

flushdbCommandflushallCommand分别实现清空当前数据库和所有数据库的功能:

void flushdbCommand(redisClient *c) {
    server.dirty += dictSize(c->db->dict);
    signalFlushedDb(c->db->id);
    dictEmpty(c->db->dict);
    dictEmpty(c->db->expires);
    addReply(c,shared.ok);
}

void flushallCommand(redisClient *c) {
    signalFlushedDb(-1);
    server.dirty += emptyDb();
    addReply(c,shared.ok);
    // 处理持久化相关逻辑
    // ...
}

清空操作通过调用dictEmpty实现,同时更新dirty计数器,通知持久化模块有大量数据被修改。

六、集群相关功能

在Redis集群模式下,db.c模块还提供了哈希槽与键的映射管理:

void SlotToKeyAdd(robj *key) {
    unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr));
    zslInsert(server.cluster.slots_to_keys,hashslot,key);
    incrRefCount(key);
}

unsigned int GetKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count) {
    zskiplistNode *n;
    zrangespec range;
    int j = 0;
    range.min = range.max = hashslot;
    range.minex = range.maxex = 0;
    n = zslFirstInRange(server.cluster.slots_to_keys, range);
    while(n && n->score == hashslot && count--) {
        keys[j++] = n->obj;
        n = n->level[0].forward;
    }
    return j;
}

这些函数通过有序集合维护键与哈希槽的映射关系,为集群数据迁移提供支持。GetKeysInSlot可以快速获取指定槽位中的键,是集群重新分片的关键功能。

七、总结与最佳实践

src/db.c模块作为Redis数据库的核心,实现了高效的键值管理、灵活的过期策略和丰富的数据库操作命令。通过深入理解这一模块的实现,开发者可以:

  1. 优化内存使用:理解LRU机制和过期策略,合理设计键的过期时间
  2. 提升性能:避免使用KEYS等阻塞命令,采用SCAN等迭代命令替代
  3. 集群设计:理解哈希槽映射机制,优化数据分布
  4. 故障排查:通过理解命令执行流程,快速定位问题

Redis的设计哲学在db.c模块中得到了充分体现:简洁高效的代码、清晰的模块划分、兼顾性能与功能的实现。对于希望深入理解Redis内部机制的开发者来说,仔细研读src/db.c代码是非常有价值的学习过程。

要获取完整的带注释Redis源码,可以通过以下命令克隆项目:

git clone https://gitcode.com/gh_mirrors/an/annotated_redis_source

通过研究这些注释丰富的源码,开发者可以更深入地理解Redis的设计思想和实现细节,为高性能Redis应用开发和优化奠定基础。

【免费下载链接】annotated_redis_source 带有详细注释的 Redis 2.6 源码 【免费下载链接】annotated_redis_source 项目地址: https://gitcode.com/gh_mirrors/an/annotated_redis_source

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值