文章目录
- 1. 前言
- 2. redis 动态字符串
- 2.1. 字符串的数据结构:
- 2.2. 剖析,length;
- 2.3. 剖析,free;
- 2.3. 使用c字符串函数;
- 3. redis 链表
- 4. 字典
- 5. 跳跃表
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)),而且在实践中这个额外空间的使用通常是可控的。
实践:在实际应用中,跳表往往能够提供与平衡树相似或有时候更优的性能表现,特别是在插入和删除操作频繁的场景中。