一、Redis 数据类型相关
数据类型实际描述的是 value 的类型,key 都是 string,常见数据类型(value)
string(embstr、raw、int)
list(quicklist(多个 ziplist 双向链表))
hash(ziplist、hashtable)
set(intset、hashtable)
sorted set(ziplist、skiplist)
bitmap
hyperloglog
每一种类型都用 redisObject 结构体来表示,每种类型根据情况不同,有不同的编码 encoding(即底层数据结构)
二、String
1.如果字符串保存的是整数值,则底层编码为 int,实际使用 long 来存储
2.如果字符串保存的是非整数值(浮点数字或其它字符)又分两种情况
长度 <= 39 字节,使用 embstr 编码来保存,即将 redisObject 和 sdshdr 结构体保存在一起,分配内存只需一次
长度 > 39 字节,使用 raw 编码来保存,即 redisObject 结构体分配一次内存,sdshdr 结构体分配一次内存,用指针相连
3.sdshdr 称为简单动态字符串,实现上有点类似于 java 中的 StringBuilder,有如下特性
单独存储字符长度,相比 char* 获取长度效率高(char* 是 C语言原生字符串表示)
支持动态扩容,方便字符串拼接操作
预留空间,减少内存分配、释放次数(< 1M 时容量是字符串实际长度 2 倍,>= 1M 时容量是原有容量 + 1M)
二进制安全,例如传统 char* 以 \0 作为结束字符,这样就不能保存视频、图片等二进制数据,而 sds 以长度来进行读取
三、List
1.3.2 开始,Redis 采用 quicklist 作为其编码方式,它是一个双向链表,节点元素是 ziplist
由于是链表,内存上不连续
操作头尾效率高,时间复杂度 O(1)
链表中 ziplist 的大小和元素个数都可以设置,其中大小默认 8kb
2.ziplist 用一块连续的内存存储数据,设计目标是让数据存储更紧凑,减少碎片开销,节约内存,它的结构如下
zlbytes – 记录整个 ziplist 占用字节数
zltail-offset – 记录尾节点偏移量
zllength – 记录节点数量
entry – 节点,1 ~ N 个,每个 entry 记录了前一 entry 长度,本 entry 的编码、长度、实际数据,为了节省内存,根据实际数据长度不同,用于记录长度的字节数也不同,例如前一 entry 长度是 253 时,需要用 1 个字节,但超过了 253,需要用 5 个字节
zlend – 结束标记
3.ziplist 适合存储少量元素,否则查询效率不高,并且长度可变的设计会带来连锁更新问题
四、Hash
1.在数据量较小时,采用 ziplist 作为其编码,当键或值长度过大(64)或个数过多(512)时,转为 hashtable 编码
2.hashtable 编码
hash 函数,Redis 5.0 采用了 SipHash 算法
采用拉链法解决 key 冲突
rehash 时机
当元素数 < 1 * 桶个数时,不扩容
当元素数 > 5 * 桶个数时,一定扩容
当 1 * 桶个数 <= 元素数 <= 5 * 桶个数时,如果此时没有进行 AOF 或 RDB 操作时
当元素数 < 桶个数 / 10 时,缩容
rehash 要点
每个字典有两个哈希表,桶个数为 2 𝑛 ,2^n, 平时使用 ht [0] , ht [1] 开始为 null ,在扩容时新数组大小为元素个数 * 2
渐进式 rehash ,即不是一次将所有桶都迁移过去,每次对这张表 CRUD 仅迁移一个桶
active rehash , server 的主循环中,每 100 ms 里留出 1s 进行主动迁移
rehash 过程中,新增操作 ht [1] ,其它操作先操作 ht [0] ,若没有,再操作 ht [1]
redis 所有 CRUD 都是单线程,因此 rehash 一定是线程安全的
五、Sorted Set
1.在数据量较小时,采用 ziplist 作为其编码,按 score 有序,当键或值长度过大(64)或个数过多(128)时,转为 skiplist + hashtable 编码,同时采用的理由是
只用 hashtable,CRUD 是 O(1),但要执行有序操作,需要排序,带来额外时间空间复杂度
只用 skiplist,虽然范围操作优点保留,但时间复杂度上升
虽然同时采用了两种结构,但由于采用了指针,元素并不会占用双份内存
2.skiplist (跳表)要点:多层链表、排序规则、 backward、level(span,forward)
整个跳表是有序的,下图按照score排好序,如果score,再按其他数据排。backward可以用于倒叙遍历。level是层级,每个数据层级可能不一样,level里面有两个属性,forward是前向指针,span用于计算排名,记录两个节点之间的跨度
六、跳表查询
skiplist 查找要点,从顶层开始
> 右边的,继续向右
= 找到了
< 右边的或右边为 NULL,下一层,重复 1、2 步骤
以查找 score = 22 为例,虽然跳表是有序的,但是底层是链表结构,不能使用二分法。多层链表就是为了减少比较次数,提高效率。先从第四层开始,同层的右侧元素null,则下一层,第三层,和右侧元素37比,下一层,第二层>右侧元素,向右小于37,下一层就找到22