文章目录

  • 对象的类型与编码
  • 字符串对象
    • 编码转换
  • 列表对象
    • 编码转换
  • 哈希对象
    • 编码转换
  • 集合对象
    • 编码转换
  • 有序集合对象
    • 为什么zset同时使用跳跃表和字典来实现?
    • 编码转换

Redis 的底层数据结构主要包括简单动态字符串(SDS)、双端链表、字典、跳跃表、整数集合、压缩列表。Redis 并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象,每一种对象类型都至少采用了两种编码,不同的编码使用的底层数据结构也不同。通过这五种不同类型的对象,Redis 可以在执行命令之前,根据对象的类型来判断一个对象是否可以执行给定的命令。使用对象的另一个好处是,我们可以针对不同的使用场景,为对象设置多种不同的数据结构实现,从而优化对象在不同场景下的使用效率

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

对象的类型与编码

Redis 使用对象来表示数据库中的键和值,每次当我们在 Redis 的数据库中新创建一个键值对时,我们至少会创建两个对象,一个对象用作键值对的键(键对象),另一个对象用作键值对的值(值对象),其中键总是一个字符串对象, 而值则可以是字符串对象、列表对象、哈希对象、集合对象或者有序集合对象的其中一种。

因此当我们对一个数据库键执行 TYPE 命令时,命令返回的结果为数据库键对应的值对象的类型,而不是键对象的类型。

Redis 中的每个对象都由一个 redisObject 结构表示,该结构中和保存数据有关的三个属性分别是 type 属性、 encoding 属性和 ptr 属性:

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

type 属性记录了对象的类型,对于 Redis 来说,键对象总是字符串类型,值对象可以是任意支持的类型。因此,当我们说Redis键采用哪种对象类型的时候,指的是对应的值采用哪种对象类型。

类型常量 对象类型名称
REDIS_STRING 字符串对象
REDIS_LIST 列表对象
REDIS_HASH 哈希对象
REDIS_SET 集合对象
REDIS_ZSET 有序集合对象

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 跳跃表和字典

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

类型 编码 对象
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 使用跳跃表和字典实现的有序集合对象。

通过encoding属性来设定对象所使用的编码,而不是为特定类型的对象关联一种固定的编码,是为了实现同一对象类型,支持不同的底层实现。这样就能根据不同场景,使用不同的底层数据结构,进而极大提升 Redis 的灵活性和效率。

字符串对象

字符串对象的编码可以是 intraw 或者 embstr

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

  • 如果字符串对象保存的是一个长度小于等于 39 字节的字符串,则编码类型为 embstr ,底层数据结构为 embstr 编码的 SDS 。

  • 如果字符串对象保存的是一个长度大于 39 字节的字符串,则编码类型为 raw ,底层数据结构为 SDS 。

注意 3.2 版本长度边界变为 45

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

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

  1. embstr 编码将创建字符串对象所需的内存分配次数从 raw 编码的两次降低为一次。
  2. 释放 embstr 编码的字符串对象只需要调用一次内存释放函数, 而释放 raw 编码的字符串对象需要调用两次内存释放函数。
  3. 因为 embstr 编码的字符串对象的所有数据都保存在一块连续的内存里面, 所以这种编码的字符串对象比起 raw 编码的字符串对象能够更好地利用缓存带来的优势。
redis> SET story "Long, long, long ago there lived a king ..."
OK
redis> STRLEN story
(integer) 45
redis> OBJECT ENCODING story
"raw"redis> SET msg "hello"
OK
redis> OBJECT ENCODING msg
"embstr"redis> SET number 10086
OK
redis> OBJECT ENCODING number
"int"

编码转换

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

  • 对于 int 编码的字符串对象来说,如果我们向对象执行了一些命令(如append),使得这个对象保存的不再是整数值, 而是一个字符串值, 那么字符串对象的编码将从 int 变为 raw

  • 对于embstr编码来说,只要我们修改了字符串的值,总会变成一个 raw 编码的字符串对象。

因为 Redis 没有为 embstr 编码的字符串对象编写任何相应的修改程序 (只有 int 编码和 raw 编码的字符串对象有这些程序),所以 embstr 编码的字符串对象实际上是只读的:当我们对 embstr 编码的字符串对象执行任何修改命令时,程序会先将对象的编码从 embstr 转换成 raw ,然后再执行修改命令。

列表对象

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

ziplist 编码的列表对象使用压缩列表作为底层实现, 每个压缩列表节点(entry)保存了一个列表元素。

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

注意, linkedlist 编码的列表对象在底层的双端链表结构中包含了多个字符串对象, 这种嵌套字符串对象的行为在哈希对象、集合对象和有序集合对象中都会出现, 字符串对象是 Redis 五种类型的对象中唯一一种会被其他四种类型对象嵌套的对象

编码转换

当列表对象保存的所有字符串元素的长度都小于 64 字节,且元素个数小于 512 个时,列表对象采用的是 ziplist 编码,否则使用 linkedlist 编码

可以通过配置文件修改默认值

哈希对象

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

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

hashtable 编码的哈希对象使用字典作为底层实现,字典是一种用于保存键值对的数据结构,Redis 的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,每个哈希表节点保存的就是一个键值对。

编码转换

当哈希对象保存的所有键值对的键和值的字符串长度都小于64个字节,并且键值对数量小于512个时,使用ziplist编码,否则使用hashtable编码

可以通过配置文件修改默认值

集合对象

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

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

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

编码转换

当集合对象保存的所有元素都是整数值,并且元素数量不超过512个时,使用intset编码,否则使用hashtable编码

有序集合对象

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

ziplist 编码的有序集合对象使用压缩列表作为底层实现, 每个集合元素使用两个紧挨在一起的压缩列表节点来保存, 第一个节点保存元素的成员(member), 而第二个元素则保存元素的分值(score)。

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 结构同时使用跳跃表和字典来保存有序集合元素, 但这两种数据结构都会通过指针来共享相同元素的成员和分值, 所以同时使用跳跃表和字典来保存集合元素不会产生任何重复成员或者分值, 也不会因此而浪费额外的内存

为什么zset同时使用跳跃表和字典来实现?

在理论上来说, 有序集合可以单独使用字典或者跳跃表的其中一种数据结构来实现, 但无论单独使用字典还是跳跃表, 在性能上对比起同时使用字典和跳跃表都会有所降低。

  • 如果只使用字典来实现: 虽然能以 O(1) 复杂度查找成员的分值, 但因为字典以无序的方式来保存集合元素, 所以每次在执行范围型操作 —— 比如 ZRANK 、 ZRANGE 等命令时, 程序都需要对字典保存的所有元素进行排序, 完成这种排序需要至少 O(Nlog N) 时间复杂度, 以及额外的 O(N) 内存空间 (因为要创建一个数组来保存排序后的元素)。

  • 如果只使用跳跃表来实现:虽然跳跃表执行范围型操作有优势, 但因为没有了字典, 所以根据成员查找分值的操作复杂度将从 O(1) 上升为 O(log N)

总结:为了提升性能,让有序集合的查找和范围型操作都尽可能快地执行, Redis 选择了同时使用字典和跳跃表两种数据结构来实现有序集合

编码转换

当有序集合保存的所有元素成员长度都小于64字节,且元素数量小于128个时,使用ziplist编码,否则使用skiplist编码

参考资料

《Redis 设计与实现》——黄健宏

Redis设计与实现——对象相关推荐

  1. 《Redis 设计与实现》读书笔记-Redis 对象

    一.Redis 对象 1.1 Redis 对象简介 Redis 使用对象来表示数据库中键和值,当我们在数据库中存储一个键值对时,至少会创建两个对象,一个对象用于存储键值对的键,另一个对象用于存储键值对 ...

  2. 《Redis设计与实现》笔记|SDS动态字符串|链表字典跳跃表整数集合压缩列表结构|redis中的对象|数据库原理|RDB持久化|AOF持久化|事件与多路利用模型|发布订阅原理|事务原理|慢查询日志

    <Redis设计与实现>笔记 前记: 参考配套网站:http://redisbook.com 带注释的源码地址:https://github.com/huangz1990/redis-3. ...

  3. Redis设计与实现之SDS和链表

    Redis的数据结构与对象 redis数据库里的键值对都是由object组成的,数据库建总是一个字符串对象. redis数据库里的值可以是字符串对象,列表对象,哈希对象,集合对象,有序集合对象中的一种 ...

  4. startindex 不能大于字符串长度_玩转云端丨redis的5种对象与8种数据结构之字符串对象(下)...

    引言 本文是对<redis设计与实现(第二版)>中数据结构与对象相关内容的整理与说明.本篇文章只对对象结构,1种对象--字符串对象.以及字符串对象所对应的两种编码--raw和embstr, ...

  5. 《redis 设计与实现》读书笔记

    大家好,我是烤鸭:     <redis 设计与实现>,读书笔记. 第一部分 数据结构与对象 第2章 简单动态字符串 Redis 使用SDS 作为字符串表示. O(1) 复杂度获取字符串长 ...

  6. Redis教程:数据对象分析(二)

    目录 1 类型检查与命令多态 2 内存回收 3 对象共享 4 对象的时转时长 1 类型检查与命令多态 Redis中用于操作键的命令基本上可以分为两种类型:其中一种命令可以对任何类型的键执行,比如DEL ...

  7. Redis教程:数据对象分析(一)

    目录 1 五种基本数据对象 2 数据结构对象分析 2.1 类型 2.2 编码与底层实现 2.3 编码转换 1 五种基本数据对象 图1 redis数据对象 2 数据结构对象分析 Redis中的每个对象都 ...

  8. 《Redis设计与实现 黄建宏 著》阅读笔记目录(持续更新)

    <Redis设计与实现 黄建宏 著>第3章 该书基于Redis2.9,即Redis3.0开发版编写 目录: 第一部分 数据结构与对象 01 简单动态字符串 02 链表 03 字典 04 跳 ...

  9. Redis设计与实现详解二:Redis数据库实现

    Redis设计与实现详解一:数据结构与对象 Redis设计与实现详解三:多机功能实现 Redis设计与实现详解四:其他单机功能 数据库 服务器中的数据库 Redis服务器将所有数据库都保存在服务器状态 ...

最新文章

  1. excel:隔行选取复制功能
  2. Mac下修改环境变量
  3. Office+SharePoint+Server+2007+部署图示指南
  4. Python处理大数据量文本数据思路
  5. Nginx卡在登录页面不断跳转如何解决?(登不进登录页面)ip_hash机制(还是没解决)
  6. jq防止冲突,后退链式,队列操作
  7. kdevelp 导入makefile工程
  8. Oacle 开窗函数 分析函数
  9. HBase的布隆过滤器详解
  10. Java 中正确获取中文字符串长度
  11. Codevs2157 配对
  12. class CT where T : new() 泛型类约束
  13. mysql性能优化较佳实践_MySQL性能优化的21个最佳实践 和 mysql使用索引
  14. 【easy!】LeetCode 14. Longest Common Prefix
  15. 9. grouped product
  16. Java实现斐波那契数列的两种方法
  17. 1. Windows网络编程(C++ Socket编程)
  18. ESP8266-Arduino编程实例-L3G4200D三轴陀螺仪驱动
  19. 贤鱼的刷题日常-【c++】P7909 [CSP-J 2021] 分糖果
  20. matlab simulink 六自由度机械臂模糊控制pid

热门文章

  1. GaussDB 如何启动和关闭数据库的归档模式
  2. 暗棋单机版_中国象棋暗棋下载_中国象棋暗棋安卓版下载 v1.6.8 安卓单机版_飞飞巴士下载...
  3. 工程中脉冲函数 c语言,怎样用C语言画出二阶系统单位脉冲响应函数的动态曲线...
  4. mysql主从复制(一):一主多从
  5. Communication-Efficient Federated Learning for Wireless Edge Intelligence in IoT
  6. 开学季:20本Python经典书单
  7. 【Niagara 04】Tridium N4使用——生成报警信息
  8. 如何判断List 集合和Map 集合是否为空
  9. Ubuntu/Deepin下Python3.8出现SSL错误的解决方案
  10. MySQL基础(附练习题+答案)