当前位置:   article > 正文

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

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对象。其源码如下:

  1. //server.h
  2. /* The actual Redis Object */
  3. #define OBJ_STRING 0 /* String object. */
  4. #define OBJ_LIST 1 /* List object. */
  5. #define OBJ_SET 2 /* Set object. */
  6. #define OBJ_ZSET 3 /* Sorted set object. */
  7. #define OBJ_HASH 4 /* Hash object. */
  8. #define LRU_BITS 24
  9. typedef struct redisObject {
  10. unsigned type:4;
  11. unsigned encoding:4;
  12. unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
  13. * LFU data (least significant 8 bits frequency
  14. * and most significant 16 bits access time). */
  15. int refcount;
  16. void *ptr;
  17. } robj;

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

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

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

Redis的编码方式 

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

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

  1. /* SET key value [NX] [XX] [KEEPTTL] [GET] [EX <seconds>] [PX <milliseconds>]
  2. * [EXAT <seconds-timestamp>][PXAT <milliseconds-timestamp>] */
  3. //结构体client的变量argv是个数组,元素是robj*。所以客户端输入的数据是已经封装成redisObject结构体的了
  4. //比如输入 set name jack,那name,jack都会封装成redisobject结构,name的赋值给c->argv[1],jack的会赋值给c->argv[2]
  5. void setCommand(client *c) {
  6. robj *expire = NULL;
  7. int unit = UNIT_SECONDS;
  8. ...............................
  9. //尝试对字符串对象进行编码以节省空间,若字符串是整数的话,就进行编码转换
  10. c->argv[2] = tryObjectEncoding(c->argv[2]);
  11. ...........................
  12. }
  13. //object.c
  14. /* Try to encode a string object in order to save space */
  15. //该函数编码是embstr时候,为什么要释放整个redisObject对象?
  16. //因为embstr编码的只申请一次内存空间,这是一个整块,之后是存储整数,就不需要那么大空间,就可释放该对象,再新创redisObject
  17. //而raw编码的,是两次申请内存空间,是两块不连续的内存空间,所以释放ptr部分即可。
  18. robj *tryObjectEncoding(robj *o) {
  19. long value;
  20. sds s = o->ptr;
  21. size_t len;
  22. .................................................
  23. len = sdslen(s);
  24. if (len <= 20 && string2l(s,len,&value)) {//函数string21,表明字符串s中存储的是整数
  25. if ((server.maxmemory == 0 ||
  26. !(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
  27. value >= 0 &&
  28. value < OBJ_SHARED_INTEGERS)
  29. {
  30. .....................................
  31. } else {
  32. if (o->encoding == OBJ_ENCODING_RAW) {
  33. sdsfree(o->ptr);
  34. o->encoding = OBJ_ENCODING_INT;
  35. o->ptr = (void*) value;
  36. return o;
  37. } else if (o->encoding == OBJ_ENCODING_EMBSTR) {
  38. decrRefCount(o); //释放o
  39. return createStringObjectFromLongLongForValue(value);//创建编码为OBJ_ENCODING_INT的redisObject对象
  40. }
  41. }
  42. }
  43. .................................
  44. return o; /* Return the original object. */
  45. }

创建string的源码:

  1. #define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
  2. robj *createStringObject(const char *ptr, size_t len) {
  3. if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
  4. return createEmbeddedStringObject(ptr,len); //小于44字节的,就使用embstr编码
  5. else
  6. return createRawStringObject(ptr,len);
  7. }
  8. robj *createEmbeddedStringObject(const char *ptr, size_t len) {
  9. robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);
  10. struct sdshdr8 *sh = (void*)(o+1);
  11. //给结构体redisObject的变量赋值
  12. o->type = OBJ_STRING;
  13. o->encoding = OBJ_ENCODING_EMBSTR;
  14. o->ptr = sh+1;
  15. o->refcount = 1;
  16. if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
  17. o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
  18. } else {
  19. o->lru = LRU_CLOCK();
  20. }
  21. //给结构体sdshdr的属性赋值
  22. sh->len = len;
  23. sh->alloc = len;
  24. sh->flags = SDS_TYPE_8;
  25. if (ptr == SDS_NOINIT)
  26. sh->buf[len] = '\0';
  27. else if (ptr) {
  28. memcpy(sh->buf,ptr,len);
  29. sh->buf[len] = '\0';
  30. } else {
  31. memset(sh->buf,0,len+1);
  32. }
  33. return o;
  34. }
  35. /* Create a string object with encoding OBJ_ENCODING_RAW, that is a plain
  36. * string object where o->ptr points to a proper sds string. */
  37. robj *createRawStringObject(const char *ptr, size_t len) {
  38. return createObject(OBJ_STRING, sdsnewlen(ptr,len));
  39. }
  40. robj *createObject(int type, void *ptr) {
  41. robj *o = zmalloc(sizeof(*o));
  42. o->type = type;
  43. o->encoding = OBJ_ENCODING_RAW;
  44. o->ptr = ptr;
  45. o->refcount = 1;
  46. ..................省略部分
  47. return o;
  48. }

为什么是小于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结构插入元素的源码入手:

  1. /* LPUSH <key> <element> [<element> ...] */
  2. void lpushCommand(client *c) {
  3. pushGenericCommand(c,LIST_HEAD,0);
  4. }
  5. /* Implements LPUSH/RPUSH/LPUSHX/RPUSHX.
  6. * 'xx': push if key exists. */
  7. void pushGenericCommand(client *c, int where, int xx) {
  8. int j;
  9. //判断每个插入的元素的长度
  10. for (j = 2; j < c->argc; j++) {
  11. if (sdslen(c->argv[j]->ptr) > LIST_MAX_ITEM_SIZE) {
  12. addReplyError(c, "Element too large");
  13. return;
  14. }
  15. }
  16. //尝试找到key对应的list
  17. robj *lobj = lookupKeyWrite(c->db, c->argv[1]);
  18. if (checkType(c,lobj,OBJ_LIST)) return; //检查类型是否正确
  19. //检查是否为空
  20. if (!lobj) {
  21. if (xx) {
  22. addReply(c, shared.czero);
  23. return;
  24. }
  25. //为空,就创建新的quikclist
  26. lobj = createQuicklistObject();
  27. quicklistSetOptions(lobj->ptr, server.list_max_ziplist_size,
  28. server.list_compress_depth);
  29. dbAdd(c->db,c->argv[1],lobj);
  30. }
  31. ..............................................
  32. }
  33. //创建list结构
  34. robj *createQuicklistObject(void) {
  35. quicklist *l = quicklistCreate(); //创建快速列表
  36. robj *o = createObject(OBJ_LIST,l);
  37. o->encoding = OBJ_ENCODING_QUICKLIST;
  38. return o;
  39. }

 3.set

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

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

set结构的源码 

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

  1. //t_set.c
  2. //sadd key value1 value2
  3. void saddCommand(client *c) {
  4. robj *set;
  5. int j, added = 0;
  6. //尝试找到对应key的set,可以简单认为c->argv[1]就是key,c->argv[2]就是value1
  7. set = lookupKeyWrite(c->db,c->argv[1]);
  8. //检查类型是否正确
  9. if (checkType(c,set,OBJ_SET)) return;
  10. //如果为空
  11. if (set == NULL) {
  12. set = setTypeCreate(c->argv[2]->ptr); //创建set
  13. dbAdd(c->db,c->argv[1],set);
  14. }
  15. ....................
  16. }
  17. robj *setTypeCreate(sds value) {
  18. //判断value是不是整数类型
  19. if (isSdsRepresentableAsLongLong(value,NULL) == C_OK)
  20. return createIntsetObject(); //若是,则采用intset编码
  21. return createSetObject(); //否则采用默认编码,即是HT
  22. }
  23. robj *createSetObject(void) {
  24. dict *d = dictCreate(&setDictType,NULL); //创建dict
  25. robj *o = createObject(OBJ_SET,d);
  26. o->encoding = OBJ_ENCODING_HT;
  27. return o;
  28. }
  29. robj *createIntsetObject(void) {
  30. intset *is = intsetNew(); //创建intset
  31. robj *o = createObject(OBJ_SET,is);
  32. o->encoding = OBJ_ENCODING_INTSET;
  33. return o;
  34. }

4.zset

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

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

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

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

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

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

  1. typedef struct zset {
  2. dict *dict;
  3. zskiplist *zsl;
  4. } 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插入元素的代码入手: 

  1. //t_zset.c
  2. void zaddCommand(client *c) {
  3. zaddGenericCommand(c,ZADD_IN_NONE);
  4. }
  5. /* This generic command implements both ZADD and ZINCRBY. */
  6. //zadd key score1 member1 score2 memeber2 .......
  7. void zaddGenericCommand(client *c, int flags) {
  8. robj *key = c->argv[1];
  9. robj *zobj;
  10. ...................................
  11. //查找key对应的zset
  12. zobj = lookupKeyWrite(c->db,key);
  13. //若没有,就创建
  14. if (zobj == NULL) {
  15. if (xx) goto reply_to_client; /* No key + XX option: nothing to do. */
  16. if (server.zset_max_ziplist_entries == 0 ||
  17. server.zset_max_ziplist_value < sdslen(c->argv[scoreidx+1]->ptr))
  18. {
  19. zobj = createZsetObject();
  20. } else {
  21. zobj = createZsetZiplistObject();
  22. }
  23. dbAdd(c->db,key,zobj);
  24. }
  25. //插入元素部分
  26. for (j = 0; j < elements; j++) {
  27. double newscore;
  28. score = scores[j];
  29. int retflags = 0;
  30. ele = c->argv[scoreidx+1+j*2]->ptr;
  31. //zsetadd插入元素,如果最初是ziplist,但随着元素数量的增加,就可能需要进行编码转换,即是用其他结构来保存
  32. int retval = zsetAdd(zobj, score, ele, flags, &retflags, &newscore);
  33. }
  34. ..............................
  35. }
  36. //创建zset
  37. robj *createZsetObject(void) {
  38. zset *zs = zmalloc(sizeof(*zs));
  39. robj *o;
  40. zs->dict = dictCreate(&zsetDictType,NULL); //创建dict
  41. zs->zsl = zslCreate(); //创建skiplist
  42. o = createObject(OBJ_ZSET,zs); //给redisObject的type赋值为obj_zset
  43. o->encoding = OBJ_ENCODING_SKIPLIST; //zset的编码是skiplist
  44. return o;
  45. }
  46. robj *createZsetZiplistObject(void) {
  47. unsigned char *zl = ziplistNew();
  48. robj *o = createObject(OBJ_ZSET,zl);
  49. o->encoding = OBJ_ENCODING_ZIPLIST;
  50. return o;
  51. }

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

  1. int zsetAdd(robj *zobj, double score, sds ele, int in_flags, int *out_flags, double *newscore) {
  2. .................................
  3. //判断编码方式,若是ziplist编码,就可能需要进行编码转换
  4. /* Update the sorted set according to its encoding. */
  5. if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
  6. unsigned char *eptr;
  7. //判断当前元素是否存在,已存在则更新score即可
  8. if ((eptr = zzlFind(zobj->ptr,ele,&curscore)) != NULL) {
  9. ...........................................
  10. return 1;
  11. } else if (!xx) {
  12. /* check if the element is too large or the list
  13. * becomes too long *before* executing zzlInsert. */
  14. //元素不存在,则新增,需要先判断ziplist长度是否超标,元素的大小是否已超
  15. if (zzlLength(zobj->ptr)+1 > server.zset_max_ziplist_entries ||
  16. sdslen(ele) > server.zset_max_ziplist_value ||
  17. !ziplistSafeToAdd(zobj->ptr, sdslen(ele)))
  18. {
  19. //表示超出了,则需要转换为skiplist编码
  20. zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
  21. } else {
  22. //没有超出,就直接往ziplist插入
  23. zobj->ptr = zzlInsert(zobj->ptr,ele,score);
  24. if (newscore) *newscore = score;
  25. *out_flags |= ZADD_OUT_ADDED;
  26. return 1;
  27. }
  28. } else {
  29. *out_flags |= ZADD_OUT_NOP;
  30. return 1;
  31. }
  32. }
  33. //本身就是skiplist编码,无需进行转换,就插入即可
  34. if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
  35. ..................................
  36. } else {
  37. serverPanic("Unknown sorted set encoding");
  38. }
  39. return 0; /* Never reached. */
  40. }

 而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结构的插入元素的代码入手:

  1. //命令格式例子:hset key filed value [filed value]...
  2. void hsetCommand(client *c) {
  3. int i, created = 0;
  4. robj *o;
  5. .................................
  6. //1. 判断hash的key是否存在,若不存在就创建新的,默认是采用ziplist编码
  7. if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
  8. //2. 判断是否需要把ziplist编码转换为dict编码
  9. hashTypeTryConversion(o,c->argv,2,c->argc-1);
  10. //3. 遍历每一对field和value,并执行hset命令
  11. for (i = 2; i < c->argc; i += 2)
  12. created += !hashTypeSet(o,c->argv[i]->ptr,c->argv[i+1]->ptr,HASH_SET_COPY);
  13. ..................................
  14. }

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

  1. robj *hashTypeLookupWriteOrCreate(client *c, robj *key) {
  2. robj *o = lookupKeyWrite(c->db,key); //查找是否有该key
  3. if (checkType(c,o,OBJ_HASH)) return NULL; //检查编码类型
  4. if (o == NULL) {
  5. o = createHashObject(); //不存在就创建新的
  6. dbAdd(c->db,key,o);
  7. }
  8. return o;
  9. }
  10. //从代码可见,其默认编码是ziplist
  11. robj *createHashObject(void) {
  12. unsigned char *zl = ziplistNew(); //创建ziplist
  13. robj *o = createObject(OBJ_HASH, zl);
  14. o->encoding = OBJ_ENCODING_ZIPLIST;
  15. return o;
  16. }

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

  1. /* Check the length of a number of objects to see if we need to convert a
  2. * ziplist to a real hash. Note that we only check string encoded objects
  3. * as their string length can be queried in constant time. */
  4. void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
  5. int i;
  6. size_t sum = 0;
  7. //若编码不是ziplist,表明不用进行转换,立即返回
  8. if (o->encoding != OBJ_ENCODING_ZIPLIST) return;
  9. //遍历命令中的field、value参数,即是遍历argv[i]
  10. for (i = start; i <= end; i++) {
  11. if (!sdsEncodedObject(argv[i])) //如果编码不是OBJ_ENCODING_RAW和OBJ_ENCODING_embstring
  12. continue;
  13. size_t len = sdslen(argv[i]->ptr);
  14. //若果field或value超过了hash_max_ziplist_value,则需转换,转成HT编码(dict)
  15. if (len > server.hash_max_ziplist_value) {
  16. hashTypeConvert(o, OBJ_ENCODING_HT);
  17. return;
  18. }
  19. sum += len;
  20. }
  21. if (!ziplistSafeToAdd(o->ptr, sum)) //若ziplist大小超过1G,也转成HT编码
  22. hashTypeConvert(o, OBJ_ENCODING_HT);
  23. }
  24. /* Don't let ziplists grow over 1GB in any case, don't wanna risk overflow in
  25. * zlbytes*/
  26. #define ZIPLIST_MAX_SAFETY_SIZE (1<<30)
  27. int ziplistSafeToAdd(unsigned char* zl, size_t add) {
  28. size_t len = zl? ziplistBlobLen(zl): 0;
  29. if (len + add > ZIPLIST_MAX_SAFETY_SIZE)
  30. return 0;
  31. return 1;
  32. }
  33. //进行编码转换,重点就在hashTypeConvertZiplist函数中
  34. void hashTypeConvert(robj *o, int enc) {
  35. if (o->encoding == OBJ_ENCODING_ZIPLIST) {
  36. hashTypeConvertZiplist(o, enc);
  37. } else if (o->encoding == OBJ_ENCODING_HT) {
  38. serverPanic("Not implemented");
  39. } else {
  40. serverPanic("Unknown hash encoding");
  41. }
  42. }
  43. void hashTypeConvertZiplist(robj *o, int enc) {
  44. if (enc == OBJ_ENCODING_ZIPLIST) {
  45. /* Nothing to do... */
  46. } else if (enc == OBJ_ENCODING_HT) {//需要转换为HT编码的
  47. hashTypeIterator *hi;
  48. dict *dict;
  49. int ret;
  50. hi = hashTypeInitIterator(o);
  51. dict = dictCreate(&hashDictType, NULL);//创建dict
  52. //逐一把key,value赋值给新创建的dict
  53. while (hashTypeNext(hi) != C_ERR) {
  54. sds key, value;
  55. //获取key,value
  56. key = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY);
  57. value = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE);
  58. ret = dictAdd(dict, key, value); //往dict中添加
  59. if (ret != DICT_OK) {
  60. serverLogHexDump(LL_WARNING,"ziplist with dup elements dump",
  61. o->ptr,ziplistBlobLen(o->ptr));
  62. serverPanic("Ziplist corruption detected");
  63. }
  64. }
  65. hashTypeReleaseIterator(hi);
  66. zfree(o->ptr);
  67. o->encoding = OBJ_ENCODING_HT; //更新编码为HT编码
  68. o->ptr = dict; //redisObject对象的ptr指针指向dict
  69. }
  70. }

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

  1. int hashTypeSet(robj *o, sds field, sds value, int flags) {
  2. int update = 0;
  3. //判断编码方式
  4. if (o->encoding == OBJ_ENCODING_ZIPLIST) { //若是ziplist编码
  5. unsigned char *zl, *fptr, *vptr;
  6. zl = o->ptr;
  7. fptr = ziplistIndex(zl, ZIPLIST_HEAD);//找到head位置
  8. if (fptr != NULL) {//head不为空,开始查找field
  9. fptr = ziplistFind(zl, fptr, (unsigned char*)field, sdslen(field), 1);//查找field
  10. if (fptr != NULL) { //表明该field已存在,即是需要进行更新
  11. /* Grab pointer to the value (fptr points to the field) */
  12. vptr = ziplistNext(zl, fptr);//到达下一节点位置,即是value
  13. serverAssert(vptr != NULL);
  14. update = 1;
  15. /* Replace value */ //更新该位置的值value
  16. zl = ziplistReplace(zl, vptr, (unsigned char*)value,
  17. sdslen(value));
  18. }
  19. }
  20. if (!update) { //不存在,则直接push
  21. /* Push new field/value pair onto the tail of the ziplist */
  22. //依次将新的field和value都push到ziplist的尾部
  23. zl = ziplistPush(zl, (unsigned char*)field, sdslen(field),
  24. ZIPLIST_TAIL);
  25. zl = ziplistPush(zl, (unsigned char*)value, sdslen(value),
  26. ZIPLIST_TAIL);
  27. }
  28. o->ptr = zl;
  29. /* Check if the ziplist needs to be converted to a hash table */
  30. //插入了新元素,判断ziplist长度是否超过了,超过则转为HT编码
  31. if (hashTypeLength(o) > server.hash_max_ziplist_entries)
  32. hashTypeConvert(o, OBJ_ENCODING_HT);
  33. } else if (o->encoding == OBJ_ENCODING_HT) { //若是HT编码,直接插入或更新
  34. dictEntry *de = dictFind(o->ptr,field);
  35. ..................................
  36. } else {
  37. serverPanic("Unknown hash encoding");
  38. }
  39. ......................
  40. return update;
  41. }

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。

  1. //命令格式 get key
  2. void getCommand(client *c) {
  3. getGenericCommand(c);
  4. }
  5. //其重点就在lookupKeyReadOrReply函数中
  6. int getGenericCommand(client *c) {
  7. robj *o;
  8. //查找是否有该key
  9. if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL)
  10. return C_OK;
  11. if (checkType(c,o,OBJ_STRING)) {
  12. return C_ERR;
  13. }
  14. addReplyBulk(c,o);
  15. return C_OK;
  16. }
  17. robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
  18. robj *o = lookupKeyRead(c->db, key);
  19. if (!o) SentReplyOnKeyMiss(c, reply);
  20. return o;
  21. }
  22. robj *lookupKeyRead(redisDb *db, robj *key) {
  23. return lookupKeyReadWithFlags(db,key,LOOKUP_NONE);
  24. }
  25. robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
  26. robj *val;
  27. ................
  28. val = lookupKey(db,key,flags);//查找该key
  29. .......................
  30. return val;
  31. ..........................
  32. }
  33. //db.c
  34. robj *lookupKey(redisDb *db, robj *key, int flags) {
  35. dictEntry *de = dictFind(db->dict,key->ptr); //在dict中查找
  36. if (de) {
  37. robj *val = dictGetVal(de);
  38. if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){
  39. if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
  40. updateLFU(val);
  41. } else {
  42. val->lru = LRU_CLOCK();
  43. }
  44. }
  45. return val;
  46. } else {
  47. return NULL;
  48. }
  49. }
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号