该博客是读《redis设计与实现》后的内容笔记整理
数据结构与对象
一、简单动态字符串:SDS
Redis使用C字符串作为字面量,但大多数情况下,使用SDS(Simple Dynamic String)作为字符串表示。
1. SDS是Redis的默认字符串存储类型,是一种抽象类型
2. 有以下优点:
- 常数复杂度获取字符串长度(获取字符串长度的复杂度为0(1)
- 杜绝缓冲区溢出(API是安全的,不会造成缓冲区溢出
- 减少修改字符串长度时所需的内存重分配次数(修改字符串长度N次最多需要执行N次内存重分配
- 二进制安全(可以保存文本或者二进制数据
- 兼容部分C字符串函数(可以使用部分<string.h>库中的函数
3.SDS的主要操作API:
二、链表
1. 链表的应用:列表键、发布与订阅、慢查询、监视器等
2. Redis的链表实现:
- 双端链表
- 每个链表节点由一个listNode结构表示,每个节点都有一个只想前置节点和后置节点的指针
- 是无环链表,因为链表表头节点的前置节点和表尾的后置节点都指向NULL
- Redis通过为链表设置不同的类型特定函数,来保存各种不同类型的值
3. 操作链表和链表节点的API
三、字典
1. Redis中的字典使用哈希表作为底层实现
- 每个字典有两个哈希表,一个平时使用,另一个尽在rehash时使用
2. 当字典被用作数据库的底层实现或哈希键的底层实现时,Redis用MurmurHash2算法来计算键的哈希值
3. 在对哈希表使用链地址法来解决键冲突,被分配到同一个索引上的多个键值对会连接成一个单向列表
在对哈希表进行扩展或收缩时,程序需要将现有哈希表包含的所有键值对rehash到哈希表里
- rehash过程并不是一次性完成,而是渐进式地完成
4.字典的主要操作API
五、跳跃表
1. 跳跃表是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。
2. Redis使用跳跃表作为有序集合键的底层实现之一
- 如果一个有序集合包含的元素数量比较多
- 又或者有序集合中元素的成员是比较长的字符串
- Redis就会使用跳跃表作为有序集合键的底层实现
3. 跳跃表实现由zskiplist和zskiplistNode两个结构组成
- zskiplist用于保存跳跃表信息,比如表头节点、表尾节点、长度
- zskiplistNode则用于表示跳跃表的节点
4. 每个跳跃表节点的层高都是1至32之间的随机数
5. 在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的成员对象必须是唯一的
6. 跳跃表的节点按照分值大小进行排序,当分值相同时,节点按照成员对象的大小进行排序
7. 跳跃表API
六、整数集合
- 是集合键的底层实现之一
- 当一个集合只包含整数值元素,并且这个集合的元素数量不多时,就会使用整数集合作为集合键的底层实现
- 可保存类型:int16_t、int32_t、int64_t的整数值,并且保证集合中不会出现重复元素
1. 整数集合的底层实现为数组
- 这个数组以有序、无重复的方式保存集合元素
- 在有需要时, 程序会根据新添加元素的类型,改变这个数组的类型
2. 升级操作为整数集合带来了操作上的灵活性,并尽可能的节约了内存
3. 整数集合只支持升级操作,不支持降级操作
4. 整数集合API
七、压缩列表
- 是列表键和哈希键的底层实现之一
- 当一个列表键只包含少量列表项,并且每个列表项是小整数值或长度较短的字符串,Redis就会用压缩列表来做列表键的底层实现
1. 压缩列表是为节约内存而开发的顺序型数据结构
2. 压缩列表可以包含多个节点,每个节点可以保存一个字节数组或整数值
3. 添加新节点到压缩列表,或者从压缩列表删除节点,可能会引发连锁更新操作,但这种操作出现几率不高
4. 压缩列表API
八、对象
- 上边介绍了Redis用到的主要数据结构:简单动态字符串(SDS)、双端链表、字典、压缩列表、整数集合等,
- 但它并没有直接使用这些数据结构来实现键值对数据库
- 而是基于这些数据结构创建了一个对象系统,
- 这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象
- 每种对象都用到了至少一种数据结构
1. 字符串对象
- 字符串对象的编码:int、raw、embstr
2. 列表对象
- 列表对象的编码:ziplist、linkedlis
3. 哈希对象
- 哈希对象的编码:ziplist、hashtable
- ziplist编码的哈希对象使用压缩列表作为底层实现
- 每当有新的键值对要加入到哈希对象时,程序会先将保存了键的压缩列表节点推入到压缩列表表尾,
- 再将保存了值的压缩列表节点推入到压缩列表表尾
- 因此,保存了同一键值对的两个节点总是紧挨在一起,保存键的节点在前,保存值的节点在后;
- 先添加到哈希对象中的键值对会被放在压缩列表的表头方向,而后来添加到哈希对象中的键值对会被放在压缩列表的表尾方向。
- hashtable编码的哈希对象使用字典作为底层实现
- 哈希对象中的每个键值对都使用一个字典键值对来保存
- 字典的每个键都是一个字符串对象,对象中保存了键值对的键;
- 字典的每个值都是一个字符串对象,对象中保存了键值对的值
- 哈希对象中的每个键值对都使用一个字典键值对来保存
- ziplist编码的哈希对象使用压缩列表作为底层实现
4.集合对象
- 集合对象的编码:intset、hashtable
5.有序集合对象
- 有序集合的编码:ziplist、skiplist
6.类型检查与命令多态
- 可对如何类型的键执行: DEL、EXPIRE、RENAME、TYPE、OBJECT等
- 只能对特定类型的键执行:
- 字符串:SET、GET、APPEND、STRLEN…
- 哈希键:HDEL、HSET、HGET、HLEN…
- 列表键:RPUSH、LPOP、LINSERT、LLEN…
- 集合键:SADD、SPOP、SINTER、SCARD…
- 有序集合键:ZADD、ZCARD、ZRANK、ZSCORE…
1)类型检查通过redisObject结构的type属性实现
- 在执行一个类型特定命令前,服务器先检查输入数据库键的值对象是否为执行命令多虚的类型,是则对键执行知道的命令
- 不是,服务器将拒绝执行命令,并向客户端返回类型错误
2)多态命令的实现
- 根据值对象的编码方式,选择正确的命令实现代码来执行命令
7. 内存回收
- 因为C语言并不具备自动内存回收功能,所以Redis在自己的对象系统中构建了一个引用计数(reference counting)技术实现的内存回收机制
- 通过这一机制,程序可以通过跟踪对象的引用计数信息,在适当的时候自动释放对象并进行内存回收。
8.对象共享
- 除了用于实现引用计数内存回收机制之外,对象的引用计数属性还带有对象共享的作用
- 尽管共享更复杂的对象可以节约更多的内存,但受到CPU时间的限制,Redis只对包含整数值的字符串对象进行共享
9. 对象的空转时长
- 除了前面介绍过的type、encoding、ptr和refcount四个属性之外,redisObject结构包含的最后一个属性为lru属性,该属性记录了对象最后一次被命令程序访问的时间
- OBJECT IDLETIME命令可以打印出给定键的空转时长,这一空转时长就是通过将当前时间减去键的值对象的lru时间计算得出的
OBJECT IDLETIME命令的实现是特殊的,这个命令在访问键的值对象时,不会修改值对象的lru属性
- 如果服务器打开了maxmemory选项,并且服务器用于回收内存的算法为volatile-lru或者allkeys-lru,那么当服务器占用的内存数超过了maxmemory选项所设置的上限值时,空转时长较高的那部分键会优先被服务器释放,从而回收内存。
10.重点
- Redis数据库中的每个键值对的键和值都是一个对象。
- Redis共有字符串、列表、哈希、集合、有序集合五种类型的对象,每种类型的对象至少都有两种或以上的编码方式,不同的编码可以在不同的使用场景上优化对象的使用效率。
- 服务器在执行某些命令之前,会先检查给定键的类型能否执行指定的命令,而检查一个键的类型就是检查键的值对象的类型。
- Redis的对象系统带有引用计数实现的内存回收机制,当一个对象不再被使用时,该对象所占用的内存就会被自动释放。
- Redis会共享值为0到9999的字符串对象。
- 对象会记录自己的最后一次被访问的时间,这个时间可以用于计算对象的空转时间。