Redis的RedisObject和对外可见的5种数据结构

目录

RedisObject

Redis的编码方式 

对外可见的5种数据结构

1.string 

string结构的源码

为什么是小于44字节会采用embstr编码?

embstr和raw区别

2.list 

list结构的源码

 3.set

set结构的源码 

4.zset

zset结构的源码

5.hash 

hash结构的源码

Redis中key和value的对应关系 


RedisObject

Redis中任意数据类型的key和value都会被封装成一个RedisObject对象。其源码如下:

//server.h
/* The actual Redis Object */
#define OBJ_STRING 0    /* String object. */
#define OBJ_LIST 1      /* List object. */
#define OBJ_SET 2       /* Set object. */
#define OBJ_ZSET 3      /* Sorted set object. */
#define OBJ_HASH 4      /* Hash object. */

#define LRU_BITS 24

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
                            * LFU data (least significant 8 bits frequency
                            * and most significant 16 bits access time). */
    int refcount;
    void *ptr;
} robj;

redisObject的成员变量的含义如下图所示:

 为什么要对底层的数据结构进行一层包装?

通过封装, 可以根据对象的类型动态地选择存储结构和可以使用的命令, 实现节省空间和优化查询速度。

Redis的编码方式 

根据存储的数据类型的不同,Redis会选择不同的编码方式,共包含11中不同类型。

/* Objects encoding. Some kind of objects like Strings and Hashes can be
 * internally represented in multiple ways. The 'encoding' field of the object
 * is set to one of this fields for this object. */
#define OBJ_ENCODING_RAW 0     /* Raw representation */ //raw编码动态字符串
#define OBJ_ENCODING_INT 1     /* Encoded as integer */ //long类型的整数
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */ //hash表(字典dict)
#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */  //编码为zipmap,已废弃
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */ //双端链表,在数据量少使用,这是旧版的编码方式
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */ //压缩列表
#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */ //整数集合
#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */ //跳表
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */ //embstr的动态字符串
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */ //快速列表
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */ //stream流

对外可见的5种数据结构

结构体redisObject中的变量type就是对外可见的数据类型,有string,hash,list,setzset 5种。

Redis中会根据存储的数据类型不同,会选择不同的编码方式。用string来举个例子。

同样是存储string,其编码方式就会有不同。下图是每种结构类型会使用到的编码方式。

1.string 

Redis中key字符串类型的value最大限制均为512mbkey均是string类型

string是Redis中最常见的数据存储类型:

  • 其基本编码方式是raw,这是基于简单动态字符串(SDS)实现的。
  • 若存储的SDS长度小于44字节,则会采用embstr编码,此时的object head与SDS是一段连续空间
  • 存储的字符串是整数值,并且大小在LONG_MAX范围捏,则会采用int编码,直接将数据保存在RedisObject的ptr指针位置(刚好8字节),不再需要SDS。

string结构的源码

从string结构插入元素的代码入手:

/* SET key value [NX] [XX] [KEEPTTL] [GET] [EX <seconds>] [PX <milliseconds>]
 *     [EXAT <seconds-timestamp>][PXAT <milliseconds-timestamp>] */

//结构体client的变量argv是个数组,元素是robj*。所以客户端输入的数据是已经封装成redisObject结构体的了
//比如输入 set name jack,那name,jack都会封装成redisobject结构,name的赋值给c->argv[1],jack的会赋值给c->argv[2]
void setCommand(client *c) {
    robj *expire = NULL;
    int unit = UNIT_SECONDS;
    ...............................
    //尝试对字符串对象进行编码以节省空间,若字符串是整数的话,就进行编码转换
    c->argv[2] = tryObjectEncoding(c->argv[2]);
    ...........................
}

//object.c
/* Try to encode a string object in order to save space */
//该函数编码是embstr时候,为什么要释放整个redisObject对象?
//因为embstr编码的只申请一次内存空间,这是一个整块,之后是存储整数,就不需要那么大空间,就可释放该对象,再新创redisObject
//而raw编码的,是两次申请内存空间,是两块不连续的内存空间,所以释放ptr部分即可。
robj *tryObjectEncoding(robj *o) {
    long value;
    sds s = o->ptr;
    size_t len;

    .................................................
    len = sdslen(s);
    if (len <= 20 && string2l(s,len,&value)) {//函数string21,表明字符串s中存储的是整数
        if ((server.maxmemory == 0 ||
            !(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
            value >= 0 &&
            value < OBJ_SHARED_INTEGERS)
        {
            .....................................
        } else {
            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) {
                decrRefCount(o);        //释放o
                return createStringObjectFromLongLongForValue(value);//创建编码为OBJ_ENCODING_INT的redisObject对象
            }
        }
    }

    .................................
    return o; /* Return the original object. */
}

创建string的源码:

#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
        return createEmbeddedStringObject(ptr,len); //小于44字节的,就使用embstr编码
    else
        return createRawStringObject(ptr,len);
}

robj *createEmbeddedStringObject(const char *ptr, size_t len) {
    robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);
    struct sdshdr8 *sh = (void*)(o+1);
    
    //给结构体redisObject的变量赋值
    o->type = OBJ_STRING;
    o->encoding = OBJ_ENCODING_EMBSTR;
    o->ptr = sh+1;
    o->refcount = 1;
    if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
        o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
    } else {
        o->lru = LRU_CLOCK();
    }

    //给结构体sdshdr的属性赋值
    sh->len = len;
    sh->alloc = len;
    sh->flags = SDS_TYPE_8;
    if (ptr == SDS_NOINIT)
        sh->buf[len] = '\0';
    else if (ptr) {
        memcpy(sh->buf,ptr,len);
        sh->buf[len] = '\0';
    } else {
        memset(sh->buf,0,len+1);
    }
    return o;
}

/* Create a string object with encoding OBJ_ENCODING_RAW, that is a plain
 * string object where o->ptr points to a proper sds string. */
robj *createRawStringObject(const char *ptr, size_t len) {
    return createObject(OBJ_STRING, sdsnewlen(ptr,len));
}

robj *createObject(int type, void *ptr) {
    robj *o = zmalloc(sizeof(*o));
    o->type = type;
    o->encoding = OBJ_ENCODING_RAW;
    o->ptr = ptr;
    o->refcount = 1;

    ..................省略部分
    return o;
}

为什么是小于44字节会采用embstr编码?

  • 因为Redis底层的内存分配是统一管理的(用c语言的内存分配器jemalloc、tcmalloc),而分配的内存大小为2^{n},即是2,4,8等。
  • Redis认为一次分配的内存大于64字节是个得不偿失的行为,大内存的申请会消耗更长的时间等。所以在Redis中,超过64字节的内存分配,就会多次进行的
  • 前面我们学习的结构体RedisObject的大小是4bit+4bit+24bit+4字节+8字节=16字节。
  • 而字符串为空的SDS,其至少占用1(len)+1(alloc)+1(flags)+1('\0')=4字节。再加上对象头16字节,那一共是20字节。那剩余给字符串的就是64-20=44字节。所以实际字符串超过44字节则为长字符串,会转换为raw格式存储

embstr和raw区别

 embstr与raw都使用redisObject和sds保存数据。

  • embstr的使用只分配一次内存空间,因此其redisObject和sds是连续的;而raw需要分配两次内存空间,分别为其redisObject和sds分配空间。
  • 因此与raw相比,embstr的好处在于创建时少分配一次空间,删除时少释放一次空间,以及对象的所有数据连在一起,寻找方便
  • 而embstr的坏处也很明显,如果字符串的长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配空间,因此redis中的embstr实现为只读。

2.list 

Redis的list结构类似一个双端链表,可以从首/尾操作列表中的元素:

  • 在3.2版本前,Redis采用ziplist和linkedlist来实现list。当元素数量小于512并且元素的大小小于64字节时采用ziplist编码,超过则采用linkedlist编码。
  • 在3.2版本后,Redis统一采用quicklist来实现list。quicklist是个双端链表,每个节点是ziplist。(Redis7.0后,listpack已完全替换ziplist)

list结构的源码

从 list结构插入元素的源码入手:

/* LPUSH <key> <element> [<element> ...] */
void lpushCommand(client *c) {
    pushGenericCommand(c,LIST_HEAD,0);
}
/* Implements LPUSH/RPUSH/LPUSHX/RPUSHX. 
 * 'xx': push if key exists. */
void pushGenericCommand(client *c, int where, int xx) {
    int j;

    //判断每个插入的元素的长度
    for (j = 2; j < c->argc; j++) {
        if (sdslen(c->argv[j]->ptr) > LIST_MAX_ITEM_SIZE) {
            addReplyError(c, "Element too large");
            return;
        }
    }

    //尝试找到key对应的list
    robj *lobj = lookupKeyWrite(c->db, c->argv[1]);
    if (checkType(c,lobj,OBJ_LIST)) return; //检查类型是否正确
    //检查是否为空
    if (!lobj) {
        if (xx) {
            addReply(c, shared.czero);
            return;
        }

        //为空,就创建新的quikclist
        lobj = createQuicklistObject();
        quicklistSetOptions(lobj->ptr, server.list_max_ziplist_size,
                            server.list_compress_depth);
        dbAdd(c->db,c->argv[1],lobj);
    }

    ..............................................
}

//创建list结构
robj *createQuicklistObject(void) {
    quicklist *l = quicklistCreate();    //创建快速列表
    robj *o = createObject(OBJ_LIST,l);
    o->encoding = OBJ_ENCODING_QUICKLIST;
    return o;
}

 3.set

 set是Redis中的集合,不保证元素有序的,其元素唯一,查询效率高。

  • 为了查询效率高,那可以使用Dict;而唯一性,就可以先从Dict中查询,若没有查到就插入,否则不插入。这个就是HT编码(Dict),Dict中的key可以用来存储元素,value统一为null。
  • 当存储的所有数据都是整数,并且元素数量不超过参数set-max-intset-entries时,set会采用intset编码以节省内存。

set结构的源码 

从set插入元素部分代码开始:

//t_set.c
//sadd key value1 value2
void saddCommand(client *c) {
    robj *set;
    int j, added = 0;

    //尝试找到对应key的set,可以简单认为c->argv[1]就是key,c->argv[2]就是value1
    set = lookupKeyWrite(c->db,c->argv[1]);
    //检查类型是否正确
    if (checkType(c,set,OBJ_SET)) return;
    //如果为空
    if (set == NULL) {
        set = setTypeCreate(c->argv[2]->ptr); //创建set
        dbAdd(c->db,c->argv[1],set);
    }

    ....................
}

robj *setTypeCreate(sds value) {
    //判断value是不是整数类型
    if (isSdsRepresentableAsLongLong(value,NULL) == C_OK)
        return createIntsetObject();    //若是,则采用intset编码
    return createSetObject();    //否则采用默认编码,即是HT
}

robj *createSetObject(void) {
    dict *d = dictCreate(&setDictType,NULL);    //创建dict
    robj *o = createObject(OBJ_SET,d);
    o->encoding = OBJ_ENCODING_HT;
    return o;
}

robj *createIntsetObject(void) {
    intset *is = intsetNew();    //创建intset
    robj *o = createObject(OBJ_SET,is);
    o->encoding = OBJ_ENCODING_INTSET;
    return o;
}

4.zset

zset也就是sortedSet,其中每个元素都需要指定一个score值和member值。其特性:

  • 可以根据score值排序
  • memeber值必须唯一
  • 可以根据member查询score

 因此,zset底层数据结构必须满足键值存储键必须唯一可排序这几个要求。

那可以使用什么结构来表示zset呢?

  • 可以排序的,并且同时存储score和member值,那可以使用跳表skiplist
  • 根据key快速找到value,可以使用dict。key保存member值,value保存score。
  • 这两种数据结构会通过指针来共享相同元素的成员和分值,所以不会产生重复成员和分值,不会造成内存的浪费

 确实是使用这两个结构体。源码中有这个结构体zset,源码如下:

typedef struct zset {
    dict *dict;
    zskiplist *zsl;
} zset;

那zset可以不使用dict吗?从性能上来说不妥。

因为zset需要可以通过member得到其score。而skiplist是通过score得到member(score有序),复杂度是O(logN),而通过member查找score,复杂度O(n),从头到尾遍历

当元素数量不多时,dict和skiplist的优势不明显,而且会更耗内存。所以,zset还会采用ziplist结构来节省内存,不过需要同时满足两个条件:

  • 元素数量小于zset_max_ziplist_entries,默认值是128
  • 每个元素的长度都小于zset_max_ziplist_value字节,默认值是64

zset结构的源码

从zset插入元素的代码入手: 

//t_zset.c
void zaddCommand(client *c) {
    zaddGenericCommand(c,ZADD_IN_NONE);
}

/* This generic command implements both ZADD and ZINCRBY. */
//zadd key score1 member1 score2 memeber2 .......
void zaddGenericCommand(client *c, int flags) {
    robj *key = c->argv[1];
    robj *zobj;

    ...................................

    //查找key对应的zset
    zobj = lookupKeyWrite(c->db,key);
    //若没有,就创建
    if (zobj == NULL) {
        if (xx) goto reply_to_client; /* No key + XX option: nothing to do. */
        if (server.zset_max_ziplist_entries == 0 ||
            server.zset_max_ziplist_value < sdslen(c->argv[scoreidx+1]->ptr))
        {
            zobj = createZsetObject();
        } else {
            zobj = createZsetZiplistObject();
        }
        dbAdd(c->db,key,zobj);
    }
    
    //插入元素部分
    for (j = 0; j < elements; j++) {
        double newscore;
        score = scores[j];
        int retflags = 0;

        ele = c->argv[scoreidx+1+j*2]->ptr;
        //zsetadd插入元素,如果最初是ziplist,但随着元素数量的增加,就可能需要进行编码转换,即是用其他结构来保存
        int retval = zsetAdd(zobj, score, ele, flags, &retflags, &newscore);
    }
    ..............................
}

//创建zset
robj *createZsetObject(void) {
    zset *zs = zmalloc(sizeof(*zs));
    robj *o;

    zs->dict = dictCreate(&zsetDictType,NULL);    //创建dict
    zs->zsl = zslCreate();        //创建skiplist
    o = createObject(OBJ_ZSET,zs);    //给redisObject的type赋值为obj_zset
    o->encoding = OBJ_ENCODING_SKIPLIST;    //zset的编码是skiplist
    return o;
}

robj *createZsetZiplistObject(void) {
    unsigned char *zl = ziplistNew();
    robj *o = createObject(OBJ_ZSET,zl);
    o->encoding = OBJ_ENCODING_ZIPLIST;
    return o;
}

zset结构的编码转换是在插入时候进行判断的,在函数zsetAdd中。

int zsetAdd(robj *zobj, double score, sds ele, int in_flags, int *out_flags, double *newscore) {
      .................................

    //判断编码方式,若是ziplist编码,就可能需要进行编码转换
    /* Update the sorted set according to its encoding. */
    if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
        unsigned char *eptr;
        //判断当前元素是否存在,已存在则更新score即可
        if ((eptr = zzlFind(zobj->ptr,ele,&curscore)) != NULL) {
            ...........................................
            return 1;
        } else if (!xx) {
            /* check if the element is too large or the list
             * becomes too long *before* executing zzlInsert. */
            //元素不存在,则新增,需要先判断ziplist长度是否超标,元素的大小是否已超
            if (zzlLength(zobj->ptr)+1 > server.zset_max_ziplist_entries ||
                sdslen(ele) > server.zset_max_ziplist_value ||
                !ziplistSafeToAdd(zobj->ptr, sdslen(ele)))
            {
                //表示超出了,则需要转换为skiplist编码
                zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
            } else {
                //没有超出,就直接往ziplist插入
                zobj->ptr = zzlInsert(zobj->ptr,ele,score);
                if (newscore) *newscore = score;
                *out_flags |= ZADD_OUT_ADDED;
                return 1;
            }
        } else {
            *out_flags |= ZADD_OUT_NOP;
            return 1;
        }
    }

    //本身就是skiplist编码,无需进行转换,就插入即可
    if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
        ..................................
    } else {
        serverPanic("Unknown sorted set encoding");
    }
    return 0; /* Never reached. */
}

 而ziplist本身没有排序功能,而且没有键值对的概念,因此需要由zset通过编码来实现:

  • ziplist是连续内存,所以score和member是紧挨在一起的两个entry,member在前,score在后
  • score越小越接近队首,score越大越接近队尾,按照score值升序排列

查看编码示例 

5.hash 

这个很明显是用dict。所以,hash采用的编码与zset也基本一致的,只需把排序相关的skiplist去掉即可。

而hash结构默认使用ziplist编码,这是为了节省内存。ziplist中相邻的两个entry分别保存field和value。

当数据量较大时,hash结构会转换成HT编码,即是dict,触发条件有2个:

  • ziplist中的元素个数超过了hash-max-ziplist-entries,默认512。
  • ziplist中任意entry大小超过了hash-max-ziplist-value,默认64字节。

hash结构的源码

从hash结构的插入元素的代码入手:

//命令格式例子:hset key filed value [filed value]...
void hsetCommand(client *c) {
    int i, created = 0;
    robj *o;
    .................................

    //1. 判断hash的key是否存在,若不存在就创建新的,默认是采用ziplist编码
    if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
     //2. 判断是否需要把ziplist编码转换为dict编码
    hashTypeTryConversion(o,c->argv,2,c->argc-1);  
    //3. 遍历每一对field和value,并执行hset命令
    for (i = 2; i < c->argc; i += 2)
        created += !hashTypeSet(o,c->argv[i]->ptr,c->argv[i+1]->ptr,HASH_SET_COPY);

    ..................................
}

1.判断key是否存在,若不存在就创建hash

robj *hashTypeLookupWriteOrCreate(client *c, robj *key) {
    robj *o = lookupKeyWrite(c->db,key);    //查找是否有该key
    if (checkType(c,o,OBJ_HASH)) return NULL;   //检查编码类型

    if (o == NULL) {
        o = createHashObject(); //不存在就创建新的
        dbAdd(c->db,key,o);
    }
    return o;
}

//从代码可见,其默认编码是ziplist
robj *createHashObject(void) {
    unsigned char *zl = ziplistNew();   //创建ziplist
    robj *o = createObject(OBJ_HASH, zl);
    o->encoding = OBJ_ENCODING_ZIPLIST;
    return o;
}

2.判断是否需要将编码转换成HT编码

/* Check the length of a number of objects to see if we need to convert a
 * ziplist to a real hash. Note that we only check string encoded objects
 * as their string length can be queried in constant time. */
void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
    int i;
    size_t sum = 0;
    //若编码不是ziplist,表明不用进行转换,立即返回
    if (o->encoding != OBJ_ENCODING_ZIPLIST) return;
    //遍历命令中的field、value参数,即是遍历argv[i]
    for (i = start; i <= end; i++) {
        if (!sdsEncodedObject(argv[i])) //如果编码不是OBJ_ENCODING_RAW和OBJ_ENCODING_embstring
            continue;
        size_t len = sdslen(argv[i]->ptr);
        //若果field或value超过了hash_max_ziplist_value,则需转换,转成HT编码(dict)
        if (len > server.hash_max_ziplist_value) {
            hashTypeConvert(o, OBJ_ENCODING_HT);
            return;
        }
        sum += len;
    }
    if (!ziplistSafeToAdd(o->ptr, sum)) //若ziplist大小超过1G,也转成HT编码
        hashTypeConvert(o, OBJ_ENCODING_HT);
}

/* Don't let ziplists grow over 1GB in any case, don't wanna risk overflow in
 * zlbytes*/
#define ZIPLIST_MAX_SAFETY_SIZE (1<<30)
int ziplistSafeToAdd(unsigned char* zl, size_t add) {
    size_t len = zl? ziplistBlobLen(zl): 0;
    if (len + add > ZIPLIST_MAX_SAFETY_SIZE)
        return 0;
    return 1;
}

//进行编码转换,重点就在hashTypeConvertZiplist函数中
void hashTypeConvert(robj *o, int enc) {
    if (o->encoding == OBJ_ENCODING_ZIPLIST) {
        hashTypeConvertZiplist(o, enc);
    } else if (o->encoding == OBJ_ENCODING_HT) {
        serverPanic("Not implemented");
    } else {
        serverPanic("Unknown hash encoding");
    }
}

void hashTypeConvertZiplist(robj *o, int enc) {
    if (enc == OBJ_ENCODING_ZIPLIST) {
        /* Nothing to do... */

    } else if (enc == OBJ_ENCODING_HT) {//需要转换为HT编码的
        hashTypeIterator *hi;
        dict *dict;
        int ret;

        hi = hashTypeInitIterator(o);
        dict = dictCreate(&hashDictType, NULL);//创建dict

        //逐一把key,value赋值给新创建的dict
        while (hashTypeNext(hi) != C_ERR) {
            sds key, value;
            //获取key,value
            key = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY);
            value = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE);
            ret = dictAdd(dict, key, value);    //往dict中添加
            if (ret != DICT_OK) {
                serverLogHexDump(LL_WARNING,"ziplist with dup elements dump",
                    o->ptr,ziplistBlobLen(o->ptr));
                serverPanic("Ziplist corruption detected");
            }
        }
        hashTypeReleaseIterator(hi);
        zfree(o->ptr);
        o->encoding = OBJ_ENCODING_HT;  //更新编码为HT编码
        o->ptr = dict;              //redisObject对象的ptr指针指向dict
    } 
}

 3.for循环遍历中,将field和value进行设置插入:

int hashTypeSet(robj *o, sds field, sds value, int flags) {
    int update = 0;
    //判断编码方式
    if (o->encoding == OBJ_ENCODING_ZIPLIST) {  //若是ziplist编码
        unsigned char *zl, *fptr, *vptr;

        zl = o->ptr;
        fptr = ziplistIndex(zl, ZIPLIST_HEAD);//找到head位置
        if (fptr != NULL) {//head不为空,开始查找field
            fptr = ziplistFind(zl, fptr, (unsigned char*)field, sdslen(field), 1);//查找field
            if (fptr != NULL) { //表明该field已存在,即是需要进行更新
                /* Grab pointer to the value (fptr points to the field) */
                vptr = ziplistNext(zl, fptr);//到达下一节点位置,即是value
                serverAssert(vptr != NULL);
                update = 1;

                /* Replace value */ //更新该位置的值value
                zl = ziplistReplace(zl, vptr, (unsigned char*)value,
                        sdslen(value));
            }
        }

        if (!update) {  //不存在,则直接push
            /* Push new field/value pair onto the tail of the ziplist */
            //依次将新的field和value都push到ziplist的尾部
            zl = ziplistPush(zl, (unsigned char*)field, sdslen(field),
                    ZIPLIST_TAIL);
            zl = ziplistPush(zl, (unsigned char*)value, sdslen(value),
                    ZIPLIST_TAIL);
        }
        o->ptr = zl;

        /* Check if the ziplist needs to be converted to a hash table */
        //插入了新元素,判断ziplist长度是否超过了,超过则转为HT编码
        if (hashTypeLength(o) > server.hash_max_ziplist_entries)
            hashTypeConvert(o, OBJ_ENCODING_HT);
    } else if (o->encoding == OBJ_ENCODING_HT) {   //若是HT编码,直接插入或更新
        dictEntry *de = dictFind(o->ptr,field);
        ..................................
    } else {
        serverPanic("Unknown hash encoding");
    }

    ......................
    return update;
}

Redis中key和value的对应关系 

首先,Redis内部会将key和value都封装成对应的redisObject对象。

  • 其中key是string数据类型的redisOect
  • value的redisObject就根据用户使用插入命令时候设置的。比如set结构类型的插入,sadd name jack。jack就会封装成type是set的redisObject。

那key是怎么对应上该value,其是如何可以快速通过key找到value的呢?

其实,Redis也是使用一个数据结构来存放key和value。要想查找效率高,那就是使用dict数据结构。所以key和value是通过hash表进行关联的。每个哈希表都有一个键值对,其key是唯一的,并且与对应的value相关联。

所以说,整个Redis是个大的dict。我们通过string的获取来查看下,比如命令get age。这时redis会调用getCommand函数。该函数最终会调用db.c文件的lookupKey函数,该函数内部是从dict中查找该key。

//命令格式 get key
void getCommand(client *c) {
    getGenericCommand(c);
}

//其重点就在lookupKeyReadOrReply函数中
int getGenericCommand(client *c) {
    robj *o;
     //查找是否有该key
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL)
        return C_OK;

    if (checkType(c,o,OBJ_STRING)) {
        return C_ERR;
    }
    addReplyBulk(c,o);
    return C_OK;
}

robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
    robj *o = lookupKeyRead(c->db, key);
    if (!o) SentReplyOnKeyMiss(c, reply);
    return o;
}

robj *lookupKeyRead(redisDb *db, robj *key) {
    return lookupKeyReadWithFlags(db,key,LOOKUP_NONE);
}

robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
    robj *val;

   ................
    val = lookupKey(db,key,flags);//查找该key
    .......................
    return val;

    ..........................
}

//db.c
robj *lookupKey(redisDb *db, robj *key, int flags) {
    dictEntry *de = dictFind(db->dict,key->ptr);    //在dict中查找
    if (de) {
        robj *val = dictGetVal(de);

        if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){
            if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
                updateLFU(val);
            } else {
                val->lru = LRU_CLOCK();
            }
        }
        return val;
    } else {
        return NULL;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/553857.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

EtherCAT开发_2_SSC使用记录

SSC快速开始参考《EtherCAT Slave Design Quick Guide》 字段内容直接参考SSC工具右侧Description&#xff0c;本文未填写。中文也可直接参考:《https://blog.csdn.net/g360250466/article/details/129847081》 ① Select EL9800 | 8Bit Digital I/O, 16Bit Analog Input 一、S…

Intel性能分析工具Vtune安装和使用简介

一、介绍 Intel Vtune profiler是用于串行和多线程应用程序的性能分析工具&#xff0c;可以帮助软件开发人员对应用程序的性能问题进行分析&#xff0c;支持包括linux和windows在内的多种操作系统。主要功能包括&#xff1a; 性能分析&#xff1a;可以对应用程序进行深入的性…

如何将低分辨率的视频变高清,使用AI工具分辨率画质增强至1080P、4K或者8K(附工具)

环境&#xff1a; Topaz Video AI 5.0 问题描述&#xff1a; 如何将低分辨率的视频变高清&#xff0c;使用AI工具分辨率画质增强至1080P、4K或者8K 原视频 增强1080P 解决方案&#xff1a; 1.打开软件&#xff0c;导入要处理的视频&#xff08;工具在本文最后附上&#xf…

网络安全:绕过 MSF 的一次渗透测试

这次渗透的主站是 一个 Discuz!3.4 的搭建 违法招 piao 网站&#xff0c; 配置有宝塔 WAF 用 Discuz!ML 3.X 的漏洞进行攻击&#xff0c;但是没有成功 发现主站外链会有一个发卡网&#xff0c;引导人们来这充值&#xff0c;是 某某发卡网&#xff0c;而且域名指向也是主站的 ip…

Stable Diffusion 模型分享:CyberRealistic XL(真实)cyberrealisticXL_v11VAE.safetensors

本文收录于《AI绘画从入门到精通》专栏,专栏总目录:点这里,订阅后可阅读专栏内所有文章。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八下载地址模型介绍

计算机网络基础:宏观认识

目录 一、网络发展背景与基本概念 二、网络协议的意义与TCP/IP五层结构模型 三、网络传输的基本流程与封装分用 四、ip地址和mac地址 随着信息技术的飞速发展&#xff0c;计算机网络已经成为了现代社会不可或缺的一部分。无论是工作、学习还是娱乐&#xff0c;我们几乎都离…

Crossref

https://baijiahao.baidu.com/s?id1766583173146005960&wfrspider&forpc https://zhidao.baidu.com/question/1796197318615421547.html

Java垃圾回收2

垃圾回收的算法有哪些 通过可达性分析算法&#xff0c;我们已经可以找到需要回收的对象。现在需要通过垃圾回收算法&#xff0c;把垃圾回收&#xff0c;释放内存。 1.标记清除算法(使用较少) 标记清除算法&#xff0c;是将垃圾回收分为2个阶段&#xff0c;分别是标记和清除。…

面试官:来说说vue3是怎么处理内置的v-for、v-model等指令?

前言 最近有粉丝找到我&#xff0c;说被面试官给问懵了。 粉丝&#xff1a;面试官上来就问“一个vue文件是如何渲染成浏览器上面的真实DOM&#xff1f;”&#xff0c;当时还挺窃喜这题真简单。就简单说了一下先是编译成render函数、然后根据render函数生成虚拟DOM&#xff0c;…

国外GIS软件排名简介<30个>

简介 国外gisgeography网站进行了一次GIS软件排名&#xff0c;通过分析、制图、编辑等因素进行测试&#xff0c;具体规则如下&#xff1a; 分析&#xff1a;矢量/栅格工具、时态、地统计、网络分析和脚本。 制图&#xff1a;地图类型、坐标系、地图布局/元素、标注/注记、3D …

请勿假设你的用户都有管理员权限

有些人觉得自己很聪明&#xff0c;他们在程序中做了这样一项”优化”。 在程序的安装阶段&#xff0c;他们不会安装某些程序功能&#xff0c;而是等到用户第一次使用的时候才执行&#xff0c;也即所谓的 “按需加载”。 问题在于&#xff0c;第一次使用的时候&#xff0c;用户…

CSS-布局

display display 属性是用于控制 布局 的最重要的 CSS 属性。display 属性规定是否/如何显示元素。 每个 HTML 元素都有一个默认的 display 值&#xff0c;具体取决于它的元素类型。大多数元素的默认 display 值为 block 或 inline。 block block&#xff1a;块级元素。块级…

从二本调剂到上海互联网公司算法工程师:我的成长故事

探讨选择成为一名程序员的原因&#xff0c;是出于兴趣还是职业发展&#xff1f; 在这个科技飞速发展的时代&#xff0c;程序员这一职业无疑成为了许多人眼中的香饽饽。那么&#xff0c;是什么驱使着越来越多的人选择投身于这一行业呢&#xff1f;是出于对编程的热爱&#xff0…

三步教你怎么把icloud照片恢复至iphone!

“我手机里面照片被优化后&#xff0c;然后不小心把所有被优化的模糊照片从手机中删除了&#xff0c;但是iCloud还有&#xff0c;我应该怎样把iCloud的照片重新放回手机&#xff1f;谢谢。” 在使用iPhone时&#xff0c;iCloud照片库是一个非常方便的功能&#xff0c;它允许你在…

文化=知识+素质!电动车限制多!——早读(逆天打工人爬取热门微信文章解读)

你是一个有文化的人&#xff01; 引言Python 代码第一篇 洞见 一个人有没有文化&#xff0c;就看这五点第二篇 人民日报 来啦 新闻早班车要闻社会政策 结尾 知耻近乎勇 文化教会我们自省 以羞耻心为镜 照见自我 不断向善向上。 引言 绝了 昨天晚上早早上床 10点左右就睡眠模…

微信小程序自定义导航栏定位及胶囊按钮图解

在自定义小程序导航栏时&#xff0c;右上角的胶囊&#xff08;MenuButton&#xff09;在不同机型测试&#xff0c;会发现很难适配。 实测中 不同的手机&#xff0c;胶囊高度不一样、状态栏高度不一样。与模拟器显示的情况是不一样的。 由于小程序在不同的手机上顶部布局会发生…

单片机入门还能从51开始吗?

选择从51单片机开始入门还是直接学习基于ARM核或RISC核的单片机&#xff0c;取决于学习目标、项目需求以及个人兴趣。每种单片机都有其特定的优势和应用场景&#xff0c;了解它们的特点可以帮助你做出更合适的选择。 首先&#xff0c;我们说一下51单片机的优势&#xff1a; 成熟…

设计模式之模板方法模式详解(上)

模板方法模式 1&#xff09;概述 1.定义 定义一个操作中算法的框架&#xff0c;而将一些步骤延迟到子类中&#xff0c;模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 2.方案 背景&#xff1a;某个方法的实现需要多个步骤&#xff08;类似…

Postman之接口测试

接口测试的必要条件 &#xff1a;请求方式、请求协议、请求地址、请求头、请求参数 常用请求方式 &#xff1a;Get请求&#xff08;get请求一般是获取数据&#xff09;、Post请求&#xff08;post请求一般是提交数据&#xff09; 传参格式 &#xff1a;表单提交、请求体提交 注…

Golang入门教程(非常详细)从零基础入门到精通,看完这一篇就够了

文章目录 一、golang 简介 1. go 语言特点2. go 语言应用领域3. 使用 go 语言的公司有哪些 二、安装 golang 1. golang 下载安装2. 配置环境变量 三、golang 开发工具 1. 安装 VSCode2. 下载所需插件 四、第一个 golang 应用 1. main 包的含义2. 示例 一、golang 简介 Go 是一…
最新文章