文章目录
- 1. 前言
- 2. redis 动态字符串
- 2.1. 字符串的数据结构:
- 2.2. 剖析,length;
- 2.3. 剖析,free;
- 2.3. 使用c字符串函数;
- 3. redis 链表
- 4. 字典
- 5. 跳跃表
- 6. 整数set(intset)
- 6.1. 升级(upgarde)
- 7. 压缩列表(ziplist)
1. 前言
reids作为最常用的缓存数据库,深入了解,对于业务开发大有裨益,那么从这里开始,我们从《redis设计与实现》这本书,我们同最常用的字符串入手,了解redis的设计与思路。
2. redis 动态字符串
字符串作为redis最为核心,最为常用的数据类型,后面我们称sds,我们深入了解一下。
2.1. 字符串的数据结构:
我们从数据结构入手,猜测字符串实现的功能和特性,我们可以发现,这里相比于c字符串多了两个属性。free
和len
;
2.2. 剖析,length;
-
快速获取length :首先,leng最简单的效果便是可以直接获取redis字符串的长短,由于是直接获取属性,时间复杂度为O(1)。
-
二进制安全: 除了获取长度外,为了实现redis的sds可以存储任意数据的功能,sds通过length判断字符串是否到结尾,这和c字符串不同(‘/0’),因此可以存储任意二进制数据。
2.3. 剖析,free;
其实简单length之后发现基本功能都差不多了,那么这个free有什么作用呢?
- 预留free空间: 减少重新分配,在sds除了记录length之外,还会分配一倍length(1mb大小以内)的未使用空间,如果length在再次增加的情况下,不过增加的长度小于free,则不需要重新分配内存。
2.3. 使用c字符串函数;
redis虽然自行实现了字符串数据机构,但是还是在字符串末尾增加一个’/0’空字符,目的是为了使用c字符串的函数。
3. redis 链表
redis链表并没有非常奇特的地方,在redis链表中主要有两个数据结构。
- list 统计
这里包括list的一些概要信息和一些函数,目的是为了后面使用链表节点方便一点。 - listnode 节点。
一般来说这个在学习数据结构中用的很多,一般情况下,只需要记录一个pre node便可以遍历整个链表。
redis链表为双向链表。并没有过于多的特殊。
4. 字典
字典又称为符号表,map,key-value。
-
dictht(hash)表,属性包括,hash表数组,表大小,hash表大小掩码,已有节点数量。
-
dictEntry表数据,是key-value结构
redis对于hash冲突的解决方案是链地址法,即如果冲突,在原来dictEntrt下面通过next链接冲突节点。
-
字典,hash的上层结构,和java中的hashMap功能类似。
dictType
可以指定不同低操作函数。
ht
为两个hash表,另外一个用于备份。(在再hash时使用) -
hash算法: hash算法和普通的hash表别无二致,通话hash算法,
hash(key)& mask
把数组放到表里。 -
hash冲突。
redis使用链地址发把键值对存储在链表之前解决冲突,这样的时间复杂度为O(1)。 -
rehash
当hash表空间不够的时候,一般需要再次hash,
渐进式再hash,在渐进式 rehash 过程中,Redis 会同时保持旧的哈希表和新的哈希表。然后,在每次执行命令时,Redis 会从旧哈希表中移动一小批键值对到新哈希表,这个过程分散在多个操作中逐步完成。
具体条件为
- 当负载因子大于1且没在持久化(BGSAVE,BGREWRITEAOF)会进行再hash。
- 当负载因子大于3,目前在执行BGSAVE,BGREWRITEAOF)时,会进行再hash。
- 在负载因子小于0.1时,会进行收缩。
- BGSAVE 命令用于在后台创建 Redis 数据库的快照。当执行此命令时,Redis 会 fork 出一个子进程,子进程则将内存中的数据写入到磁盘上的一个 RDB 文件中。这个过程不会阻塞主 Redis 进程,所以 Redis 可以继续处理客户端请求。RDB(Redis DataBase)文件是一个压缩的二进制文件,表示某一时刻 Redis 数据库的完整快照
- BGREWRITEAOF 命令用于优化 AOF(Append Only File)文件的大小。Redis 的 AOF 持久化通过记录数据库的所有写操作到一个文件中来工作,这个文件随着操作的积累会不断增长。BGREWRITEAOF 命令会在后台创建一个当前数据的最小操作集,以此来重写 AOF 文件,这个过程同样不会阻塞主 Redis 进程。
5. 跳跃表
跳跃表几乎只用于有序集合。
- zskiplistNode: 这是跳跃表的节点结构定义。每个节点代表有序集合中的一个元素。
- zskiplistLevel: 这个结构体定义了跳跃表节点在不同层级的信息,每个节点可以有一个或多个层级(level)。
-
forward: 是一个指向同一层级的下一个节点的指针。在查找操作中,这个指针允许我们“跳过”一些节点,从而更快地在跳跃表中进行搜索。
-
span: 这是一个无符号整数,它记录了当前节点与通过 forward 指针所指向的下一个节点之间的跨度。在进行范围查询或者计算排名时,这个值非常有用,因为它可以快速计算出两个节点之间的间隔。
backward: 这是一个指向当前节点前一个节点的指针,在双向遍历时使用。
-
score: 这是一个双精度浮点数,用来保存节点的分数值。在有序集合中,元素是根据这个分数进行排序的,分数相同时则按照存储的对象(obj)来进行字典序比较。
-
obj: 这是一个指向实际存储数据的指针,通常是一个字符串类型。在 Redis 中,这是指向 robj(Redis 对象)的指针,它可以存储字符串、列表、哈希表等不同类型的数据结构。
-
level[]: 这是一个大小可变的数组,它的具体长度取决于节点所在的层数。这个数组存储每一层的 zskiplistLevel 结构体,允许节点在跳跃表的多个层级上存在。
可以通过zskiplist持有这些节点。
为什么要用跳跃表?
简单性:跳表的算法和代码实现相比平衡树要简单得多。对于平衡树,如 AVL
树或红黑树,它们的旋转操作逻辑复杂,难以编写且容易出错。跳表提供了一种容易理解和实现的高效有序数据结构。效率:跳表的查找、插入和删除操作的平均时间复杂度都是 O(log n),与平衡树相当。
灵活性:跳表支持快速的顺序访问和有效的范围查询,这对于数据库索引来说是非常重要的。
并发性:跳表的数据结构更容易实现锁定机制,这使得在并发环境下的性能表现更好。由于节点的层次结构,跳表可以更容易地实现细粒度锁或无锁并发算法。
动态:跳表无需预先知道数据规模,它可以根据实际需要动态地进行扩展,这在不可预知数据量的实时系统中非常有用。
空间效率:虽然跳表的多层结构需要额外的空间来存储指针,但它的空间复杂度仍然是线性的(O(n)),而且在实践中这个额外空间的使用通常是可控的。
实践:在实际应用中,跳表往往能够提供与平衡树相似或有时候更优的性能表现,特别是在插入和删除操作频繁的场景中。
6. 整数set(intset)
整数集合是集合键的底层实现之一,当一个集合包含整数值元素,并且这个集合的元素数量不多时,redis就会使用整数集合作为集合键的底层实现。
-
encoding: 整数类型位宽
决定着整数是几位的。 -
length: 集合中元素数量
-
contents 元素集合
元素集合仅仅手encoding影响。
intset 主要支持三种整数编码类型:16 位、32 位和 64 位整数
6.1. 升级(upgarde)
整数集合一般默认会给于较小的整数类型,如16位。
不过当加入一个新的集合元素,数据类型大于整数位宽的话,会进行升级操作,以便存入新的较长的整数。
下面简述下升级步骤
- 根据新元素的类型,拓展地城数组空间的大小。
- 将原来元素全部转换成新的元素。
- 将新元素添加到数组中。
有序集合不支持降级。
思考一下为什么?
- 判断麻烦
升级操作只需大于当前数据类型即可,降级操作需要所有元素小于更小一档数据类型的长度,难以判断。
7. 压缩列表(ziplist)
Redis 的压缩列表(ziplist)是一种为了节省内存而设计的紧凑数据结构,用于存储整数值、小字符串或者一些较小的聚合数据结构,如小哈希表、小列表等。压缩列表是 Redis 内部使用的一种序列化方式,它通过在一个连续的内存区域中紧密排列数据元素来减少内存占用。
- 列表结构