您的位置:首页 > 数据库 > Redis

redis 笔记01 简单动态字符串、链表、字典、跳跃表、整数集合、压缩列表

2016-01-04 00:54 716 查看
文中内容摘自《redis设计与实现》

简单动态字符串

1. Redis只会使用C字符串作为字面量,在大多数情况下,Redis使用SDS(Simple Dynamic String,简单动态字符串)作为字符串表示。

2. SDS包含以下三个属性:

1). free : 记录buf数组中未使用字节的数量

2). len : 记录buf数组中已使用自己的数量,等于SDS所保存字符串的长度

3). buf : char类型数组,用于保存字符串,最后一个字节是一个空字符'\0'

3. SDS遵循C字符串以空字符串结尾的惯例,保存空字符的1字节空间不计算在SDS的len属性里面。遵循空字符串惯例的好处是,SDS可以直接重用一部分C字符串函数库里的函数。

4. 比起C字符串,SDS具有以下优点:

1). 常数复杂度获取字符串长度,通过len属性直接获取。C字符串需要遍历所有字符串内容。

2). 杜绝缓冲区内存溢出。SDS会在插入新数据前判断是否需要扩充内存。C字符串需要手动扩充。

3). 减少修改字符串长度时所需的内存重分配次数。主要有两种方式: 空间预分配 和 惰性空间释放

4). 二进制安全。SDS可以保存文本或者二进制数据,而C字符串只能保存文本数据。

5). 兼容部分C字符串函数。

5. 二进制安全:虽然数据库一般用于保存文本数据,但使用数据库来保存二进制数据的场景也不少见。为了确保Redis可以适用于各种不同的使用场景,SDS的API都是二进制安全的,

所有SDS API都会以二进制的方式来处理SDS存放在buf数组里的数据。程序不会对其中的数据做任何的限制、过滤、或者假设,数据写入时什么样的,他被读取就是什么样的。

链表:

1. 链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可以通过增删节点来灵活地调整链表的长度。

2. 链表节点主要含有三个属性:

1). * prev : 前置节点

2). * next : 后置节点

3). * value : 节点的值

3. 链表主要包含以下几个属性:

1). * head : 表头节点

2). * tail : 表尾节点

3). len :链表包含的节点数量

4). dup函数:用于复制链表节点所保存的值。

5). free函数:用于释放节点所保存的值

6). match函数:用于对比链表节点所保存的值和另一个输入值是否相等

4. Redis的链表实现的特征可以总结如下:

1). 双端:链表节点带有prev和next指针,获取某个节点的前置节点和后置节点的复杂度都是O(1)

2). 无环:表头节点的prev指针和表尾节点的next指针都指向NULL,对链表的访问以NULL为节点。

3). 带表头指针和表尾指针:通过list结构的head指针和tail指针,程序获取链表的表头节点和表尾节点的复杂度为O(1)

4). 带链表长度计数器:程序使用list结构的len属性来对list持有的链表节点进行计数,程序获取链表中节点数量复杂度为O(1)

5). 多态:链表节点使用void*指针来保存节点值,并且可以通过list结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。

5. 链表被广泛用于redis的各种功能,比如列表键、发布与订阅、慢查询、监视器等

字典:

1. 字典,又称为符号表,关联数组或映射,是一种用于保存键值对的抽象数据结构。

2. Redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对。

3. 哈希表主要包含以下几个属性:

1). table : 哈希表数组

2). size : 哈希表大小

3). sizemask : 哈希表大小掩码,用于计算索引值;总是等于size - 1

4). used : 该哈希表已有节点的数量

4. 哈希表节点主要包含以下几个属性:

1). key : 键

2). v : 值。其中键值对的值可以是一个指针,或者是一个unit64_t的整数,又或者是一个int64_t整数

3). next : 指向另一个哈希表节点的指针,这个指针可以将多个哈希值相同的键值对连接在一起,以此来解决键冲突的问题。

5. 字典主要包含以下几个属性:

1). type : 一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数,Redis会为用途不同的字典设置不同的类型特定函数。

2). privdata : 保存了需要传给哪些类型特定函数的可选参数。

3). ht : 包含两个项的数组,数组中的每个项都是一个dictht哈希表,一般情况只是用ht[0]哈希表,ht[1]只会在对ht[0]进行rehash时使用。

4). trehashidx : 记录rehash目前的进度。如果目前没有进行rehash,那么它的值为-1.

6. 字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键。

7. Redis中的字典使用暗黑系表作为底层实现,每个字典带有两个哈希表,一个平时使用,另一个仅在进行rehash时使用。

8. 哈希表使用链地址法来解决键冲突,被分配到同一个索引上的多个键值对会连接成单向链表。最新的键值在最上面,便于访问。

9. 在对哈希表进行扩展或者收缩操作时,程序需要将现有的哈希表包含的所有键值对rehash到新哈希表里面,并且这个rehash过程并不是一次性完成的,而是渐进式的。

10. 因为在进行渐进式rehash的过程中,字典会同时使用ht[0]和ht[1]两个哈希表,所有在渐进式rehash进行期间,字典的删除、查找、更新等操作会在两个哈希表上进行。先找ht[0],再找ht[1].

另外,在渐进式rehash期间,新添加到字典中的键值对一律被保存到ht[1]中。

11. 哈希表的扩展与收缩:

满足以下任意条件时,执行扩展操作:

1). 服务器目前没有在执行BGSAVE或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于1。

2). 服务器目前正在执行BGSAVE或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于5

另一方面,当哈希表的负载因子小于0.1时,程序自动开始对哈希表执行收缩操作。

跳跃表:

1. 跳跃表是以各种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。

2. Redis只有两个地方用到跳跃表:一个是实现有序集合键,另一个是在集群节点中用作内部数据结构。

3. Redis的跳跃表zskiplistNode和zskiplist两个结构定义,其中zskiplistNode结构用于表示跳跃表节点,而zskiplist结构则用于保存跳跃表节点的相关信息,比如:节点的数量以及指向表头节点和表尾节点的指针等。

4. zskiplistz主要包含一下几个属性:

1). header : 指向跳跃表的表头节点

2). tail : 指向跳跃表的表尾节点

3). level : 记录目前跳跃层内,层数最大的那个节点的层数(表头节点的层数不计算在内容)

4). length : 记录跳跃表的长度,也即是,跳跃表目前包含节点的数量(表头节点不计算在内,表头节点好像是最高层)

5. zskiplistNode主要包含以下几个属性:

1). zskiplistLevel : 层,里面包含前进指针(forward) 和跨度(span)

2). backward : 后退指针

3). score : 分值

4). obj : 成员对象

6. 跳跃表是有序集合的底层实现之一。

7. 每个跳跃表节点的层高都是1至32之间的随机数。

8. 在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的成员对象必须是唯一的。

9. 跳跃表的节点按照分值大小进行排序,当分值相同时,节点按照成员对象的大小进行排序。

整数集合

1. 整数集合是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis就会使用整数集合作为集合键的底层实现。

整数集合可以保存的类型为:int16_t,int32_t,int64_t 的整数值,并且保证集合中不会出现重复元素。

2. 整数集合主要包含以下几个属性:

1). encoding : 编码方式。值可以为:int16_t,int32_t,int64_t

2). length : 集合包含的元素数量

3). contents : 保存元素的集合。数组中元素按从小到大的顺序排列。contents数组的真正类型取决于encoding属性的值。

3. 当我们要将一个新元素添加到整数集合里面,并且新元素的类型比整数集合现有所有元素的类型都要长时,整数集合需要先进行升级,然后才能将新元素添加到整数集合里面。

4. 整数集合的升级策略有两个好处:一个是提升整数集合的灵活性(可以随意添加int16_t,int_32,int_64类型的整数到集合,而不必担心类型错误),另一个是尽可能地节约内存。

5. 整数集合只支持升级操作,不支持降级操作。

压缩列表:

1. 当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。

2. 压缩列表是Reids为解决内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构。一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值。

3. 压缩列表主要包括以下几个属性:

1). zlbytes : 记录整个压缩列表占用的内存字节数。

2). zltail : 记录压缩列表表尾节点距离压缩列表的起始地址有多少节点:通过这个偏移量,程序无须遍历整个压缩列表就可以确定表尾节点的地址。

3). zllen : 2个字节长度。记录了压缩列表包含的节点数量:当这个节点的值小于UINT16_MAX(65535)时,这个属性的值就是压缩列表包含节点的数量;当这个值等于UINT16_MAX时,节点的

真实数量需要遍历正而过压缩列表才能计算得出。

4). entyX : 压缩列表包含的各个节点,节点的长度由节点保存的内容决定。

5). zlend : 特殊值0xFF(十进制255),用于标记压缩列表的末端。

4. 指向尾节点的指针可以通过指向压缩列表起始地址的指针加上zltail属性的值得出。

5. 压缩列表节点包含以下几个属性:

1). previous_entry_length : 记录压缩列表前一个节点的长度。长度值可以是1字节或者5字节。程序可以通过指针运算,根据当期节点的起始地址来计算出前一个节点的起始地址。

2). encoding : 记录了节点content属性所保存数据的类型以及长度。

3). content : content 属性负责保存节点的值,节点值可以是一个字节数组或者整数,值的类型和长度由节点encoding属性决定。

6. 添加新节点到压缩列表中或者从压缩列表中删除节点,可能会引发连锁更新操作,但这种操作出现的几率并不高。

7. 压缩列表是一种为节约内存而开发的顺序型数据结构。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: