Redis是一个KV数据库,常用于实现缓存,因为基于内存实现,所以速度极快。最近阅读《Redis设计与实现》一书,整理几篇文章,本文介绍Redis数据结构相关内容。
我们通常说的Redis支持的数据类型有五种,包括字符串、哈希、列表、集合、有序集合,其实这只是存储的数据类型,底层用于存储数据的数据结构并不是这些,而是动态字符串(SDS)、链表、字典(哈希表)、跳跃表、整数集合、压缩列表。
Redis的每一种数据类型其底层所用的数据结构并不只有一种,往往会在合适的情况下采用合适的数据结构,也是基于性能考虑。对照下面导图,我们可以简单的了解每种数据类型有哪些数据结构实现方式。
下面具体介绍Redis中定义的几种数据结构。
Redis中自定义了一个动态字符串取代了C语言中的字符串作为默认字符串数据结构,凡是可能会变化的字符串Redis都会用SDS作为实现。
用途:
定义——ArrayList的感觉:
struct sdshdr {
int len; // 实际buf使用长度
int free; // 剩余长度
char buf[]; // 存储字节数据数组
}
优势:
记录了长度信息,获取长度 O ( 1 ) O(1) O(1)复杂度
自动扩容机制——缓冲区不会溢出(莫名其妙更改了别的内存空间数据)
空间预分配和惰性空间释放——减少增减字符串时带来的内存重分配次数
以 len
来判断数据结束——不仅仅可以存储字符,其实可以是二进制数据
Redis定义的双端链表其实也很正常,就是一个能够双向遍历,含有头尾指针的链表数据结构,当然也就具有链表的优势与劣势——增删快,查询慢。
用途:
节点定义——双向链表节点:
typedef struct listNode{
struct listNode *prev;
struct listNode *next;
void *value;
}
链表定义——双端双向链表:
typedef struct list{
listNode *head;
listNode *tail;
unsigned long len;
void *(*dup) (void *ptr); // 节点值复制函数
void (*free) (void *ptr); // 节点释放函数
int (*match) (void *ptr, void *key); // 节点值对比函数
}
优势:
NULL
len
——获取列表元素数量复杂度 O ( 1 ) O(1) O(1)void*
指针保存节点值,可以是多态的字典的实现也是一个Key-Value的映射,也就是Map数据结构,实现方法熟知的就是Java里的 HashMap
和 TreeMap
。Redis实现的方式是 HashMap
,也就是利用hash表的方式。
用途:
哈希节点定义——哈希表中的每个元素:
typedef struct dicEntry {
void *key; // 键
union {
void *val; // 指针
uint64_t u64; // uint64_t整数
int64_t s64; // int64_t整数
} v; // 值有三种形式
struct dicEntry *next; // 指向下一个哈希节点,拉链法解决哈希碰撞
} dicEntry;
哈希表定义——存储数据元素:
typedef struct dictht {
dicEntry **table; // 哈希数组
unsigned long size; // 数组长度,会扩容的
unsigned long sizemask; // 哈希表大小掩码,用于计算索引值,等于size-1
unsigned long used; // 已经使用的数量
} dicht;
字典定义:
typedef struct dict {
dictType *type; // 保存了一簇用于操作特定类型键值对的函数,Redis会为不同的字典设置不同的dictType
void *privdata; // 私有数据。
dictht ht[2]; // 哈希表数组,两个哈希表,ht[1]专为扩容缩容使用
long rehashidx; // 用于记录rehash进度,-1表示结束
} dict;
字典类型定义:
typedef struct dictType {
unsigned int (*hashFunction)(const void *key); // hash函数
void *(*keyDup)(void *privdata, const void *key); // key复制
void *(*valDup)(void *privdata, const void *obj); // val复制函数
int (*keyCompare)(void *privdata, const void *key1, const void *key2); // 两个key值比较
void (*keyDestructor)(void *privdata, void *key); // key的析构函数
void (*valDestructor)(void *privdata, void *obj); // val的析构函数
} dictType;
特性:
关于 HashMap
这种数据结构,其需要关注的几个特性相信都是老生常谈:
dicEntry
的 next
指针用于拉链)rehashidx
来记录h[0]迁移h[1]数据的进度,-1表示结束,≥0表示哈希到ht[0]的下标进度跳跃表是种有序的数据结构,其本质是加速有序链表的插入删除速度,其实链表插入删除如果不是在头尾的话,首先需要定位,定位的复杂度就不是 O ( 1 ) O(1) O(1)了。对于跳跃表,不熟悉的可以参考:以后有面试官问你跳跃表,你就把这篇文章扔给他
用途:
跳跃表节点定义:
typedef struct zskiplistNode {
struct zskiplistNode *backward; // 后退指针,也就是支持逆向遍历
double score; // 分值
robj *obj; // 成员对象,也就是跳跃表该节点保存的数据
// 该节点的层数组,表示该节点定义多少层
struct zskiplistLevel {
struct zskiplistNode *forward; // 每层中包含前向指针
unsigned int span; // 跨度,表示前进几步到下一个节点
} level[];
}
跳跃表定义:
一个跳跃表是由一个个节点顺序连接而成。
typedef struct zskiplist {
/* 跳跃表的头结点和尾节点,尾节点的存在主要是为了能够快速定位出当前跳跃表的最后一个节点,实现反向遍历 */
struct zskiplistNode *header, *tail;
/* 当前跳跃表的长度,保留这个字段的主要目的是可以再O(1)时间内获取跳跃表的长度 */
unsigned long length;
/* 所有节点中level最大值,不包括头结点(头结点的level永远都是最大的值---ZSKIPLIST_MAXLEVEL = 32)。level的值随着跳跃表中节点的插入和删除随时动态调整 */
int level;
} zskiplist;
特性:
span
的加速,统计个数有 length
的加速整数集合其实就是用于保存整数数值的有序无重复集合,其实现方式底层就是数组,数组类型则可以是 int16_t
、int32_t
和 int64_t
,数据类型会自动升级。
用途:
整数集合定义——数组实现的Set:
typedef struct intset {
uint32_t encoding; // 编码方式,其实就是标识当前的整数集合类型(有符号16位、32位和64位)
uint32_t length; // 集合中元素数量
int8_t contents[]; // 保存整数数据,从小到大有序排列
} intset;
特性:
压缩列表的本质是一系列特殊编码的连续内存块组成的顺序性数据结构,其实是Redis为了节约内存而设计的数据结构。一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值。
用途:
Entry节点定义:
struct entry {
int<var> prevlen; // 前一个节点的占用字节长度,prevlen属性的长度可以是1字节或5字节(取决于前一个节点字节长度是否小于254字节),
int<var> encoding; // 元素类型编码,保存了数据的类型和长度
optional byte[] content; // 元素内容,可以是字节数组,或者是整数类型数据
}
压缩列表定义:
struct ziplist<T> {
int32 zlbytes; // 整个压缩列表占用字节数
int32 zltail_offset; // 最后一个元素距离压缩列表起始位置的偏移量,用于快速定位到最后一个节点
int16 zllength; // 元素个数,当该值等于65535(可能发生溢出)时,需要遍历才能确定真实元素个数
T[] entries; // 元素内容列表,挨个紧凑存储
int8 zlend; // 标志压缩列表的结束,值恒为0xFF(十进制255)
}
特性:
prevlen
属性可以快速的定位前一节点的地址prevlen
属性的长度依赖前一个节点的具体长度,本身可以是1字节或者5字节。如果其中一个节点的长度更改,引发后继节点的 prevlen
属性从1字节变为5字节,进而又导致后继的后继也需要调整…这就是连锁更新的问题。但其实也无大碍,实际中很难有这种极端的连锁下去的情况。