Redis数据结构底层概述

145 阅读4分钟

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战

redis作为一款Nosql的数据库,拥有五大基本数据类型和三大扩展类型,每种类型都有各自的特点,也有不同的底层实现。今天就来简单总结一下;

String

String的数据结构为简单动态字符串(Simple Dynamic String,缩写SDS)。是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配.

image-20211203093040514

如图中所示,内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M。

List

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

image-20211203093118589

它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。

底层数据结构

List的数据结构为快速链表quickList

ziplist

首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表。

它将所有的元素紧挨着一起存储,分配的是一块连续的内存。

ziplist是由一系列特殊编码的内存块构成的,使用一块连续的内存空间存储。每个元素长度不同,采用的是变长编码。

image-20211203131337584

底层源码:

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

image-20211203132623795

结构源码

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的范围获取元素列表。