单步调试Redis源码——set key value方法

486 阅读4分钟

小编相信大家都或多或少用过redis,如果你没用过,那你是不是就得emo一会了,这么好用的东西都没接触过,小编只想说,你们公司还缺不缺人。

今晚这篇文章我们一起来了解一下,redis的set方法究竟是如何运行的,小编将带大家一起以redis的set key value方法为例单步调试一下redis(不会的偶尔也直接跳过)。

按惯例,先说调试工具

  • clion
  • redis6.0.1源码
  • 编译环境- cygwin
  • cmake

接下来就要开始卷了

注:set key value 具体执行的方法定义在t_string.c文件中。

首先我在客户端执行了 set flkey flvalue 方法。通过我们后端打的断点发现,执行具体set key value功能的方法是 void setGenericCommand。 如下图 (我们从具体的业务方法开始说,之前的socket连接传送具体命令都不属于具体的功能方法,就不提了,有想了解了可以私聊小编一起过几招)。注:小编在下面代码中做了注释。 在这里插入图片描述 在这里插入图片描述

void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
    long long milliseconds = 0; /* initialized to avoid any harmness warning */
    //如果通过set ex方法设置了超时时间
    if (expire) {
        //获取超时时间给milliseconds赋值
        if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != C_OK)
            return;
        //判断时间是否小于等于0
        if (milliseconds <= 0) {
            addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name);
            return;
        }
        //时间单位转换
        if (unit == UNIT_SECONDS) milliseconds *= 1000;
    }
    // 校验:   nx 不能互斥设值, xx 需要有值 
    if ((flags & OBJ_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
        (flags & OBJ_SET_XX && lookupKeyWrite(c->db,key) == NULL))
    {
        addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
        return;
    }
    //设置key value值
    genericSetKey(c,c->db,key,val,flags & OBJ_SET_KEEPTTL,1);
    server.dirty++;
    //设置超时时间
    if (expire) setExpire(c,c->db,key,mstime()+milliseconds);
    //对订阅了事件set的客户端进行通知
    notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
    //如果设置了超时时间,对订阅了事件expire的客户端进行通知
    if (expire) notifyKeyspaceEvent(NOTIFY_GENERIC,
        "expire",key,c->db->id);
    addReply(c, ok_reply ? ok_reply : shared.ok);
}

接下来我们主要看genericSetKey方法

void genericSetKey(client *c, redisDb *db, robj *key, robj *val, int keepttl, int signal) {
    //当前redis中不存在要插入的key
    if (lookupKeyWrite(db,key) == NULL) {
        //添加key val
        dbAdd(db,key,val);
    } else {
        //覆盖key val
        dbOverwrite(db,key,val);
    }
    //增加对val的引用计数
    incrRefCount(val);
    //保留设置前指定key的过期时间,6.0版本之后新增的参数
    if (!keepttl) removeExpire(db,key);
    if (signal) signalModifiedKey(c,db,key);
}

由于我们的执行的key是不存在的,所以这个时候走dbAdd方法,截图为证 在这里插入图片描述 接下来我们走到dbAdd方法里看一看

void dbAdd(redisDb *db, robj *key, robj *val) {
    //ptr是key字符串的内存首地址指针,将其封装成一个sds对象
    sds copy = sdsdup(key->ptr);
    //添加key val
    int retval = dictAdd(db->dict, copy, val);
    //通过宏验证是否添加成功,如果失败直接_exit(1)
    serverAssertWithInfo(NULL,key,retval == DICT_OK);
    //这里返回给客户端通知先不管,和本次文章的字符串类型不相关
    if (val->type == OBJ_LIST ||
        val->type == OBJ_ZSET ||
        val->type == OBJ_STREAM)
        signalKeyAsReady(db, key);
    //如果是集群部署,将命令发送给其他节点(slot)
    if (server.cluster_enabled) slotToKeyAdd(key->ptr);
}

这个dbAdd方法主要就是通过key对应的指针首地址创建一个sds对象。然后将key对象与value值插入到hash表中。

sds:是redis作者自己写的一个字符串数据结构(简单动态字符串),小编猜测,应该是redis整体是用c语言写的,二c语言中是没有字符串这个数据结构的,如果作者不自己封装一个sds,那就得用c语言中的char[]表示一个字符串。而且char[]用起来也很不方便,要自己记录长度,如果遇到字符串结尾还得自己添加\0,并且对于redis这种内存存储的系统,要尽量做到节省内存,就更不能直接用char数组了。 hash表:redis里定义为dict结构体。其实就是一个哈希表结构。

typedef struct dict {
    dictEntry **table;
    dictType *type;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
    void *privdata;
} dict;

typedef struct dictEntry {
    void *key;
    void *val;
    struct dictEntry *next;
} dictEntry;

接下来就是往hash表中(dict )添加具体的元素

/* 往hash表中添加元素 */
int dictAdd(dict *d, void *key, void *val)
{
    //这里会为要插入的key申请内存和rehash等操作,都是一些hash表基本的操作 就不看了
    dictEntry *entry = dictAddRaw(d,key,NULL);

    if (!entry) return DICT_ERR;
    //set val
    dictSetVal(d, entry, val);
    return DICT_OK;
}

#define dictSetVal(d, entry, _val_) do { \
    if ((d)->type->valDup) \
        (entry)->v.val = (d)->type->valDup((d)->privdata, _val_); \
    else \
        (entry)->v.val = (_val_); \
} while(0)

redis中set方法 插入一个字符串key val的主流程大概就是这些,大家学废了吗?当然后面添加方法返回后还有一些集群化的代码,我这里是本地起的单机服务,没有走这些代码,就不介绍了。有兴趣的可以私聊小编一起卷。

问君能有几多愁,恰似满屏代码加需求

对一些新技术感兴趣的可以关注公众号:云下风澜