Redis 五种对象
程序员文章站
2022-07-12 16:48:41
...
阅读本节前需要阅读 Redis 对象系统概览一节。此外,对于各个对象底层中所用到的数据结构,请阅读前面的相关博文笔记,此次不再赘述。
在 Redis 的五种类型的对象中,字符串对象是唯一一种会被其他四种类型对象嵌套的对象。字符串对象的编码可以是 int、raw 或者 embstr。
如果一个字符串对象保存的是整数值,并且该值可以使用 long 型表示,则该对象结构的 ptr 属性里面保存的就是整数值(会将 void* 转换成 long),并同时将其 encoding 属性设置为 int。但如果该整数值超过了 long 型所能表示的数值范围,则使用下面的 embstr 或者 raw 编码方式来表示。
如果字符串对象保存的是一个字符串值,并且其长度大于 32 字节,则字符串对象将使用一个简单动态字符串(SDS)结构来保存,同时将该对象的 encoding 属性设置为 raw。而如果字符串值的长度小于等于 32 字节,则使用的就是 embstr 编码的方式。
embstr 编码和 raw 编码一样,都使用 redisObject 结构和 sdshdr 结构来表示字符串对象,但 raw 编码会调用两次内存分配函数来分别创建这两个结构,而 embstr 编码则是调用一次内存分配函数来分配一块连续的空间,空间中依次包含 redisObject 和 sdshdr 两个结构。因此 embstr 编码的字符串对象在释放内存时也会少调用一次内存释放函数,同时使用连续的内存空间也能让字符串对象更好地利用缓存带来的优势。
此外,可以用 long double 类型表示的浮点数在 Redis 中也是作为字符串值来保存的,Redis 会在操作需要的时候将其转换回浮点数。至于使用的是 embstr 还是 raw 编码方式,同样需要根据将其转换成字符串后的长度而定。
但是一个字符串对象使用的编码方式并非是一成不变的,int 和 embstr 编码的字符串对象在条件满足的情况下,会被转换为 raw 编码的字符串对象。
对于 int 编码的字符串对象来说,当执行了一些命令(如 APPEND),使得该对象保存的不再是整数值时,对象的编码就会从 int 变为 raw。而对于 embstr 编码的字符串对象来说,由于 Redis 没有为之编写任何相应的修改接口,因此 embstr 编码的字符串对象实际上是只读的,对这样的字符串对象执行任何修改命令,都会先将其转换为 raw 编码的后再进行修改,最终的结果都是一个 raw 编码的字符串对象。下面的命令片段说明了这几点。
列表对象的编码可以是 ziplist 或者 linkedlist。ziplist 编码的列表对象使用压缩列表作为底层实现,每个压缩列表节点保存了一个列表元素。linkedlist 编码的列表对象使用双端链表作为底层实现,每个双端链表节点都保存了一个字符串对象,而每个字符串对象都保存了一个列表元素。
当列表对象同时满足以下两个条件时,列表对象使用 ziplist 来编码,否则使用 linkedlist 编码(这两个条件值可以使用 list-max-ziplist-value 和 list-max-ziplist-entries 选项来配置):
(1)列表对象保存的所有字符串元素的长度都小于 64 字节。
(2)列表对象保存的元素数量小于 512 个。
当这两个条件有任何一个不满足时,使用 ziplist 编码的列表对象就会被转换成使用 linkedlist 编码,原本保存在压缩列表里的所有列表元素都会被转移并保存到双端链表里面。下面的代码片段阐明了这一点。
哈希对象的编码可以是 ziplist 或者 hashtable。ziplist 编码的哈希对象使用压缩列表作为底层实现。每当有新的键值对加入时,程序会依次将保存了键和值的压缩表节点推入到压缩表表尾。而 hashtable 编码的哈希对象使用字典作为底层实现,每个键值对都使用一个字典键值对来保存,而每个字典的键和值都是保存在一个字符串对象中。
类似于列表对象,使用 ziplist 编码的哈希对象也需要同时满足以下两个条件(同样可以通过 hash-max-ziplist-value 和 hash-max-ziplist-entries 选项来配置):
(1)哈希对象保存的所有键值对的键和值的字符串长度都小于 64 字节。
(2)哈希对象保存的键值对数量小于 512 个。
如果不能同时满足,则原本保存在压缩列表里的所有键值对都会被转移并保存到字典里面,对象的编码也会从 ziplist 变为 hashtable。
集合对象的编码可以是 intset 或者 hashtable。intset 编码的集合对象使用整数集合作为底层实现。hashtable 编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,表示一个集合元素,而字典的值则全部被设置为 NULL。
集合对象同时满足以下两个条件时,才使用 intset 编码,否则使用 hashtable 编码:
(1)集合对象保存的元素都是整数。
(2)集合对象的元素数量不超过 512 个(该值可以使用 set-max-intset-entries 选项修改)。
有序集合对象的编码可以是 ziplist 或者 skiplist。ziplist 编码的压缩列表对象使用压缩列表作为底层实现,每个集合元素按分值从小到大使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个则保存元素的分值。skiplist 编码的有序集合对象使用如下的 zset 结构作为底层实现,一个 zset 结构同时包含一个字典和一个跳跃表:
zset 结构中的 zsl 跳跃表按分值从小到大保存了所有集合元素,每个跳跃表节点都保存了一个集合元素:object 属性保存了元素的成员,score 属性则保存了元素的分值。通过该跳跃表,Redis 可以使用如 ZRANK、ZRANGE 等命令来对有序集合进行范围型操作。
zset 结构中的 dict 字典为有序集合创建了一个从成员到分值的映射,其中的每个键值对都保存了一个集合元素:键保存了元素的成员,值保存了元素的分值。通过该字典,Redis 可以使用如 ZSCORE 等命令在 O(1) 复杂度内查找给定成员的分值。
有序集合每个元素的成员都是一个字符串对象,而每个元素的分值都是一个 double 类型的浮点数。值得一提的是,为了节约内存,zset 结构中的跳跃表和字典其实是通过指针来共享相同元素的成员和分数。
使用 ziplist 编码的有序集合对象需要同时满足以下两个条件:
(1)有序集合的元素数量小于 128 个。
(2)有序集合的所有元素成员的长度都小于 64 字节。
当然,这两个值也可以分别通过 zset-max-ziplist-entries 和 zset-max-ziplist-value 选项来配置。
在 Redis 的五种类型的对象中,字符串对象是唯一一种会被其他四种类型对象嵌套的对象。字符串对象的编码可以是 int、raw 或者 embstr。
如果一个字符串对象保存的是整数值,并且该值可以使用 long 型表示,则该对象结构的 ptr 属性里面保存的就是整数值(会将 void* 转换成 long),并同时将其 encoding 属性设置为 int。但如果该整数值超过了 long 型所能表示的数值范围,则使用下面的 embstr 或者 raw 编码方式来表示。
如果字符串对象保存的是一个字符串值,并且其长度大于 32 字节,则字符串对象将使用一个简单动态字符串(SDS)结构来保存,同时将该对象的 encoding 属性设置为 raw。而如果字符串值的长度小于等于 32 字节,则使用的就是 embstr 编码的方式。
embstr 编码和 raw 编码一样,都使用 redisObject 结构和 sdshdr 结构来表示字符串对象,但 raw 编码会调用两次内存分配函数来分别创建这两个结构,而 embstr 编码则是调用一次内存分配函数来分配一块连续的空间,空间中依次包含 redisObject 和 sdshdr 两个结构。因此 embstr 编码的字符串对象在释放内存时也会少调用一次内存释放函数,同时使用连续的内存空间也能让字符串对象更好地利用缓存带来的优势。
此外,可以用 long double 类型表示的浮点数在 Redis 中也是作为字符串值来保存的,Redis 会在操作需要的时候将其转换回浮点数。至于使用的是 embstr 还是 raw 编码方式,同样需要根据将其转换成字符串后的长度而定。
但是一个字符串对象使用的编码方式并非是一成不变的,int 和 embstr 编码的字符串对象在条件满足的情况下,会被转换为 raw 编码的字符串对象。
对于 int 编码的字符串对象来说,当执行了一些命令(如 APPEND),使得该对象保存的不再是整数值时,对象的编码就会从 int 变为 raw。而对于 embstr 编码的字符串对象来说,由于 Redis 没有为之编写任何相应的修改接口,因此 embstr 编码的字符串对象实际上是只读的,对这样的字符串对象执行任何修改命令,都会先将其转换为 raw 编码的后再进行修改,最终的结果都是一个 raw 编码的字符串对象。下面的命令片段说明了这几点。
redis> SET number 10086 OK redis> OBJECT ENCODING number "int" redis> APPEND number " is a good number" (integer) 23 redis> OBJECT ENCODING number "raw" redis> SET msg "hello world" OK redis> OBJECT ENCODING msg "embstr" redis> APPEND msg " again!" (integer) 18 redis> OBJECT ENCODING msg "raw"
列表对象的编码可以是 ziplist 或者 linkedlist。ziplist 编码的列表对象使用压缩列表作为底层实现,每个压缩列表节点保存了一个列表元素。linkedlist 编码的列表对象使用双端链表作为底层实现,每个双端链表节点都保存了一个字符串对象,而每个字符串对象都保存了一个列表元素。
当列表对象同时满足以下两个条件时,列表对象使用 ziplist 来编码,否则使用 linkedlist 编码(这两个条件值可以使用 list-max-ziplist-value 和 list-max-ziplist-entries 选项来配置):
(1)列表对象保存的所有字符串元素的长度都小于 64 字节。
(2)列表对象保存的元素数量小于 512 个。
当这两个条件有任何一个不满足时,使用 ziplist 编码的列表对象就会被转换成使用 linkedlist 编码,原本保存在压缩列表里的所有列表元素都会被转移并保存到双端链表里面。下面的代码片段阐明了这一点。
# 所有元素的长度都小于 64 字节 redis> RPUSH blah "hello" "world" "again" (integer) 3 redis> OBJECT ENCODING blah "ziplist" # 将一个 65 字节长的元素推入到列表对象中 redis> RPUSH blah "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww" (integer) 4 redis> OBJECT ENCODING blah "linkedlist" # 列表对象包含 512 个元素 redis> EVAL "for i=1, 512 do redis.call('RPUSH', KEYS[1], i)end" 1 "integers" (nil) redis> LLEN integers (integer) 512 redis> OBJECT ENCODING integers "ziplist" # 再向列表对象推入一个新元素,使其元素数量达到 513 个 redis> RPUSH integers 513 (integer) 513 redis> OBJECT ENCODING integers "linkedlist"
哈希对象的编码可以是 ziplist 或者 hashtable。ziplist 编码的哈希对象使用压缩列表作为底层实现。每当有新的键值对加入时,程序会依次将保存了键和值的压缩表节点推入到压缩表表尾。而 hashtable 编码的哈希对象使用字典作为底层实现,每个键值对都使用一个字典键值对来保存,而每个字典的键和值都是保存在一个字符串对象中。
类似于列表对象,使用 ziplist 编码的哈希对象也需要同时满足以下两个条件(同样可以通过 hash-max-ziplist-value 和 hash-max-ziplist-entries 选项来配置):
(1)哈希对象保存的所有键值对的键和值的字符串长度都小于 64 字节。
(2)哈希对象保存的键值对数量小于 512 个。
如果不能同时满足,则原本保存在压缩列表里的所有键值对都会被转移并保存到字典里面,对象的编码也会从 ziplist 变为 hashtable。
集合对象的编码可以是 intset 或者 hashtable。intset 编码的集合对象使用整数集合作为底层实现。hashtable 编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,表示一个集合元素,而字典的值则全部被设置为 NULL。
集合对象同时满足以下两个条件时,才使用 intset 编码,否则使用 hashtable 编码:
(1)集合对象保存的元素都是整数。
(2)集合对象的元素数量不超过 512 个(该值可以使用 set-max-intset-entries 选项修改)。
有序集合对象的编码可以是 ziplist 或者 skiplist。ziplist 编码的压缩列表对象使用压缩列表作为底层实现,每个集合元素按分值从小到大使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个则保存元素的分值。skiplist 编码的有序集合对象使用如下的 zset 结构作为底层实现,一个 zset 结构同时包含一个字典和一个跳跃表:
typedef struct zset{ zskiplist *zsl; dict *dict; }zset;
zset 结构中的 zsl 跳跃表按分值从小到大保存了所有集合元素,每个跳跃表节点都保存了一个集合元素:object 属性保存了元素的成员,score 属性则保存了元素的分值。通过该跳跃表,Redis 可以使用如 ZRANK、ZRANGE 等命令来对有序集合进行范围型操作。
zset 结构中的 dict 字典为有序集合创建了一个从成员到分值的映射,其中的每个键值对都保存了一个集合元素:键保存了元素的成员,值保存了元素的分值。通过该字典,Redis 可以使用如 ZSCORE 等命令在 O(1) 复杂度内查找给定成员的分值。
有序集合每个元素的成员都是一个字符串对象,而每个元素的分值都是一个 double 类型的浮点数。值得一提的是,为了节约内存,zset 结构中的跳跃表和字典其实是通过指针来共享相同元素的成员和分数。
使用 ziplist 编码的有序集合对象需要同时满足以下两个条件:
(1)有序集合的元素数量小于 128 个。
(2)有序集合的所有元素成员的长度都小于 64 字节。
当然,这两个值也可以分别通过 zset-max-ziplist-entries 和 zset-max-ziplist-value 选项来配置。
下一篇: mongodb自启动