Redis源码分析(九)——字符串类型t_string

在上一分析中已经说明了Reids的类型系统的设计原由和基本思想。t_string就是其中最为常用的类型之一,其主要是对sds字符串的的函数调用,以及和客户端(redisClient)的数据交互过程:根据客户端的操作命令(如:SET /GET/SETNX/INCR等等)传入客户端对象——>从对象的数据缓冲区中取出各命令操作参数——>检测参数——>调用底层函数对键、值执行相应命令操作——>把操作结果返回给客户端对象数据缓冲区。


t_string类型分包括两种形式的编码:只有能表示为long类型的值才会以整数的形式保存,其他类型的整数、小数、字符串都是以sdshdr结构来保存。新建的字符串默认使用REDIS_ENCONDING_RAW编码,在将字符串作为键或值保存进数据库时,程序会尝试将字符串转化为REDIS_ENCODING_INT编码。如图:


具体各操作命令的实现见代码分析注释(t_string.c):

(在此参考了http://www.cnblogs.com/stephen-liu74/archive/2012/03/14/2349815.html的命令总结)

<span style="color:#3333ff;">/*
 * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Redis nor the names of its contributors may be used
 *     to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "redis.h"
#include <math.h> /* isnan(), isinf() */

/*-----------------------------------------------------------------------------
 * String Commands
 *----------------------------------------------------------------------------*/

/*
 * 检查给定字符串长度 len 是否超过限制值 512 MB
 *
 * 超过返回 REDIS_ERR ,未超过返回 REDIS_OK
 *
 * T = O(1)
 */
static int checkStringLength(redisClient *c, long long size) {

    if (size > 512*1024*1024) {
        addReplyError(c,"string exceeds maximum allowed size (512MB)");
        return REDIS_ERR;
    }

    return REDIS_OK;
}

/* 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.
 *
 * setGenericCommand() 函数实现了 SET 、 SETEX 、 PSETEX 和 SETNX 命令。
 *
 * 'flags' changes the behavior of the command (NX or XX, see belove).
 *
 * flags 参数的值可以是 NX 或 XX ,它们的意义请见下文。
 *
 * '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 对象的过期时间。
 *
 * 而这个过期时间的格式由 unit 参数指定。
 *
 * '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.
 * If abort_reply is NULL, "$-1" is used. 
 *
 * 如果 ok_reply 为 NULL ,那么 "+OK" 被返回。
 * 如果 abort_reply 为 NULL ,那么 "$-1" 被返回。
 */

#define REDIS_SET_NO_FLAGS 0
#define REDIS_SET_NX (1<<0)     /* Set if key not exists. */
#define REDIS_SET_XX (1<<1)     /* Set if key exists. */
//SET:设置key:存在着覆盖其value否则添加
void setGenericCommand(redisClient *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) {

        // 取出 expire 参数的值
        // T = O(N)
        if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != REDIS_OK)
            return;

        // expire 参数的值不正确时报错
        if (milliseconds <= 0) {
            addReplyError(c,"invalid expire time in SETEX");
            return;
        }

        // 不论输入的过期时间是秒还是毫秒
        // Redis 实际都以毫秒的形式保存过期时间
        // 如果输入的过期时间为秒,那么将它转换为毫秒
        if (unit == UNIT_SECONDS) milliseconds *= 1000;
    }

    // 如果设置了 NX 或者 XX 参数,那么检查条件是否不符合这两个设置
    // 在条件不符合时报错,报错的内容由 abort_reply 参数决定
    if ((flags & REDIS_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||    //key存在
        (flags & REDIS_SET_XX && lookupKeyWrite(c->db,key) == NULL))   //   key不存在位运算优先于逻辑运算
    {
        addReply(c, abort_reply ? abort_reply : shared.nullbulk);
        return;
    }

    // 将键值关联到数据库(存在则覆盖,否则添加)
    setKey(c->db,key,val);

    // 将数据库设为脏
    server.dirty++;

    // 为键设置过期时间
    if (expire) setExpire(c->db,key,mstime()+milliseconds);

    // 发送事件通知
    notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",key,c->db->id);

    // 发送事件通知
    if (expire) notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,
        "expire",key,c->db->id);

    // 设置成功,向客户端发送回复
    // 回复的内容由 ok_reply 决定
    addReply(c, ok_reply ? ok_reply : shared.ok);
}

/* SET key value [NX] [XX] [EX <seconds>] [PX <milliseconds>] */
//设定该Key持有指定的字符串Value,如果该Key已经存在,则覆盖其原有值。
void setCommand(redisClient *c) {
    int j;
    robj *expire = NULL;
    int unit = UNIT_SECONDS;
    int flags = REDIS_SET_NO_FLAGS;

    // 设置选项参数
    for (j = 3; j < c->argc; j++) {
        char *a = c->argv[j]->ptr;
        robj *next = (j == c->argc-1) ? NULL : c->argv[j+1];

        if ((a[0] == 'n' || a[0] == 'N') &&
            (a[1] == 'x' || a[1] == 'X') && a[2] == '\0') {
            flags |= REDIS_SET_NX;
        } else if ((a[0] == 'x' || a[0] == 'X') &&
                   (a[1] == 'x' || a[1] == 'X') && a[2] == '\0') {
            flags |= REDIS_SET_XX;
        } else if ((a[0] == 'e' || a[0] == 'E') &&
                   (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) {
            unit = UNIT_SECONDS;
            expire = next;
            j++;
        } else if ((a[0] == 'p' || a[0] == 'P') &&
                   (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) {
            unit = UNIT_MILLISECONDS;
            expire = next;
            j++;
        } else {
            addReply(c,shared.syntaxerr);
            return;
        }
    }

    // 尝试对值对象进行编码
    c->argv[2] = tryObjectEncoding(c->argv[2]);//主要是防止Object Value是一个数字  看看能不能进行重新编码

    setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
}

//如果指定的Key不存在,则设定该Key持有指定字符串Value,此时其效果等价于SET命令。
//相反,如果该Key已经存在,该命令将不做任何操作并返回。
void setnxCommand(redisClient *c) {
    c->argv[2] = tryObjectEncoding(c->argv[2]);
    setGenericCommand(c,REDIS_SET_NX,c->argv[1],c->argv[2],NULL,0,shared.cone,shared.czero);
}

//原子性完成两个操作,一是设置该Key的值为指定字符串,同时设置该Key在Redis服务器中的存活时间(秒数)。该命令主要应用于Redis被当做Cache服务器使用时。
void setexCommand(redisClient *c) {
    c->argv[3] = tryObjectEncoding(c->argv[3]);
    setGenericCommand(c,REDIS_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_SECONDS,NULL,NULL);
}

void psetexCommand(redisClient *c) {
    c->argv[3] = tryObjectEncoding(c->argv[3]);
    setGenericCommand(c,REDIS_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_MILLISECONDS,NULL,NULL);
}

//GET  key的值对象
int getGenericCommand(redisClient *c) {
    robj *o;

    // 尝试从数据库中取出键 c->argv[1] 对应的值对象
    // 如果键不存在时,向客户端发送回复信息,并返回 NULL
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
        return REDIS_OK;

    // 值对象存在,检查它的类型
    if (o->type != REDIS_STRING) {
        // 类型错误
        addReply(c,shared.wrongtypeerr);
        return REDIS_ERR;
    } else {
        // 类型正确,向客户端返回对象的值
        addReplyBulk(c,o);
        return REDIS_OK;
    }
}

//获取指定Key的Value。如果与该Key关联的Value不是string类型,
//Redis将返回错误信息,因为GET命令只能用于获取string Value。 
void getCommand(redisClient *c) {
    getGenericCommand(c);
}

//原子性的设置该Key为指定的Value,同时返回该Key的原有值。和GET命令一样,
//该命令也只能处理string Value,否则Redis将给出相关的错误信息。
void getsetCommand(redisClient *c) {

    // 取出并返回键的值对象
    if (getGenericCommand(c) == REDIS_ERR) return;

    // 编码键的新值 c->argv[2]
    c->argv[2] = tryObjectEncoding(c->argv[2]);

    // 将数据库中关联键 c->argv[1] 和新值对象 c->argv[2]
    setKey(c->db,c->argv[1],c->argv[2]);

    // 发送事件通知
    notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",c->argv[1],c->db->id);

    // 将服务器设为脏
    server.dirty++;
}

/*替换指定Key的部分字符串值。从offset开始,替换的长度为该命令第三个参数value的字符串长度,
其中如果offset的值大于该Key的原有值Value的字符串长度,
Redis将会在Value的后面补齐(offset - strlen(value))数量的0x00,之后再追加新值。
如果该键不存在,该命令会将其原值的长度假设为0,并在其后添补offset个0x00后再追加新值。
鉴于字符串Value的最大长度为512M,因此offset的最大值为536870911。
最后需要注意的是,如果该命令在执行时致使指定Key的原有值长度增加,
这将会导致Redis重新分配足够的内存以容纳替换后的全部字符串,因此就会带来一定的性能折损。
*/
void setrangeCommand(redisClient *c) {
    robj *o;
    long offset;

    sds value = c->argv[3]->ptr;

    // 取出 offset 参数
    if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != REDIS_OK)
        return;

    // 检查 offset 参数
    if (offset < 0) {
        addReplyError(c,"offset is out of range");
        return;
    }

    // 取出键现在的值对象
    o = lookupKeyWrite(c->db,c->argv[1]);
    if (o == NULL) {

        // 键不存在于数据库中。。。

        /* Return 0 when setting nothing on a non-existing string */
        // value 为空,没有什么可设置的,向客户端返回 0
        if (sdslen(value) == 0) {
            addReply(c,shared.czero);
            return;
        }

        /* Return when the resulting string exceeds allowed size */
        // 如果设置后的长度会超过 Redis 的限制的话
        // 那么放弃设置,向客户端发送一个出错回复
        if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK)
            return;

        // 如果 value 没有问题,可以设置,那么创建一个空字符串值对象
        // 并在数据库中关联键 c->argv[1] 和这个空字符串对象
        o = createObject(REDIS_STRING,sdsempty());
        dbAdd(c->db,c->argv[1],o);
    } else {
        size_t olen;

        // 值对象存在。。。

        /* Key exists, check type */
        // 检查值对象的类型
        if (checkType(c,o,REDIS_STRING))
            return;

        /* Return existing string length when setting nothing */
        // 取出原有字符串的长度
        olen = stringObjectLen(o);

        // value 为空,没有什么可设置的,向客户端返回 0
        if (sdslen(value) == 0) {
            addReplyLongLong(c,olen);
            return;
        }

        /* Return when the resulting string exceeds allowed size */
        // 如果设置后的长度会超过 Redis 的限制的话
        // 那么放弃设置,向客户端发送一个出错回复
        if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK)
            return;

        /* Create a copy when the object is shared or encoded. */
        o = dbUnshareStringValue(c->db,c->argv[1],o);
    }

    // 这里的 sdslen(value) > 0 其实可以去掉
    // 前面已经做了检测了
    if (sdslen(value) > 0) {
        // 扩展字符串值对象
        o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
        // 将 value 复制到字符串中的指定的位置
        memcpy((char*)o->ptr+offset,value,sdslen(value));

        // 向数据库发送键被修改的信号
        signalModifiedKey(c->db,c->argv[1]);

        // 发送事件通知
        notifyKeyspaceEvent(REDIS_NOTIFY_STRING,
            "setrange",c->argv[1],c->db->id);

        // 将服务器设为脏
        server.dirty++;
    }

    // 设置成功,返回新的字符串值给客户端
    addReplyLongLong(c,sdslen(o->ptr));
}
/*如果截取的字符串长度很短,我们可以该命令的时间复杂度视为O(1),否则就是O(N),
这里N表示截取的子字符串长度。该命令在截取子字符串时,
将以闭区间的方式同时包含start(0表示第一个字符)和end所在的字符,
如果end值超过Value的字符长度,该命令将只是截取从start开始之后所有的字符数据。
*/
void getrangeCommand(redisClient *c) {
    robj *o;
    long start, end;
    char *str, llbuf[32];
    size_t strlen;

    // 取出 start 参数
    if (getLongFromObjectOrReply(c,c->argv[2],&start,NULL) != REDIS_OK)
        return;

    // 取出 end 参数
    if (getLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK)
        return;

    // 从数据库中查找键 c->argv[1] 
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL ||
        checkType(c,o,REDIS_STRING)) return;

    // 根据编码,对对象的值进行处理
    if (o->encoding == REDIS_ENCODING_INT) {
        str = llbuf;
        strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
    } else {
        str = o->ptr;
        strlen = sdslen(str);
    }

    /* Convert negative indexes */
    // 将负数索引转换为整数索引
    if (start < 0) start = strlen+start;
    if (end < 0) end = strlen+end;
    if (start < 0) start = 0;
    if (end < 0) end = 0;
    if ((unsigned)end >= strlen) end = strlen-1;

    /* Precondition: end >= 0 && end < strlen, so the only condition where
     * nothing can be returned is: start > end. */
    if (start > end) {
        // 处理索引范围为空的情况
        addReply(c,shared.emptybulk);
    } else {
        // 向客户端返回给定范围内的字符串内容
        addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
    }
}

/*N表示获取Key的数量。返回所有指定Keys的Values,
如果其中某个Key不存在,或者其值不为string类型,该Key的Value将返回nil。
*/
void mgetCommand(redisClient *c) {
    int j;

    addReplyMultiBulkLen(c,c->argc-1);
    // 查找并返回所有输入键的值
    for (j = 1; j < c->argc; j++) {
        // 查找键 c->argc[j] 的值
        robj *o = lookupKeyRead(c->db,c->argv[j]);查询key对象的value,如果不存在返回null,存在返回其value
        if (o == NULL) {
            // 值不存在,向客户端发送空回复
            addReply(c,shared.nullbulk);
        } else {
            if (o->type != REDIS_STRING) {
                // 值存在,但不是字符串类型
                addReply(c,shared.nullbulk);
            } else {
                // 值存在,并且是字符串
                addReplyBulk(c,o);
            }
        }
    }
}

/*N表示指定Key的数量。该命令原子性的完成参数中所有key/value的设置操作,
其具体行为可以看成是多次迭代执行SET命令。 */
void msetGenericCommand(redisClient *c, int nx) {
    int j, busykeys = 0;

    // 键值参数不是成相成对出现的,格式不正确
    if ((c->argc % 2) == 0) {
        addReplyError(c,"wrong number of arguments for MSET");
        return;
    }
    /* Handle the NX flag. The MSETNX semantic is to return zero and don't
     * set nothing at all if at least one already key exists. */
    // 如果 nx 参数为真,那么检查所有输入键在数据库中是否存在
    // 只要有一个键是存在的,那么就向客户端发送空回复
    // 并放弃执行接下来的设置操作
    if (nx) {
        for (j = 1; j < c->argc; j += 2) {
            if (lookupKeyWrite(c->db,c->argv[j]) != NULL) {
                busykeys++;
            }
        }
        // 键存在
        // 发送空白回复,并放弃执行接下来的设置操作
        if (busykeys) {
            addReply(c, shared.czero);
            return;
        }
    }

    // 设置所有键值对
    for (j = 1; j < c->argc; j += 2) {

        // 对值对象进行解码
        c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);

        // 将键值对关联到数据库
        // c->argc[j] 为键
        // c->argc[j+1] 为值
        setKey(c->db,c->argv[j],c->argv[j+1]);

        // 发送事件通知
        notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",c->argv[j],c->db->id);
    }

    // 将服务器设为脏
    server.dirty += (c->argc-1)/2;

    // 设置成功
    // MSET 返回 OK ,而 MSETNX 返回 1
    addReply(c, nx ? shared.cone : shared.ok);
}
/*N表示指定Key的数量。该命令原子性的完成参数中所有key/value的设置操作,
其具体行为可以看成是多次迭代执行SET命令。 */
void msetCommand(redisClient *c) {
    msetGenericCommand(c,0);
}
/*N表示指定Key的数量。该命令原子性的完成参数中所有key/value的设置操作,
其具体行为可以看成是多次迭代执行SETNX命令。然而这里需要明确说明的是,
如果在这一批Keys中有任意一个Key已经存在了,
那么该操作将全部回滚,即所有的修改都不会生效。*/
void msetnxCommand(redisClient *c) {
    msetGenericCommand(c,1);
}
/*增减key对应的value对象*/
void incrDecrCommand(redisClient *c, long long incr) {
    long long value, oldvalue;
    robj *o, *new;

    // 取出值对象
    o = lookupKeyWrite(c->db,c->argv[1]);

    // 检查对象是否存在,以及类型是否正确
    if (o != NULL && checkType(c,o,REDIS_STRING)) return;

    // 取出对象的整数值,并保存到 value 参数中
    if (getLongLongFromObjectOrReply(c,o,&value,NULL) != REDIS_OK) return;

    // 检查加法操作执行之后值释放会溢出
    // 如果是的话,就向客户端发送一个出错回复,并放弃设置操作
    oldvalue = value;
    if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
        (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
        addReplyError(c,"increment or decrement would overflow");
        return;
    }

    // 进行加法计算,并将值保存到新的值对象中
    // 然后用新的值对象替换原来的值对象
    value += incr;
    new = createStringObjectFromLongLong(value);
    if (o)
        dbOverwrite(c->db,c->argv[1],new);
    else
        dbAdd(c->db,c->argv[1],new);

    // 向数据库发送键被修改的信号
    signalModifiedKey(c->db,c->argv[1]);

    // 发送事件通知
    notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"incrby",c->argv[1],c->db->id);

    // 将服务器设为脏
    server.dirty++;

    // 返回回复
    addReply(c,shared.colon);
    addReply(c,new);
    addReply(c,shared.crlf);
}

/*将指定Key的Value原子性的递增1。
如果该Key不存在,其初始值为0,在incr之后其值为1。
如果Value的值不能转换为整型值,如Hello,该操作将执行失败并返回相应的错误信息。
注意:该操作的取值范围是64位有符号整型。 */
void incrCommand(redisClient *c) {
    incrDecrCommand(c,1);
}
/*将指定Key的Value原子性的递减1。
如果该Key不存在,其初始值为0,在decr之后其值为-1。
如果Value的值不能转换为整型值,如Hello,该操作将执行失败并返回相应的错误信息。
注意:该操作的取值范围是64位有符号整型。*/
void decrCommand(redisClient *c) {
    incrDecrCommand(c,-1);
}
/*将指定Key的Value原子性的增加increment。
如果该Key不存在,其初始值为0,在incrby之后其值为increment。
如果Value的值不能转换为整型值,如Hello,该操作将执行失败并返回相应的错误信息。
注意:该操作的取值范围是64位有符号整型。 */
void incrbyCommand(redisClient *c) {
    long long incr;

    if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return;
    incrDecrCommand(c,incr);
}
/*将指定Key的Value原子性的减少decrement。如果该Key不存在,
其初始值为0,在decrby之后其值为-decrement。如果Value的值不能转换为整型值,
如Hello,该操作将执行失败并返回相应的错误信息。
注意:该操作的取值范围是64位有符号整型。 */
void decrbyCommand(redisClient *c) {
    long long incr;

    if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return;
    incrDecrCommand(c,-incr);
}

void incrbyfloatCommand(redisClient *c) {
    long double incr, value;
    robj *o, *new, *aux;

    // 取出值对象
    o = lookupKeyWrite(c->db,c->argv[1]);

    // 检查对象是否存在,以及类型是否正确
    if (o != NULL && checkType(c,o,REDIS_STRING)) return;

    // 将对象的整数值保存到 value 参数中
    // 并取出 incr 参数的值
    if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != REDIS_OK ||
        getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != REDIS_OK)
        return;

    // 进行加法计算,并检查是否溢出
    value += incr;
    if (isnan(value) || isinf(value)) {
        addReplyError(c,"increment would produce NaN or Infinity");
        return;
    }

    // 用一个包含新值的新对象替换现有的值对象
    new = createStringObjectFromLongDouble(value);
    if (o)
        dbOverwrite(c->db,c->argv[1],new);
    else
        dbAdd(c->db,c->argv[1],new);

    // 向数据库发送键被修改的信号
    signalModifiedKey(c->db,c->argv[1]);

    // 发送事件通知
    notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"incrbyfloat",c->argv[1],c->db->id);

    // 将服务器设为脏
    server.dirty++;

    // 回复
    addReplyBulk(c,new);

    /* Always replicate INCRBYFLOAT as a SET command with the final value
     * in order to make sure that differences in float precision or formatting
     * will not create differences in replicas or after an AOF restart. */
    // 在传播 INCRBYFLOAT 命令时,总是用 SET 命令来替换 INCRBYFLOAT 命令
    // 从而防止因为不同的浮点精度和格式化造成 AOF 重启时的数据不一致
    aux = createStringObject("SET",3);
    rewriteClientCommandArgument(c,0,aux);
    decrRefCount(aux);
    rewriteClientCommandArgument(c,2,new);
}
/*如果该Key已经存在,APPEND命令将参数Value的数据追加到已存在Value的末尾。
如果该Key不存在,APPEND命令将会创建一个新的Key/Value
*/
void appendCommand(redisClient *c) {
    size_t totlen;
    robj *o, *append;

    // 取出键相应的值对象
    o = lookupKeyWrite(c->db,c->argv[1]);
    if (o == NULL) {

        // 键值对不存在。。。

        /* Create the key */
        // 键值对不存在,创建一个新的
        c->argv[2] = tryObjectEncoding(c->argv[2]);
        dbAdd(c->db,c->argv[1],c->argv[2]);
        incrRefCount(c->argv[2]);
        totlen = stringObjectLen(c->argv[2]);
    } else {

        // 键值对存在。。。

        /* Key exists, check type */
        // 检查类型
        if (checkType(c,o,REDIS_STRING))
            return;

        /* "append" is an argument, so always an sds */
        // 检查追加操作之后,字符串的长度是否符合 Redis 的限制
        append = c->argv[2];
        totlen = stringObjectLen(o)+sdslen(append->ptr);
        if (checkStringLength(c,totlen) != REDIS_OK)
            return;

        /* Append the value */
        // 执行追加操作
        o = dbUnshareStringValue(c->db,c->argv[1],o);
        o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
        totlen = sdslen(o->ptr);
    }

    // 向数据库发送键被修改的信号
    signalModifiedKey(c->db,c->argv[1]);

    // 发送事件通知
    notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"append",c->argv[1],c->db->id);

    // 将服务器设为脏
    server.dirty++;

    // 发送回复
    addReplyLongLong(c,totlen);
}
/*返回指定Key的字符值长度,如果Value不是string类型,
Redis将执行失败并给出相关的错误信息。
*/
void strlenCommand(redisClient *c) {
    robj *o;

    // 取出值对象,并进行类型检查
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
        checkType(c,o,REDIS_STRING)) return;

    // 返回字符串值的长度
    addReplyLongLong(c,stringObjectLen(o));
}
</span>


在这里能看到,已经从前面的各个小模块底层的独立实现,到了上层的系统级的封装调用了,在分析过程中需要对运行流程的整体把握才能更好的理解代码,这也正是我所需要突破的。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值