「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」
redis作为一款Nosql的数据库,拥有五大基本数据类型和三大扩展类型,每种类型都有各自的特点,也有不同的底层实现。今天就来简单总结一下;
String
String的数据结构为简单动态字符串(Simple Dynamic String,缩写SDS)。是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配.
如图中所示,内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M。
List
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。
底层数据结构
List的数据结构为快速链表quickList。
ziplist
首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表。
它将所有的元素紧挨着一起存储,分配的是一块连续的内存。
ziplist是由一系列特殊编码的内存块构成的,使用一块连续的内存空间存储。每个元素长度不同,采用的是变长编码。
底层源码:
struct ziplist<T> {
int32 zlbytes; // 整个压缩列表占用字节数
int32 zltail_offset; // 最后一个元素距离压缩列表起始位置的偏移量,用于快速定位到最后一个节点
int16 zllength; // 元素个数
T[] entries; // 元素内容列表,挨个挨个紧凑存储
int8 zlend; // 标志压缩列表的结束,值恒为 0xFF
}
每一个entry节点的结构:
struct entry {
int<var> prevlen; // 前一个 entry 的字节长度
int<var> encoding; // 元素类型编码
optional byte[] content; // 元素内容
}
可见zipList在结构上可以得到上一个结点的长度和当前结点的长度。那么通过上一个结点的长度,就可以将指针定位到上一个元素起始的位置,而通过当前结点的长度,就可以将指针定位到下一个元素的起始位置。
linkedlist
Redis早期版本的list采用的就是ziplist和linkedlist结合,当数据量较小时存储类型采用ziplist,较大时则采用linkedlist存储数据
linkedlist结构:
// 链表的节点
struct listNode<T> {
listNode* prev;
listNode* next;
T value;
}
// 链表
struct list {
listNode *head;
listNode *tail;
long length;
}
这样的存储类型能够很方便地存储大量的数据,但相对来说,这样地内存运用方式是不紧凑地,会出现内存被割裂,因为普通的链表需要的附加指针空间太大,会比较浪费空间。比如这个列表里存的只是int类型的数据,结构上还需要两个额外的指针prev和next,不能产生连续的效益。
后来redis为了节约更多的内存空间,也为了提高内存管理的效率,采用quicklist结构。
quicklist
结构源码
struct quicklistNode {
quicklistNode* prev;
quicklistNode* next;
ziplist* zl; // 指向压缩列表
int32 size; // ziplist 的字节总数
int16 count; // ziplist 中的元素数量
int2 encoding; // 存储形式 2bit,原生字节数组还是 LZF 压缩存储
...
}
struct quicklist {
quicklistNode* head;
quicklistNode* tail;
long count; // 元素总数
int nodes; // ziplist 节点的个数
int compressDepth; // LZF 算法压缩深度
...
}
quickList是一个zipList组成的双向链表。每个结点使用zipList来保存数据。本质上说quickList就是由一个一个小的zipList串起来的链表。
Set
Set数据结构是dict字典,字典是用哈希表实现的。
Java中HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值。
Hash
Hash类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表)。当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable。
ZSet
SortedSet(zset)是Redis提供的一个非常特别的数据结构,一方面它等价于Java的数据结构Map<String, Double>,可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。
zset底层使用了两个数据结构
(1)hash,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。
(2)跳跃表,跳跃表的目的在于给元素value排序,根据score的范围获取元素列表。