基础环境:基于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_INT | long类型的整数 |
REDIS_ENCODING_HT | 字典 |
REDIS_ENCODING_ZIPLIST | 压缩列表 |
REDIS_ENCODING_LINKEDLIST | 双端链表 |
REDIS_ENCODING_INTSET | 整数集合 |
REDIS_ENCODING_SKIPLIST | 跳跃表 |
REDIS_ENCODING_EMBSTR | embstr编码的简单动态字符串 |
2.3、对象的类型和编码
每种类型的对象都可能使用了多种不能编码实现
对象 | 对象type属性值 | TYPE命令的输出 |
---|---|---|
REDIS_STRING | REDIS_ENCODING_INT | 使用整数指事项的字符串对象 |
REDIS_STRING | REDIS_ENCODING_EMBSTR | 使用embstr编码的简单动态字符串实现的字符串对象 |
REDIS_STRING | REDIS_ENCODING_RAW | 使用简单动态字符串实现的字符串对象 |
REDIS_LIST | REDIS_ENCODING_ZIPLIST | 使用压缩列表实现的列表对象 |
REDIS_LIST | REDIS_ENCODING_LINKEDLIST | 使用双端列表实现的列表对象 |
REDIS_HASH | REDIS_ENCODING_ZIPLIST | 使用压缩列表事项的哈希对象 |
REDIS_HASH | REDIS_ENCODING_HT | 使用字典实现的哈希对象 |
REDIS_SET | REDIS_ENCODING_INTSET | 使用整数集合事项的集合对象 |
REDIS_SET | REDIS_ENCODING_HT | 使用字典实现的集合对象 |
REDIS_ZSET | REDIS_ENCODING_ZIPLIST | 使用压缩列表事项的有序集合对象 |
REDIS_ZSET | REDIS_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"
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两个结构。
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"
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编码的列表对象
4.3、linkedlist编码的列表对象
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"
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
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"
6.3、hash table编码的哈希对象
[6]> sadd monkey "a" "b" "c"
(integer) 3
[6]> type monkey
set
[6]> object encoding monkey
"hashtable"
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