redis源码之set命令解析(8)

1.背景

源码是redis5版本,在笔记7中,找到了命令数组redisCommandTable
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bmjJksO4-1653131380149)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/f7b1abd6-5b1c-48bc-a733-473922b31c21/Untitled.png)]

先从最简单的set命令开始看,如何进行最简单的键值对进行set。


2.setCommand

setCommand命令实现是在t_string.c文件中。

这里主要是处理set if exist ,set if not exist,set expire 这三种情况。

另外对set命令的value进行redisObject 对象构建。

/* SET key value [NX] [XX] [EX <seconds>] [PX <milliseconds>] */
void setCommand(client *c) {
    int j;
    robj *expire = NULL;
    int unit = UNIT_SECONDS;
    // 处理标识
    /**
     * OBJ_SET_NO_FLAGS 0      啥也不带 就是最基础的set key value
     * OBJ_SET_NX (1<<0)      当key不存在的时候才设置
     * OBJ_SET_XX (1<<1)      当key存在的时候才设置
     * OBJ_SET_EX (1<<2)     带过期时间 单位是秒
     * OBJ_SET_PX (1<<3)     带过期时间 单位毫秒
     */
    int flags = OBJ_SET_NO_FLAGS;
		
    for (j = 3; j < c->argc; j++) {
        // ---- 省略对exist和expire的额外参数解读----
    }
    /**
     * typedef struct redisObject {
      * unsigned type:4;
      * unsigned encoding:4;
      * unsigned lru:LRU_BITS;
      *  int refcount;
      *  void *ptr;
      * } robj;
     */
    // 对值进行字符串编码,看是通过OBJ_ENCODING_EMBSTR 进行构建还是通过 OBJ_ENCODING_RAW
    c->argv[2] = tryObjectEncoding(c->argv[2]);
		// 进行set命令执行
    setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
}

3.setGenericCommand

  1. 过期时间校验
  2. 针对set if exist 和 set if not exist的处理
  3. 向db添加键值对
  4. 向db添加键的过期时间
/**
 *
 * @param c 客户端对象
 * @param flags 对exist的判断和对过期时间的使用标识
 * @param key 键
 * @param val 值
 * @param expire 过期时间
 * @param unit 过期时间单位
 * @param ok_reply 成功响应
 * @param abort_reply 中止响应
 */
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 */

    if (expire) {
        // 过期时间读取与校验
        if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != C_OK)
            return;
        if (milliseconds <= 0) {
            addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name);
            return;
        }
        if (unit == UNIT_SECONDS) milliseconds *= 1000;
    }
    // 针对OBJ_SET_NX 与 OBJ_SET_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.nullbulk);
        return;
    }
    // 设置键值对
    setKey(c->db,key,val);
    // 修改+1
    server.dirty++;
    // 设置过期时间
    if (expire) setExpire(c,c->db,key,mstime()+milliseconds);
    notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
    if (expire) notifyKeyspaceEvent(NOTIFY_GENERIC,
        "expire",key,c->db->id);
    addReply(c, ok_reply ? ok_reply : shared.ok);
}

4.setKey 键值对设置

  1. 先查key是不是存在
  2. 再新增或者覆盖
  3. 值引用计数
  4. 移除key的过期时间
void setKey(redisDb *db, robj *key, robj *val) {
	//根据key在hash表中找元素,没找到就新增,找到了就覆盖。
    if (lookupKeyWrite(db,key) == NULL) {
    	//新增
        dbAdd(db,key,val);
    }else {
        // 覆盖
        dbOverwrite(db,key,val);
    }
    // 值的引用计数统计
    incrRefCount(val);
    // 移除过期key
    removeExpire(db,key);
    // 标记key的值被修改
    signalModifiedKey(db,key);
}

5.lookupKeyWrite

robj *lookupKeyWrite(redisDb *db, robj *key) {
		// 如果key已过期,进行过期处理
    expireIfNeeded(db,key);
		// 去数据库里找key
    return lookupKey(db,key,LOOKUP_NONE);
}

<br/>

6.lookupKey 在db中查找指定key的值

去db中找到键值对所在的字典,然后通过key去字典中找到键值对所在的索引位取出链表进行比较并返回键一致的字典元素对象dictEntry。

这里其实就需要字典相关知识了,在下一篇笔记中,将从redisDb数据库对象的创建引入到dict的使用。也就是字典数据结构dict的使用。

robj *lookupKey(redisDb *db, robj *key, int flags) {
		// 找到键值对
    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. */
        if (server.rdb_child_pid == -1 &&
            server.aof_child_pid == -1 &&
            !(flags & LOOKUP_NOTOUCH))
        {
            if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
								// 
                updateLFU(val);
            } else {
                val->lru = LRU_CLOCK();
            }
        }
        return val;
    } else {
        return NULL;
    }
}

7.小结

其实setCommand命令相对来说还是比较容易理解。主要是在字典方面的操作。

  1. 其实就是redisDb中找到键值对数据字典dict
  2. 在键值对数据字典中通过是否rehash来判断使用的是哪一个dictht
  3. 从dictht中再通过key的hash值去定位dictEntry
  4. dictEntry是个单链表,通过链表表头向后查找到指定key的dictEntry进行返回
  5. 然后就是判断dictEntry是否存在,存在则覆写值,不存在则新增。
  6. dictEntry使用的是头插法,新俩的会在链表表头(简单不用遍历到链表尾部)

上面其实是键值对类型结构的通用流程。不同的是针对于不同的值结构,采用不同的值结构的覆盖和新增方法。
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值