Redis—对象

170 阅读7分钟

基础环境:基于redis3.0的源码

1、对象

Redis使用对象来表示数据库中的键和值,每次当我们在Redis的数据库中新创建一个键值对时,我们至少会创建两个对象,一个对象用作键值对的键(键对象),另一个对象用作键值对的值(值对象)。
而这些对象就是使用简单动态字符串(SDS)、双端链表、字典、压缩列表、整数集合等数据类型组成的。

2、对象的基础结构

// 文件位置:/src/redis.h
typedef struct redisObject {
    // 类型
    unsigned type:4;
    // 编码
    unsigned encoding:4;
    // 4位,对象最后一次被命令程序访问的时间,与内存回收有关
    // LRU time (relative to global lru_clock) or
    // LFU data (least significant 8 bits frequency and most significant 16 bits access time).
    unsigned lru:REDIS_LRU_BITS; 
    // 引用计数。当refcount为0的时候,表示该对象已经不被任何对象引用,则可以进行垃圾回收了
    int refcount;
    // 指向底层实现数据结构的指针
    void *ptr;
} robj;

// 对象类型
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4

// 对象的编码
#define REDIS_ENCODING_RAW 0     /* Raw representation */
#define REDIS_ENCODING_INT 1     /* Encoded as integer */
#define REDIS_ENCODING_HT 2      /* Encoded as hash table */
#define REDIS_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define REDIS_ENCODING_INTSET 6  /* Encoded as intset */
#define REDIS_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define REDIS_ENCODING_EMBSTR 8  /* Embedded sds string encoding */

2.1、对象的类型

对象对象type属性值TYPE命令的输出对象特点
字符串对象REDIS_STRING"string"字符串类型的值最大能存储 512MB
列表对象REDIS_LIST"list"列表是简单的字符串列表,按照插入顺序排序,可以在一个元素的头部或尾部增加新的元素
哈希对象REDIS_HASH"hash"哈希是一个string类型的key和value的映射表,哈希特别适合用于存储对象
集合对象REDIS_SET"set"集合是string类型的无序集合;集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)
有序集合对象REDIS_ZSET"zset"与集合不同的是每个有序集合元素都会关联一个double类型的分数,redis正是通过分数来为有序集合中的成员进行从小到大的排序

2.2、对象的编码

对象的编码表示该对象使用了什么数据结构作为对象的底层实现。

编码常量编码所对应的底层数据结构
REDIS_ENCODING_RAW简单动态字符串
REDIS_ENCODING_INTlong类型的整数
REDIS_ENCODING_HT字典
REDIS_ENCODING_ZIPLIST压缩列表
REDIS_ENCODING_LINKEDLIST双端链表
REDIS_ENCODING_INTSET整数集合
REDIS_ENCODING_SKIPLIST跳跃表
REDIS_ENCODING_EMBSTRembstr编码的简单动态字符串

2.3、对象的类型和编码

每种类型的对象都可能使用了多种不能编码实现

对象对象type属性值TYPE命令的输出
REDIS_STRINGREDIS_ENCODING_INT使用整数指事项的字符串对象
REDIS_STRINGREDIS_ENCODING_EMBSTR使用embstr编码的简单动态字符串实现的字符串对象
REDIS_STRINGREDIS_ENCODING_RAW使用简单动态字符串实现的字符串对象
REDIS_LISTREDIS_ENCODING_ZIPLIST使用压缩列表实现的列表对象
REDIS_LISTREDIS_ENCODING_LINKEDLIST使用双端列表实现的列表对象
REDIS_HASHREDIS_ENCODING_ZIPLIST使用压缩列表事项的哈希对象
REDIS_HASHREDIS_ENCODING_HT使用字典实现的哈希对象
REDIS_SETREDIS_ENCODING_INTSET使用整数集合事项的集合对象
REDIS_SETREDIS_ENCODING_HT使用字典实现的集合对象
REDIS_ZSETREDIS_ENCODING_ZIPLIST使用压缩列表事项的有序集合对象
REDIS_ZSETREDIS_ENCODING_SKIPLIST使用跳跃表事项的有序集合对象

3、字符串对象

字符串对象的编码可以是int、raw或者embstr。

文件位置:src/object.c
#define REDIS_ENCODING_EMBSTR_SIZE_LIMIT 39
robj *createStringObject(char *ptr, size_t len) {
    if (len <= REDIS_ENCODING_EMBSTR_SIZE_LIMIT)
        return createEmbeddedStringObject(ptr,len);
    else
        return createRawStringObject(ptr,len);
}

robj *createStringObjectFromLongLong(long long value) {
    robj *o;
    if (value >= 0 && value < REDIS_SHARED_INTEGERS) {
        incrRefCount(shared.integers[value]);
        o = shared.integers[value];
    } else {
        if (value >= LONG_MIN && value <= LONG_MAX) {
            o = createObject(REDIS_STRING, NULL);
            o->encoding = REDIS_ENCODING_INT;
            o->ptr = (void*)((long)value);
        } else {
            o = createObject(REDIS_STRING,sdsfromlonglong(value));
        }
    }
    return o;
}

根据源码我们可以发现当值是正数类型,并且可以使用long类型表示那么会设置编码为int,如果保存的是字符串类型并且长度小于REDIS_ENCODING_EMBSTR_SIZE_LIMIT则是embstr类型,否则是raw类型。

3.1、编码为int类型

[6]> set monkey 1
OK
[6]> type monkey
string
[6]> object encoding monkey
"int"

image.png

3.2、编码为embstr类型

[6]> get   monkey
"it is message it length exceeds 32 bytes"
[6]> object encoding   monkey
"embstr"
[6]> type   monkey
string

embstr编码是专门用于保存短字符串的一种优化编码方式,这种编码和raw编码一样,都使用redisObject结构和sdshdr结构来表示字符串对象,但raw编码会调用两次内存分配函数来分别创建redisObject结构和sdshdr结构,而embstr编码则通过调用一次内存分配函数来分配一块连续的空间,空间中依次包含redisObject和sdshdr两个结构。

image.png

3.3、编码为raw类型

[6]> append monkey " monkey"
(integer) 47
[6]> get monkey
"it is message it length exceeds 32 bytes monkey"
[6]> type monkey
string
[6]> object encoding monkey
"raw"

image.png

4、列表对象

列表对象可以是ziplist或者linkedlist

文件路径:src/redis.h
// 列表对象存储元素数量
#define REDIS_LIST_MAX_ZIPLIST_ENTRIES 512
// 列表对象保存的字符串元素长度
#define REDIS_LIST_MAX_ZIPLIST_VALUE 64

// 文件位置:src/config.c
} else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
    server.list_max_ziplist_entries = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
    server.list_max_ziplist_value = memtoll(argv[1], NULL);


// 文件路径:src/rdb.c
/* Use a real list when there are too many entries */
if (len > server.list_max_ziplist_entries) {
    o = createListObject();
} else {
    o = createZiplistObject();
}

4.1、编码转换

根据源码可以看到当列表对象同时满足以下两个条件,列表对象使用ziplist编码,其他情况都使用linkedlist编码:

  • 列表对象保存的所有字符创元素的长度都小于list_max_ziplist_value(64)
  • 列表对象保存的元素数量小于list_max_ziplist_entries(512)

同时根据config.c文件的源码可以看到,无论长度值,还是数量的值都可以再配置文件中进行设置

4.2、ziplist编码的列表对象

image.png

4.3、linkedlist编码的列表对象

image.png

5、哈希对象

哈希对象的编码可以是ziplist或者hashtable

// 文件路径:src/redis.h
#define REDIS_HASH_MAX_ZIPLIST_ENTRIES 512
#define REDIS_HASH_MAX_ZIPLIST_VALUE 64

// 文件位置:src/config.c
} else if (!strcasecmp(argv[0],"hash-max-ziplist-entries") && argc == 2) {
    server.hash_max_ziplist_entries = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"hash-max-ziplist-value") && argc == 2) {
   server.hash_max_ziplist_value = memtoll(argv[1], NULL);

// 文件位置: src/rdb.c
o = createHashObject();

/* Too many entries? Use a hash table. */
if (len > server.hash_max_ziplist_entries)
    hashTypeConvert(o, REDIS_ENCODING_HT);

5.1、编码转换

当哈希对象可以同时满足以下两个条件时,哈希对象使用ziplist编码:

  • 哈希对象保存的所有键值对的键和值的字符串长度都小于hash_max_ziplist_value(64)字节;
  • 哈希对象保存的键值对数量小于hash_max_ziplist_entries(512)个;不能满足这两个条件的哈希对象需要使用hashtable编码。

同时根据config.c文件的源码可以看到,无论长度值,还是数量的值都可以再配置文件中进行设置

5.2、ziplist编码的哈希对象

[6]> hset monkey url www.baidu.com
(integer) 1
[6]> type monkey
hash
[6]> object encoding monkey
"ziplist"

image.png

5.3、hash table编码的哈希对象

[6]> hset monkey url weread.qq.com/web/reader/d35323e0597db0d35bd957bk283328802332838023a7529.weread.qq.com/web/reader/d35323e0597db0d35bd957bk283328802332838023a7529
(integer) 0
[6]> object encoding monkey
"hashtable"
[6]> type monkey
hash

image.png

6、集合对象

集合对象的编码可以是intset或者hashtable

// 文件位置: src/redis.h
#define REDIS_SET_MAX_INTSET_ENTRIES 512

// 文件位置: src/config.c
} else if (!strcasecmp(argv[0],"set-max-intset-entries") && argc == 2) {
    server.set_max_intset_entries = memtoll(argv[1], NULL);

// 文件位置:src/rdb.c
/* Use a regular set when there are too many entries. */
if (len > server.set_max_intset_entries) {
    o = createSetObject();
    /* It's faster to expand the dict to the right size asap in order
        * to avoid rehashing */
    if (len > DICT_HT_INITIAL_SIZE)
        dictExpand(o->ptr,len);
} else {
    o = createIntsetObject();
}

6.1、编码转换

当集合对象可以同时满足以下两个条件时,对象使用intset编码

  • 集合对象保存的所有元素都是整数值;
  • 集合对象保存的元素数量不超过set_max_intset_entries(512)个。

6.2、intset编码的哈希对象

[6]> sadd monkey 1 2 3
(integer) 3
[6]> type monkey
set
[6]> object encoding monkey
"intset"

image.png

6.3、hash table编码的哈希对象

[6]> sadd monkey "a" "b" "c"
(integer) 3
[6]> type monkey
set
[6]> object encoding monkey
"hashtable"

image.png

7、有序集合对象

有序集合对象的编码可以是ziplist或者skiplist

// 文件位置: src/redis.h
#define REDIS_ZSET_MAX_ZIPLIST_ENTRIES 128
#define REDIS_ZSET_MAX_ZIPLIST_VALUE 64

// 文件位置: src/config.c
} else if (!strcasecmp(argv[0],"zset-max-ziplist-entries") && argc == 2) {
    server.zset_max_ziplist_entries = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"zset-max-ziplist-value") && argc == 2) {
    server.zset_max_ziplist_value = memtoll(argv[1], NULL);


// 文件位置:src/t_zset.c
if (server.zset_max_ziplist_entries == 0 ||
    server.zset_max_ziplist_value < sdslen(c->argv[3]->ptr))
{
    zobj = createZsetObject();
} else {
    zobj = createZsetZiplistObject();
}

7.1、编码的转换

当有序集合对象可以同时满足以下两个条件时,对象使用ziplist编码:

  • 有序集合保存的元素数量小于128个;
  • 有序集合保存的所有元素成员的长度都小于64字节;

7.2、ziplist编码实现有序集合对象

[6]> zadd monkey 1 "a"
(integer) 1
[6]> type monkey
zset
[6]> object encoding monkey
"ziplist"

7.3、skiplist编码实现有序集合对象

[6]> zadd monkey 1 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
(integer) 1
[6]> object encoding monkey
"skiplist"
[6]> type monkey
zset

参考

Redis设计与实现