Redis存储结构
1 简介
从Redis系列(一):Redis数据结构 可以了解到,Redis为用户提供了多种数据结构及相应API,用户可随心使用,本文将具体介绍这些数据结构的具体实现。
2 Redis存储结构
Redis主要实现了6中存储结构,分别为:整数集合;字典、字符串;链表、压缩列表、跳跃表。
2.1 SDS
SDS是Simple Dynamic String
的首字母缩写,是对C语言中的字符串的封装,其主要数据结构定义如下:
struct sdshdr {
// 表示已分配使用的字节数
// buf数组已使用的长度
int len;
// 表示SDS未使用的字节数;
int free;
// 字节数组保存二进制数据
// 遵循C语言规则,以'\0'结尾,但不计算在len属性
char buf[];
};
在Redis系列(一):Redis数据结构 已经讲过,Redis所有的键都是字符串类型,也就是说所有的键都是SDS对象,举例来说:
set msg “hello world”
该命令创建了一个键值对,且键和值都是字符串类型,键是一个保存着msg的SDS,值是保存着 hello world的SDS;
SDS是对在C字符串的基础上进行的封装,具有以下优势:
常数复杂度获取字符串长度
:C语言获取字符串长度需要进行一次便利,SDS只需要通过获取len属性即可达到目的;减少修改字符串时带来的内存重分配
:C字符串拼接或截断操作都需要重新分配内存空间,而SDS在分配内存空间时,预留了free长度的空闲空间,能够有效减少内存冲分配次数;二进制安全
:C字符串只能保存文本数据,而SDS能保存包括图片、音频、视频等二进制数据,其原因是SDS是通过len属性而不是’\0’来判断字符串的结尾。杜绝缓冲区溢出
:C字符串拼接需要手动分配内存空间,以容纳拼接后的字符串,如果忘记该操作就会导致缓冲区溢出,SDS已经封装好了字符串拼接操作,可完全避免缓冲区溢出问题。2.2 链表
链表是一种常用的数据结构,其基本特性大家都应该很了解,如高效的节点重排能力、顺序性节点访问、灵活的节点增删等。Redis使用的C语言并没有内置这种数据结构,因此Redis自己实现了链表结构。
链表节点listNode主要包含三个属性:前置节点 *prev、后置节点 *next及节点值 *value,并且通过list结构来持有链表:
/**
* 链表节点数据结构
**/
typedef struct listNode{
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
/**
* 通过list持有链表
**/
typedef struct list{
// 头节点
listNode *head;
// 尾节点
listNode *tail;
// 链表节点数量
unsigned int len;
// 节点复制函数
void *(*dup)(void *ptr);
// 节点释放函数
void *(*free)(void *ptr);
// 节点值对比函数
void *(*match)(void *ptr);
} list;
Redis链表具有以下特征:
- 双端:list结构持有head和tail指针,获取表头或表尾节点时间复杂度为O(1);
- 双向:链表每个节点都带有prev和next指针,获取前置节点或后置节点时间复杂度为O(1);
- 无环:头节点的prev和尾节点的next都为NULL;
长度计数器:list通过len属性记录链表节点数量;
2.3 整数集合
整数集合是集合键的底层实现之一,当一个集合只包含整数且数量不多时,Redis就会使用整数集合intset作为底层实现。
intset可以保存int16_t, int32_t, int64_t的整数值,并且可以保证不重复。
typedef struct intset {
// 编码方式
uint32_t encoding;
// 集合包含的元素数量
uint32_t length;
// 升序保存着所有元素
int8_t contents[];
} intset;
contents属性声明为int8_t类型,但实际上元素的类型取决于encoding属性的值:
- 如果encoding为INTSET_ENC_INT16,那么contents所有元素都是int16_t类型整数值;
- 如果encoding为INTSET_ENC_INT32,那么contents所有元素都是int32_t类型整数值;
如果encoding为INTSET_ENC_INT64,那么contents所有元素都是int6_4t类型整数值;
升级: 当将一个新元素添加到整数集合,且其长度比集合中任何元素类型都长时,整数集合需要先进行升级,升级分为三步:
1)根据新元素类型,分配内存空间,包括已有元素和新元素;
2)将已有元素转换成新元素类型,并放置到新的内存空间;
3)将新元素添加到内存空间;
整个升级过程保证所有元素升序排序。对于具体的内存分配过程,请大家参阅《Redis设计与实现》。
需要说明的是 整数集合不支持降级,因此一旦对数组进行了升级,编码就会一直保持升级后的状态。