redis对象

一、redis对象

1.1,介绍

上文介绍了redis的数据结构,从底层源码入手,图示了redis底层用到的数据结构。实际上redis并没有直接用到上文提到的SDS、双端链表、字典、压缩链表等,而是基于这些数据结构创建了一个对象系统,这个系统包含了字符串对象、列表对象、哈希对象、集合对象和有序集合对象五种基本类型的对象,每种对象都用到了至少一种我们上文提到的数据结构。redis中的这个对象,可以简单理解为我们编程语言中的对象,不过它更像是一种组合、封装和适配。本文将详细介绍redis的对象系统,并分析五种对象类型的底层实现和特性。

二、对象的类型与编码

redis使用对象来表示数据库中的键和值,每次我们在数据库中创建键值对时,都会创建两个对象,即键对象和值对象。使用命令创建一个简单的键值对,set name zys数据库中就存在了key=name、value=zys这两个对象。键值对中的键就是包含字符串“name”的字符串对象,键值对中的值就是包含字符串“zys”的字符串对象。

redis对象的定义如下:

1
typedef struct redisObject {
2
		usigned type:4;//类型
3
		usigned encoding:4;//编码
4
		void *ptr;//指向底层实现数据结构的指针
5
		...
6
}

redisObject有三个关键属性:类型type、编码encoding、指向底层实现的指针ptr。下面我们就来介绍这三个属性各自的含义。

2.1,对象的类型

type字段记录了redisObject的类型,这个类型其实指的就是这个对象类型,到底是个什么对象:字符串对象、列表对象、哈希对象、集合对象和有序集合对象。对于redis保存的键值对中的键而言,始终是一个字符串对象;而键值对中的键是上述五种类型中的一种。redis中有一个命令type可以查询键对应的值的对象类型。如下:

1
-- 字符串类型对象
2
127.0.0.1:6379> set name zys
3
OK
4
127.0.0.1:6379> type name
5
string
6
-- 列表类型对象
7
127.0.0.1:6379> rpush nums 1 2 3 4
8
(integer) 4
9
127.0.0.1:6379> type nums
10
list
11
-- 哈希类型对象
12
127.0.0.1:6379> HMSET p name zys age 18
13
OK
14
127.0.0.1:6379> type p
15
hash
16
-- 集合类型对象
17
127.0.0.1:6379> sadd names zys lww
18
(integer) 2
19
127.0.0.1:6379> type names
20
set
21
-- 有序集合类型对象
22
127.0.0.1:6379> zadd price 4 apple 3 banana
23
(integer) 2
24
127.0.0.1:6379> type price
25
zset

下表为不同类型值对象映射相关信息

对象名称 类型常量 type命令返回值
字符串对象 REDIS_STRING “string”
列表对象 REDIS_LIST “list”
哈希对象 REDIS_HASH “hash”
集合对象 REDIS_SET “set”
有序集合对象 REDIS_ZSET “zset”

2.2,编码和底层实现

对象的ptr指向了对象底层实现的数据结构,而这些数据结构则由encoding决定。encoding指定了底层数据结构的类型,也就是说明了这个对象是由什么数据结构实现的。不同类型的对象底层编码对应如下表:

对象类型 编码 对象 object输出
REDIS_STRING REDIS_ENCODING_INT 使用整数值实现的字符串对象 int
REDIS_STRING REDIS_ENCODING_EMBSTR 使用embstr编码简单动态字符串实现的字符串对象 embstr
REDIS_STRING REDIS_ENCODING_RAW 使用简单动态字符串实现的字符串对象 raw
REDIS_LIST REDIS_ENCODING_ZIPLIST 使用压缩列表实现的列表对象 zippiest
REDIS_LIST REDIS_ENCODING_LINKEDLIST 使用双端链表实现的列表对象 linked list
REDIS_HASH REDIS_ENCODING_ZIPLIST 使用压缩列表实现的哈希对象 zippiest
REDIS_HASH REDIS_ENCODING_HT 使用字典实现的哈希对象 hash table
REDIS_SET REDIS_ENCODING_INTSET 使用整数集合实现的集合对象 inset
REDIS_SET REDIS_ENCODING_HT 使用字典实现的集合对象 hash table
REDIS_ZSET REDIS_ENCODING_ZIPLIST 使用压缩列表实现的集合对象 zippiest
REDIS_ZSET REDIS_ENCODING_SKIPLIST 使用跳跃表和字典实现的有序集合对象 skiplist

我们可以使用命令object encoding(查看对象的encoding属性)简单实验一下:

1
-- embstr编码简单动态字符串
2
127.0.0.1:6379> set name zys
3
OK
4
127.0.0.1:6379> object encoding name
5
"embstr"
6
-- 简单动态字符串
7
127.0.0.1:6379> set long_name "zhaoyansheng is very handsome.what do you think?"
8
OK
9
127.0.0.1:6379> object encoding long_name
10
"raw"
11
-- 整数值
12
127.0.0.1:6379> set num 1
13
OK
14
127.0.0.1:6379> object encoding num
15
"int"
16
127.0.0.1:6379> sadd nums 1 2 3
17
(integer) 3
18
127.0.0.1:6379> object encoding nums
19
"intset"
20
127.0.0.1:6379> object encoding nums
21
"hashtable"

通过测试我们可以知道,通过encoding来指定对象实际使用的数据结构,而不是一个对象类型指定一种数据结构,而是根据特定的条件来进行匹配使用。接下来,我们分别介绍五种对象类型在不同情况下的编码情况。

2.2.1,字符串对象

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

如果一个字符串对象保存的是整数值,可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里,此时对象的编码为int。

int-object

如果一个字符串对象保存的是一个字符串值,并且这个字符串的长度大于39字节,那么字符串对象将使用简单动态字符串SDS来保存这个字符串值,此时对象的编码为raw。

raw-object

如果一个字符串对象保存的是一个字符串值,并且这个字符串的长度小于等于39字节,那么字符串对象将使用embstr来保存这个字符串值,此时对象的编码为embstr。embstr编码主要是针对短字符串的一种优化编码方式,和raw编码一样,都使用redisObject结构和sdshdr结构来表示字符串对象,但是raw会进行两次内存分配函数分别创建redisObject和sdshdr,而embstr编码则通过一次内存分配函数来分配一块连续的空间,空间中依次包含redisObject和sdshdr两个结构。

embstr-object

2.2.2,列表对象

列表对象编码是ziplist或者linkedlist。

ziplist使用压缩列表作为底层实现,每个压缩节点保存了一个列表元素。假设列表有三个元素分别为“1”、“test”、“9”,那么通过ziplist保存如下:

ziplist-object

linkedlist编码底层使用双端链表,每个双端链表节点保存了一个字符串对象,每个字符串对象保存一个队列元素。上述列表通过linkedlist保存如下(注:使用StringObject代表字符串对象,具体实现细节同上2.2.1,字符串对象):

linkedlist-object

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

  1. 列表对象保存的所有字符串元素的长度都小于64字节
  2. 列表对象保存的元素数量小于512个;不能满足这两个条件的列表对象会使用linkedlist编码。

2.2.3,哈希对象

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

当使用ziplist作为底层实现时,键值对的键先加入到压缩列表表尾,然后键值对的值再加入到表尾。也就是键值对的键和值相邻;先添加哈希对象的键值对靠近表头,后添加进入哈希对象的键值对靠近表尾。使用ziplist作为底层实现的哈希对象图示如下:

zl-hash-object

当使用hashtable作为底层实现时,哈希对象的每一个键值对都作为字典的键值对保存。字典的键和值都是字符串对象,对象分别保存了键值对的键和值。使用hashtable作为底层实现的哈希对象图示如下:

dict-hash-object

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

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

2.2.4,集合对象

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

当使用intset作为底层实现时,集合对象包含的所有元素都被保存在整数集合里面。图示如下:

1
redis> sadd mums 1 2 3
2
(integer) 3

intset-set-object

intset-set-object

当使用hashtable作为底层实现时,字典的每个键都是一个字符串对象,每个字符串对象包含了一个集合元素,而字典的值则全部设置为NULL。图示如下:

1
redis> sadd strs zhao yan sheng
2
(integer) 3

dict-set-object

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

  1. 集合对象保存的所有元素都是整数值
  2. 集合对象保存的元素数量不超过512个;不能满足时使用hashtable编码。

2.2.5,有序集合对象

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

当使用ziplist编码作为底层实现时,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员(member),第二个元素保存元素的分值(score)。压缩列表内集合元素按照分值从小到大排序,分值较小的元素放置在表头,分值较大的放置在表尾。图示和上述哈希对象ziplist表示类似,这里就不再给出。

zset结构持有了跳跃表zsl和字典dict两个数据结构来实现有序集合对象。每个跳跃表节点保存了一个集合元素,跳跃表节点的object属性保存了元素的成员,而跳跃表节点的score属性则保存了元素的分值。通过这个跳跃表,可以对集合中的元素进行范围查询、排序等功能。dict字典结构则记录了有序集合中元素成员到分值的一个映射,通过这个字典结构,可以快速获取每个成员的分值。下面举例图示说明这个结构:

skiplist-zset-object

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

  1. 有序集合保存的元素数量小于128个
  2. 有序集合保存的所有元素成员的长度都小于64字节;不满足上述情况的有序集合对象使用skiplist编码。
坚持原创分享