Redis 6.2当发送set String时,redis是如何处理的(源码)

下面主要是源码,标题也都是各个方法,所以不建议跳着看,而是从头开始看会,

setCommand

server.c中的redisCommand redisCommandTable[],里面有很多命令行操作的命令
这里只看set String

{"set",setCommand,-3,
 "write use-memory @string",
 0,NULL,1,1,1,0,0,0},

t_string.c中就有setCommand方法

/* SET key value [NX] [XX] [KEEPTTL] [GET] [EX <seconds>] [PX <milliseconds>]
 *     [EXAT <seconds-timestamp>][PXAT <milliseconds-timestamp>] */
void setCommand(client *c) {
    robj *expire = NULL;
    int unit = UNIT_SECONDS;
    int flags = OBJ_NO_FLAGS;

    if (parseExtendedStringArgumentsOrReply(c,&flags,&unit,&expire,COMMAND_SET) != C_OK) {
        return;
    }

    c->argv[2] = tryObjectEncoding(c->argv[2]);
//执行set操作的实际方法  setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
}

tryObjectEncoding(尝试对字符串对象进行编码以节省空间)

/* Try to encode a string object in order to save space
尝试对字符串对象进行编码以节省空间*/
robj *tryObjectEncoding(robj *o) {
    long value;
    sds s = o->ptr;
    size_t len;

    /* Make sure this is a string object, the only type we encode
     * in this function. Other types use encoded memory efficient
     * representations but are handled by the commands implementing
     * the type.
     *确保这是一个字符串对象,我们在这个函数中编码的唯一类型。其他类型使用编码内存高效表示,但由实现该类型的命令处理*/
    serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);

    /* We try some specialized encoding only for objects that are
     * RAW or EMBSTR encoded, in other words objects that are still
     * in represented by an actually array of chars.
     我们仅针对 RAW 或 EMBSTR 编码的对象尝试一些专门的编码,换句话说,仍然由实际的字符数组表示的对象*/
    if (!sdsEncodedObject(o)) return o;

    /* It's not safe to encode shared objects: shared objects can be shared
     * everywhere in the "object space" of Redis and may end in places where
     * they are not handled. We handle them only as values in the keyspace.
     对共享对象进行编码是不安全的:共享对象可以在 Redis 的“对象空间”中的任何地方共享,
     并且可能在它们未被处理的地方结束。我们仅将它们作为键空间中的值来处理*/
     if (o->refcount > 1) return o;

    /* Check if we can represent this string as a long integer.
     * Note that we are sure that a string larger than 20 chars is not
     * representable as a 32 nor 64 bit integer.
     检查我们是否可以将此字符串表示为长整数。请注意,我们确信大于 20 个字符的字符串不能表示为 32 位或 64 位整数*/
    len = sdslen(s);
    if (len <= 20 && string2l(s,len,&value)) {
        /* This object is encodable as a long. Try to use a shared object.
         * Note that we avoid using shared integers when maxmemory is used
         * because every object needs to have a private LRU field for the LRU
         * algorithm to work well.
         此对象可编码为 long。尝试使用共享对象。请注意,当使用 maxmemory 时,我们避免使用共享整数,
         因为每个对象都需要有一个私有的 LRU 字段才能使 LRU 算法正常工作*/
        if ((server.maxmemory == 0 ||
            !(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
            value >= 0 &&
            value < OBJ_SHARED_INTEGERS)
        {
            decrRefCount(o);
            incrRefCount(shared.integers[value]);
            return shared.integers[value];
        } else {
            //raw
            if (o->encoding == OBJ_ENCODING_RAW) {
                sdsfree(o->ptr);
                o->encoding = OBJ_ENCODING_INT;
                o->ptr = (void*) value;
                return o;

            } else if (o->encoding == OBJ_ENCODING_EMBSTR) {//embstr
                decrRefCount(o);
                return createStringObjectFromLongLongForValue(value);
            }
        }
    }

    /* If the string is small and is still RAW encoded,
     * try the EMBSTR encoding which is more efficient.
     * In this representation the object and the SDS string are allocated
     * in the same chunk of memory to save space and cache misses.
     如果字符串很小并且仍然是 RAW 编码,请尝试更有效的 EMBSTR 编码。在这种表示中,对象和 SDS 字符串被分配在同一块内存中,以节省空间和缓存未命中。
     */
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {
        robj *emb;

        if (o->encoding == OBJ_ENCODING_EMBSTR) return o;
        emb = createEmbeddedStringObject(s,sdslen(s));
        decrRefCount(o);
        return emb;
    }

    /* We can't encode the object...
     *我们无法对对象进行编码...
     * Do the last try, and at least optimize the SDS string inside
     * the string object to require little space, in case there
     * is more than 10% of free space at the end of the SDS string.
     *做最后一次尝试,至少优化字符串对象内部的 SDS 字符串,使其占用空间很小,以防 SDS 字符串末尾有超过 10% 的可用空间。
     * We do that only for relatively large strings as this branch
     * is only entered if the length of the string is greater than
     * OBJ_ENCODING_EMBSTR_SIZE_LIMIT.
     *我们仅对相对较大的字符串执行此操作,因为仅当字符串的长度大于 OBJ_ENCODING_EMBSTR_SIZE_LIMIT 时才会进入此分支
     */
    trimStringObjectIfNeeded(o);

    /* Return the original object.
     返回原始对象*/
    return o;
}

setGenericCommand(set 字符串实际的执行流程)

/* The setGenericCommand() function implements the SET operation with different
 * options and variants. This function is called in order to implement the
 * following commands: SET, SETEX, PSETEX, SETNX, GETSET.
 *setGenericCommand() 函数以不同的方式实现 SET 操作 选项和变体。调用此函数是为了实现以下命令:SET、SETEX、PSETEX、SETNX、GETSET

 * 'flags' changes the behavior of the command (NX, XX or GET, see below).
 * 'flags' 改变命令的行为(NX、XX 或 GET,见下文)。

 * 'expire' represents an expire to set in form of a Redis object as passed
 * by the user. It is interpreted according to the specified 'unit'.
 *'expire' 表示要以用户传递的 Redis 对象的形式设置的过期时间。它根据指定的“单位”进行解释

 * 'ok_reply' and 'abort_reply' is what the function will reply to the client
 * if the operation is performed, or when it is not because of NX or
 * XX flags.
 *'ok_reply' 和 'abort_reply' 是函数在执行操作或不是因为 NX 或 XX 标志时将回复客户端的内容

 * If ok_reply is NULL "+OK" is used. 如果 ok_reply 为 NULL,则使用“+OK”
 * If abort_reply is NULL, "$-1" is used.  如果 abort_reply 为 NULL,则使用“-1”。*/

#define OBJ_NO_FLAGS 0
#define OBJ_SET_NX (1<<0)          /* Set if key not exists. 如果键不存在则 set*/
#define OBJ_SET_XX (1<<1)          /* Set if key exists. 如果键存在则set*/
#define OBJ_EX (1<<2)              /* Set if time in seconds is given 设置是否给出以秒为单位的时间*/
#define OBJ_PX (1<<3)              /* Set if time in ms in given *设置是否在给定时间以毫秒为单位/
#define OBJ_KEEPTTL (1<<4)         /* Set and keep the ttl */
#define OBJ_SET_GET (1<<5)         /* Set if want to get key before set 如果想在设置之前获取密钥,请设置*/
#define OBJ_EXAT (1<<6)            /* Set if timestamp in second is given 如果给出以秒为单位的时间戳,则设置*/
#define OBJ_PXAT (1<<7)            /* Set if timestamp in ms is given 如果给出以毫秒为单位的时间戳,则设置*/
#define OBJ_PERSIST (1<<8)         /* Set if we need to remove the ttl 设置我们是否需要删除 ttl*/

void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
    long long milliseconds = 0, when = 0; /* initialized to avoid any harmness warning 初始化以避免任何危害警告*/
    //转换过期时间
    if (expire) {
        if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != C_OK)
            return;
        if (milliseconds <= 0 || (unit == UNIT_SECONDS && milliseconds > LLONG_MAX / 1000)) {
            /* Negative value provided or multiplication is gonna overflow. */
            addReplyErrorFormat(c, "invalid expire time in %s", c->cmd->name);
            return;
        }
        if (unit == UNIT_SECONDS) milliseconds *= 1000;
        when = milliseconds;
        if ((flags & OBJ_PX) || (flags & OBJ_EX))
            when += mstime();
        if (when <= 0) {
            /* Overflow detected. */
            addReplyErrorFormat(c, "invalid expire time in %s", c->cmd->name);
            return;
        }
    }
    //key存在,则返回失败,符合NX语义,key不存在,返回失败,这符合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;
    }

    if (flags & OBJ_SET_GET) {
        if (getGenericCommand(c) == C_ERR) return;
    }
    //核心set指令的操作
    genericSetKey(c,c->db,key, val,flags & OBJ_KEEPTTL,1);
    server.dirty++;
    notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
    if (expire) {
        setExpire(c,c->db,key,when);
        notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);

        /* Propagate as SET Key Value PXAT millisecond-timestamp if there is EXAT/PXAT or
         * propagate as SET Key Value PX millisecond if there is EX/PX flag.
         *如果有 EXATPXAT,则作为 SET Key Value PXAT 毫秒时间戳传播,如果有 EXPX 标志,则作为 SET Key Value PX 毫秒传播
         * Additionally when we propagate the SET with PX (relative millisecond) we translate
         * it again to SET with PXAT for the AOF.
         *此外,当我们使用 PX(相对毫秒)传播 SET 时,我们再次将其转换为带有 PXAT 的 SET 用于 AOF
         * Additional care is required while modifying the argument order. AOF relies on the
         * exp argument being at index 3. (see feedAppendOnlyFile)
           修改参数顺序时需要额外小心。 AOF 依赖于索引 3 处的 exp 参数。(参见 feedAppendOnlyFile)
         * */
        robj *exp = (flags & OBJ_PXAT) || (flags & OBJ_EXAT) ? shared.pxat : shared.px;
        robj *millisecondObj = createStringObjectFromLongLong(milliseconds);
        rewriteClientCommandVector(c,5,shared.set,key,val,exp,millisecondObj);
        decrRefCount(millisecondObj);
    }
    if (!(flags & OBJ_SET_GET)) {
        addReply(c, ok_reply ? ok_reply : shared.ok);
    }

    /* Propagate without the GET argument (Isn't needed if we had expire since in that case we completely re-written the command argv)
        在没有 GET 参数的情况下传播(如果我们已经过期则不需要,因为在这种情况下我们完全重写了命令 argv)
    */
    if ((flags & OBJ_SET_GET) && !expire) {
        int argc = 0;
        int j;
        robj **argv = zmalloc((c->argc-1)*sizeof(robj*));
        for (j=0; j < c->argc; j++) {
            char *a = c->argv[j]->ptr;
            /* Skip GET which may be repeated multiple times. */
            if (j >= 3 &&
                (a[0] == 'g' || a[0] == 'G') &&
                (a[1] == 'e' || a[1] == 'E') &&
                (a[2] == 't' || a[2] == 'T') && a[3] == '\0')
                continue;
            argv[argc++] = c->argv[j];
            incrRefCount(c->argv[j]);
        }
        replaceClientCommandVector(c, argc, argv);
    }
}

genericSetKey(核心set指令的操作)
/* High level Set operation. This function can be used in order to set
 * a key, whatever it was existing or not, to a new object.
 * 高等级设置操作。此函数可用于将键设置为新对象,无论它是否存在
 * 1) The ref count of the value object is incremented.
    值对象的引用计数递增
 * 2) clients WATCHing for the destination key notified.
    客户端正在监视通知的目标密钥
 * 3) The expire time of the key is reset (the key is made persistent),
 *    unless 'keepttl' is true.
 *  重置密钥的过期时间(使密钥持久化),除非 'keepttl' 为真
 * All the new keys in the database should be created via this interface.
 * The client 'c' argument may be set to NULL if the operation is performed
 * in a context where there is no clear client performing the operation. */
void genericSetKey(client *c, redisDb *db, robj *key, robj *val, int keepttl, int signal) {
    //lookupKeyWrite 先尝试判断key是否过期,过期则删除
    if (lookupKeyWrite(db,key) == NULL) {
       //添加新键和值
        dbAdd(db,key,val);
    } else {
    //说明key存在而且没有过期,覆盖老的value,但是不会修改key过期时间
        dbOverwrite(db,key,val);
    }
    //这里对value的引用计算++,保证val不会被释放
    incrRefCount(val);
    //移除超时时间,因为setGenericCommand会重新设置超时时间,
    //考虑一种情况,上面的覆盖旧val,没有修改新的超过时间,可能刚覆盖后key就过期了
    if (!keepttl) removeExpire(db,key);
    if (signal) signalModifiedKey(c,db,key);
}


lookupKeyWrite(先尝试判断key是否过期,过期则删除)
robj *lookupKeyWrite(redisDb *db, robj *key) {
    return lookupKeyWriteWithFlags(db, key, LOOKUP_NONE);
}
/* Lookup a key for write operations, and as a side effect, if needed, expires
 * the key if its TTL is reached.
 *查找写操作的键,如果需要,如果达到 TTL,则使key过期。。
 * Returns the linked value object if the key exists or NULL if the key
 * does not exist in the specified DB. */
robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {
    expireIfNeeded(db,key);
    return lookupKey(db,key,flags);
}

lookupKey(查找key)
/* Low level key lookup API, not actually called directly from commands
 * implementations that should instead rely on lookupKeyRead(),
 * lookupKeyWrite() and lookupKeyReadWithFlags().
    低级查找 API,实际上并不直接从命令实现中直接调用,而是应该依赖 lookupKeyRead()、lookupKeyWrite() 和 lookupKeyReadWithFlags()。
 */
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 (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){
            if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
                updateLFU(val);
            } else {
                val->lru = LRU_CLOCK();
            }
        }
        return val;
    } else {
        return NULL;
    }
}

dbAdd(正常添加键值对)
/* Add the key to the DB. It's up to the caller to increment the reference
 * counter of the value if needed.
 *
 * The program is aborted if the key already exists. */
void dbAdd(redisDb *db, robj *key, robj *val) {
    sds copy = sdsdup(key->ptr);
    //正常dict添加操作,hash添加值
    int retval = dictAdd(db->dict, copy, val);

    serverAssertWithInfo(NULL,key,retval == DICT_OK);
    signalKeyAsReady(db, key, val->type);
    if (server.cluster_enabled) slotToKeyAdd(key->ptr);
}

dbOverwrite(覆盖旧的value,但是不修改过期时间)

因为在调用dbOverwrite方法之后,会有重置超时时间的操作

/* Overwrite an existing key with a new value. Incrementing the reference
 * count of the new value is up to the caller.
 * This function does not modify the expire time of the existing key.
 *  用新值覆盖现有键。增加新值的引用计数取决于调用者。此函数不会修改现有键的过期时间
 * The program is aborted if the key was not already present.
  如果密钥不存在,程序将中止
  */
void dbOverwrite(redisDb *db, robj *key, robj *val) {
    dictEntry *de = dictFind(db->dict,key->ptr);

    serverAssertWithInfo(NULL,key,de != NULL);
    dictEntry auxentry = *de;
    robj *old = dictGetVal(de);
    if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
        val->lru = old->lru;
    }
    /* Although the key is not really deleted from the database, we regard
    overwrite as two steps of unlink+add, so we still need to call the unlink
    callback of the module. */
    moduleNotifyKeyUnlink(key,old);
    dictSetVal(db->dict, de, val);

    if (server.lazyfree_lazy_server_del) {
        freeObjAsync(key,old);
        dictSetVal(db->dict, &auxentry, NULL);
    }

    dictFreeVal(db->dict, &auxentry);
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值