Redis高级应用--对象的实现原理

对象的类型

Redis有5中基本数据类型,使用TYPE命令,即可查看对象类型:

1
2
3
4
127.0.0.1:6379> SET name gavin
OK
127.0.0.1:6379> TYPE name
string
对象 TYPE类型输出
字符串对象 “string”
列表对象 “list”
哈希对象 “hash”
集合对象 “set”
有序集合对象 “zset”

对象的编码

Redis使用OBJECT ENCODING命令查看对象内部实现数据结构:

1
2
3
4
5
6
7
8
127.0.0.1:6379> SET name gavin
OK
127.0.0.1:6379> SET age 10
OK
127.0.0.1:6379> OBJECT ENCODING name
"embstr"
127.0.0.1:6379> OBJECT ENCODING age
"int"
对象底层数据结构 OBJECT ENCODING命令输出
整数 int
embstr编码的简单动态字符串 embstr
简单动态字符串 raw
字典 hashtable
双端链表 linkedlist
压缩列表 ziplist
整数集合 intset
跳跃表和字典 skiplist
快速列表 quicklist

字符串对象

字符串对象编码有三种int、embstr和raw

int

如果字符串保存的是整数,长度小于等于20字节,并且能转为整形,则编码为int

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
len = sdslen(s);
//判断字符串长度是否小于20,并且s为long类型
if (len <= 20 && string2l(s,len,&value)) {
/* This object is encodable as a long. Try to use a shared object.
* Note that we avoid using shared integers when maxmemory is used
* because every object needs to have a private LRU field for the LRU
* algorithm to work well. */
if ((server.maxmemory == 0 ||
!(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
value >= 0 &&
value < OBJ_SHARED_INTEGERS)
{
decrRefCount(o);
incrRefCount(shared.integers[value]);// 如果数字在0到10000之间,使用大小为10000的共享数组
return shared.integers[value]; //直接返回共享数组中的地址
} 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);
return createStringObjectFromLongLongForValue(value);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 用到的其他定义
#define OBJ_SHARED_INTEGERS 10000
extern struct sharedObjectsStruct shared;
struct sharedObjectsStruct {
robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *pong, *space,
*colon, *queued, *null[4], *nullarray[4], *emptymap[4], *emptyset[4],
*emptyarray, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
*outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr,
*masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr,
*busykeyerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
*unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *unlink,
*rpop, *lpop, *lpush, *rpoplpush, *zpopmin, *zpopmax, *emptyscan,
*multi, *exec,
*select[PROTO_SHARED_SELECT_CMDS],
*integers[OBJ_SHARED_INTEGERS], //定义大小为10000的数组
*mbulkhdr[OBJ_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */
*bulkhdr[OBJ_SHARED_BULKHDR_LEN]; /* "$<value>\r\n" */
sds minstring, maxstring;
};

embstr和raw

embstr和raw的分界线是44字节,如果大于44字节,使用raw,小于等于44字节使用embstr编码

1
2
3
4
5
6
7
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44     
robj *createStringObject(const char *ptr, size_t len) {
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) // 字符串长度小于44,使用EmbeddedString
return createEmbeddedStringObject(ptr,len);
else
return createRawStringObject(ptr,len); //字符串大于44,使用Raw
}

列表对象

列表对象在3.2版本之前使用ziplist和linkedlist两种编码,在3.2版本之后使用quicklist编码。

quicklist

1
2
3
4
127.0.0.1:6379> LPUSH list 1 2 3
(integer) 3
127.0.0.1:6379> OBJECT ENCODING list
"quicklist"

哈希对象

哈希对象编码使用ziplist和hashtable

  • 哈希对象保存的键值对的键和值得字符串长度都小于64字节使用ziplist
  • 哈希对象的键值对数量小于512个使用ziplist
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
127.0.0.1:6379> hset book name 1234567890123456789012345678901234567890123456789012345678901234
(integer) 0
127.0.0.1:6379> OBJECT ENCODING book
"ziplist"
127.0.0.1:6379> hset book name 12345678901234567890123456789012345678901234567890123456789012345
(integer) 0
127.0.0.1:6379> OBJECT ENCODING book
"hashtable"
127.0.0.1:6379> EVAL "for i=1, 512 do redis.call('HSET', KEYS[1], i, i)end" 1 "numbers"
(nil)
127.0.0.1:6379> HLEN numbers
(integer) 512
127.0.0.1:6379> OBJECT ENCODING numbers
"ziplist"
127.0.0.1:6379> HMSET numbers "key" "value"
OK
127.0.0.1:6379> HLEN numbers
(integer) 513
127.0.0.1:6379> OBJECT ENCODING numbers
"hashtable"

集合对象

集合对象编码使用intset和hashtable两种

  • 集合对象保存的都是整数,但是不超过512个,使用intset编码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
127.0.0.1:6379> sadd numbers 1 2 3 4 5
(integer) 5
127.0.0.1:6379> OBJECT ENCODING numbers
"intset"
127.0.0.1:6379> sadd name "h" "gavin"
(integer) 2
127.0.0.1:6379> OBJECT ENCODING name
"hashtable"
127.0.0.1:6379> EVAL "for i=1, 512 do redis.call('SADD', KEYS[1], i)end" 1 "numbers"
(nil)
127.0.0.1:6379> SCARD numbers
(integer) 512
127.0.0.1:6379> OBJECT ENCODING numbers
"intset"
127.0.0.1:6379> SADD numbers 12312
(integer) 1
127.0.0.1:6379> OBJECT ENCODING numbers
"hashtable"

有序集合对象

有序集合对象编码使用ziplist和skiplist

  • 有序集合保存元素数量小于128个使用ziplist
  • 有序集合保存元素成员长度小于64字节使用ziplist
1
2
3
4
5
127.0.0.1:6379> ZADD price 1  apple123456789012345678901234567890123456789012345678901234567890
(integer) 1
127.0.0.1:6379> OBJECT ENCODING price
"skiplist"