专栏首页用户7890857的专栏Redis数据结构——对象
原创

Redis数据结构——对象

Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象,每种对象都用到了至少一种我们前面所介绍的数据结构。

1、Redis数据结构——简单动态字符串-SDS

2、Redis数据结构——链表-linkedlist

3、Redis数据结构——字典-hashtable

4、Redis数据结构——整数集合-intset

5、Redis数据结构——跳跃表-skiplist

6、Redis数据结构——压缩列表-ziplist

跳跃表深入理解

redis 使用对象来表示数据库中的键和值,即每新建一个键值对,至少创建两个对象,使用对象具有以下好处:

1、redis 可以在执行命令前会根据对象的类型判断一个对象释放可以执行给定的命令

2、针对不同的使用场景,为对象设置不同的数据结构实现,从而优化对象在不同场景下的使用效率。

除此之外,redis的对象系统还实现了基于引用计数技术的内存回收机制,当程序不再使用某个对象的时候,这个对象所占用的内存就会被自动释放;另外,redis还通过引用计数技术实现了对象共享机制,这一机制可以在适当条件下,通过让多个数据库键共享同一个对象来节约内存。

最后,redis对象带有访问时间记录信息,该信息可以用于计算数据库键的空转时长,在服务器启用了maxmenory功能的情况下,空转时长较大的那些键可能会优先被服务器删除。

1、对象的类型与编码

redis使用对象来表示数据库中的键和值,每次当我们在redis 的数据库中新创建一个键值对时,我们至少会创建两个对象,一个对象用作键值对的键,另一个对象用于键值对的值。

reids中的每个对象都由一个redisObject结构表示,该结构中和保存数据有关的三个属性如下

typedef struct redisObject{     
//类型     
unsigned type:4;     
//编码     
unsigned encoding:4;     
//指向底层实现数据结构的指针     
void *ptr;    
 ……. 
 }robj 

1.1、类型

对象的type属性记录了对象的类型,这个属性的值是下表中其中的一个。

对于redis数据库保存的键值对来说,键总是一个字符串对象,而值则可以是字符串对象、列表对象、哈希对象、集合对象或者有序集合对象其中一种。

所以我们执行type命令时,命令返回的结果为数据库键对应的值对象的类型,而不是键对象的类型。

2、编码和底层实现

对象ptr指针指向对象的底层实现数据结构,而这些数据结构由对象的encoding属性决定。

encoding属性记录了对象所使用的编码,也即是说这个对象使用了什么数据结构作为对象的底层实现,这个属性可以是下面列出的常量的其中之一

编码常量

编码对应的底层数据结构

REDIS_ENCODING_INT

long类型的整数

REDIS_ENCODING_EMBSTR

embstr编码的简单动态字符串

REDIS_ENCODING_RAW

简单动态字符串

REDIS_ENCODING_HT

字典

REDIS_ENCODING_LINKEDLIST

双向链表

REDIS_ENCODING_ZIPLIST

压缩列表

REDIS_ENCODING_INTSET

整数集合

REDIS_ENCODING_SKIPLIST

跳跃表和字典

每种类型的对象都至少使用了两种不同的编码,每种类型的对象可以使用的编码如下

类型

编码

实现

String

int

整数值实现的字符串对象

String

embstr

sds实现 <=39 字节

String

raw

sds实现 > 39字节

List

ziplist

压缩列表实现

List

linkedlist

双端链表实现

Set

intset

整数集合使用

Set

hashtable

字典实现

Hash

ziplist

压缩列表实现

Hash

hashtable

字典使用

Sorted set

ziplist

压缩列表实现

Sorted set

skiplist

跳跃表和字典

2.1、字符串对象

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

如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面(将void *转换成long),并将字符串对象的编码设置为int。

举个例子,执行以下命令

set number 10086 object encoding number //”int” 

如果字符串对象保存的是一个字符串值,并且这个字符串值的长度大于32字节【每个版本对应的字节不同,最新版本是44字节】,那么字符串对象将使用一个简单动态字符串(sds)来保存这个字符串值,并将对象的编码设置为raw。

如果字符串对象保存的是一个字符串值,并且这个字符串值的长度小于等于32字节,那么字符串对象将使用embstr编码的方式来保存这个字符串值。

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

embstr编码的字符串对象在执行命令时,产生的效果和raw编码的字符串对象执行命令时产生的效果是相同的,但使用embstr编码的字符串对象来保存短字符串值有以下好处:

  1. embstr编码将创建字符串对象所需的内存分配次数从raw编码的两次降为一次。
  2. 释放embstr编码的字符串对象只需要调用一次内存释放函数,而释放raw编码的字符串对象需要调用两次内存释放函数。
  3. 因为embstr编码的字符串对象的所有数据都保存在一块连续的内存里面,所以这种编码的字符串对象比起raw编码的字符串对象能够更好地利用缓存带来的优势。

最后要说的是,可以用long double类型表示的浮点数在redis中也是作为字符串值来保存的。如果我们要保存一个浮点数到字符串对象里面,那么程序会先将这个浮点数转换成字符串值,然后再保存转换所得的字符串值。

2.1.1、编码的转化

int编码的字符串对象和embstr编码的字符串对象在条件满足的情况下,会被转换为raw编码的字符串对象。

Redis没有为embstr编码字符串对象编写任何相应修改功能,所以embstr编码字符串对象实际上是只读的。当我们对embstr编码字符串修改时,先将对象编码从embstr转换成raw。

2.2、列表对象

列表对象的编码可以使ziplist或者linkedlist。

ziplist编码的列表对象使用压缩列表作为底层实现,每个压缩列表节点(entry)保存了一个列表元素。下图就是ziplist编码的列表对象,红框内为存储的数据。

另一方面,linkedlist编码的列表对象使用双端链表作为底层实现,每个双端链表节点都保存了一个字符串对象,而每个字符串对象都保存了一个列表元素,如下图

编码转换

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

  1. 列表对象保存的所有字符串元素的长度都小于64字节
  2. 列表对象保存的元素数量小于512个;

(以上两个条件的上限值可以在配置文件中修改)

2.3、哈希对象

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

ziplist编码的哈希对象使用压缩列表作为底部实现,每当有新的键值对要加入到哈希对象时,程序会先保存了键的压缩列表节点推入到压缩列表表尾,然后再将保存了值的压缩列表节点推入到压缩列表表尾,因此:

1 保存了同一键值对的两个节点总是紧挨在一起,保存键的节点在前,保存值的节点在后

2 先添加到哈希对象中的键值对会被放在压缩列表的表头方向,而后来添加到哈希对象中的键值对会被放在压缩列表的表尾方向。

举个例子,执行如下命令 hset profile name “Tom” hset profile age 25 hset profile career “Programmer”

另一方面,hashtable编码的哈希对象使用字典作为底层实现,哈希对象中的每个键值对都使用一个字典键值对来保存

1 字典的每个键都是一个字符串对象,对象中保存了键值对的键

2 字典的每个值都是一个字符串对象,对象中保存了键值对的值

编码转换

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

1 哈希对象保存的所有键值对的键和值字符串长度都小于64字节。

2 哈希对象保存的键值对数量小于512个

不能满足这两个条件的哈希对象需要使用hashtable编码(这两个条件的上限值可以在redis配置中修改。)

2.4、集合对象

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

intset编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都被保存在整数集合里面。

另一方面,hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象包含了一个集合元素,而字典的值则全部被设置为null。

编码转换

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

1 集合对象保存的所有元素都是整数值

2 集合对象保存的元素数量不超过512个

不能满足这两个条件的集合对象使用hashtable编码。

2.5、有序集合对象

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

ziplist编码的压缩列表对象使用压缩列表作为底层实现,每个集合元素使用两个金爱在一起的压缩列表节点保存,第一个节点保存元素的成员,而第二个元素则保存元素的分值。

压缩列表内的集合元素按分值从小到大金星排序,分值较小的元素被防止在靠近表头的方向,而分值较大的元素责备防止在靠近表尾的方向,如下图。

skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表:

typedef struct zset{ zskiplist *zsl; dict *dict; }zset

zset结构中的zsl跳跃表按分值从小到大保存所有集合元素,每个跳跃表节点都保存了一个集合元素:跳跃表节点的object属性保存了元素的成员,而跳跃表节点的score属性则保存了元素的分值。通过这个跳跃表,程序可以对有序集合进行范围性操作,比如zrank、zrange等命令就是基于跳跃表api来实现的。

除此之外,zset结构中的dict字典为有序集合创建了一个从成员到分值的映射,字典中的每个键值对都保存了一个集合元素:字典的键保存了元素的成员,而字典的值则保存了元素的分值。通过这个字典,程序可以用O(1)复杂度查找给定成员的分值,ZSCORE命令就是根据这一特性实现的,而很多其他有序集合命令都在实现的内部用到了这一特性。

有序集合每个元素的成员都是一个字符串对象,而每个元素的分值都是一个double类型的浮点数。值得一提的是,虽然zset结构同时使用跳跃表和字典来保存有序集合元素,但这两种数据结构都会通过指针来共享相同的成员和分值,所以同时使用跳跃表和字典来保存集合元素不会产生任何重复成员或分值,也不会因此而浪费额外的内存。

编码的转换

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

1 有序集合同时保持的元素数量小于128个

2 有序集合保存的所有元素成员的长度都小于64字节

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

关注作者,阅读全部精彩内容

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Redis 数据结构与对象编码 (Object Encoding)

    为了将性能优化到极致,redis 作者为每种数据结构提供了不同的实现方式,以适应特定应用场景。

    烂猪皮
  • Redis 的底层数据结构(对象)

    目前为止,我们介绍了 redis 中非常典型的五种数据结构,从 SDS 到 压缩列表,这都是 redis 最底层、最常用的数据结构,相信你也掌握的不错。

    Single
  • Redis 数据结构和对象系统,有这 12 张图就够了!

    Redis 是一个开源的 key-value 存储系统,它使用六种底层数据结构构建了包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象的对象系统。

    CSDN技术头条
  • Redis 的基础数据结构(三)对象

    前两篇文章介绍了 Redis 的基本数据结构动态字符串,链表,字典,跳跃表,压缩链表,整数集合,但是使用过 Redis 的同学会发现,平时根本没有使用过这些数...

    用户2060079
  • Redis的数据结构和对象系统是怎么设计的?

    Redis是一个开源的 key-value 存储系统,它使用六种底层数据结构构建了包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象的对象系统。今天我们...

    用户5927304
  • 十二张图带你了解 Redis 的数据结构和对象系统

    Redis是一个开源的 key-value 存储系统,它使用六种底层数据结构构建了包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象的对象系统。今天我们...

    程序员历小冰
  • 十二张图带你了解 Redis 的数据结构和对象系统

    Redis是一个开源的 key-value 存储系统,它使用六种底层数据结构构建了包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象的对象系统。今天我们...

    程序员历小冰
  • Redis对象底层数据结构实现概述

    Redis没有直接使用C语言传统的字符串表示(以空字符结尾的字符数组,以下简称C字符串),而是自己构建了一种名为简单动态字符串(simple dynamic s...

    kentian
  • Redis对象底层数据结构实现概述

    | 导语 本文是一篇redis读书笔记,主要内容整理自 Redis设计与实现。如果你想快速了解redis底层数据结构,相信这篇文章会有所帮助。 文章主要分为两...

    腾讯Bugly
  • 数据结构与对象

    简单动态字符串(simple dynamic string,SDS),结构体非常简单

    用户7962184
  • 细品Redis高性能数据结构之hash对象

    上一节讲Redis的高性能字符串结构SDS,今天我们来看一下redis的hash对象。

    居士
  • 《redis设计与实现》1-数据结构与对象篇

    学习完《redis设计与实现》前面关于数据结构与对象的章节,以上问题都能得到解答。你也能了解到redis作者如此的煞费苦心设计了这么多丰富的数据结构,目的就是优...

    kinnylee
  • Redis数据结构

    主要内容来源于书籍Redis实战(Redis In Action),这篇只是用来记录自己学习的过程,因为刚学所以很浅显,适合初学者哈 Redis数据结构 5种数...

    GavinZhou
  • Redis 数据结构

    String 数据结构是简单的 key-value 类型,value 不仅可以是 String,也可以是数字。

    happyJared
  • Redis数据结构

    我们可以将元素添加到列表(支持从头部添加也支持从尾部添加),也可以从列表中移除并获取某个元素(支持从头部移除也支持从尾部移除),还可以读取整个列表的元素。

    Action
  • Redis-1.Redis数据结构

    自增自减命令 自增自减命令只能作用于整数,如果对不存在的键或者保存了空串的键执行自增/自减操作,那么会将这个键的值当作0处理,如果对无法解释为整数或者浮点数的...

    悠扬前奏
  • Redis:07---Redis数据结构

    这里我不会讲的太深入,深入的内容会在后续章节,每个数据结构作为一个专题来具体讲。

    用户3479834
  • 抽象数据结构与表抽象数据结构表

    抽象数据结构 抽象数据结构(ADT)是一些操作的集合,集合了一些必要且重用性高的操作,这些操作在一个项目中只被编写一次。抽象数据结构只定义操作的存在,并不定义操...

    月见樽
  • Redis数据结构总结

    字符串 string 是 Redis 最简单的数据结构。Redis 所有的数据结构都是以唯一的 key 字符串作为名称,然后通过这个唯一 key 值来获取相应的...

    用户5325874

扫码关注云+社区

领取腾讯云代金券