1、基础环境
// 查看当前对象的类型
type key
// 查看当前key的编码
object encoding key
2、简单动态字符串
Redis默认并未直接使用C字符串(C字符串仅仅作为字符串字面量,用在一些无需对字符串进行修改的地方,如打印日志);而是以struct的形式构造了一个SDS的抽象类型。当Redis需要一个可以被修改的字符串时,就会使用SDS来表示。
在Redis数据库里,包含字符串值的键值对都是由SDS实现的(Redis中所有的键都是由字符串对象实现的即底层是由SDS实现,Redis中所有的值对象中包含的字符串对象底层也是由SDS实现)
2.1、SDS基础结构
struct sdshdr {
// 记录bug数组中已使用字节的数量
int len;
// 记录buf数组中未使用字节的数量
int free;
// 字节数组,用户保存字符串
cha buf[];
}
2.2、SDS特性
- 可以高效的执行长度计算
- 可以高效的执行追加操作
- 二进制安全
- 杜绝缓存区溢出
- 兼容部分C字符串函数
2.3、与C字符串的区别
| C字符串 | SDS |
|---|---|
| 获取字符串长度的复杂度为O(n) | 获取字符串长度的复杂度为O(1) |
| API是不安全的,可能会造成缓冲区溢出 | API是安全的,不会造成缓冲区溢出 |
| 修改字符串长度N次必然需要执行N次内存重分配 | 修改字符串长度N次”最多“需要执行N次内存重分配 |
| 只能保存文本数据 | 可以保存文本、二进制数据 |
| 可以使用所有<string.h>库函数 | 可以使用一部分<string.h>库中的函数 |
2.4、应用范围
- 字符串对象
3、链表
Redis链表为双向无环链表;链表是种非常常见的数据结构,在Redis中使用非常广泛,列表对象的底层实现之一就是链表。其它如慢查询,发布订阅,监视器等功能也用到了链表。
3.1、节点结构
// 文件路径:src/adlist.h
typedef struct listNode {
// 前置节点
struct listNode *prev;
// 后置节点
struct listNode *next;
// 节点的值
void *value;
} listNode;
3.2、链表结构
typedef struct list {
// 表头节点
listNode *head;
// 表尾节点
listNode *tail;
// 节点值复制函数
void *(*dup)(void *ptr);
// 节点值释放函数
void (*free)(void *ptr);
// 节点值对比函数
int (*match)(void *ptr, void *key);
// 链表所包含的节点数量
unsigned long len;
} list;
由list结构和listNode结构组成的链表
3.3、Redis的链表实现的特性
- 双端:链表节点带有prev和next指针,获取某个节点的前置节点和后置节点的复杂度都是O(1)。
- 无环:表头节点的prev指针和表尾节点的next指针都指向NULL,对链表的访问以NULL为终点。
- 带表头指针和表尾指针:通过list结构的head指针和tail指针,程序获取链表的表头节点和表尾节点的复杂度为O(1)。
- 带链表长度计数器:程序使用list结构的len属性来对list持有的链表节点进行计数,程序获取链表中节点数量的复杂度为O(1)。
- 多态:链表节点使用void*指针来保存节点值,并且可以通过list结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。
3.4、应用范围
- 列表对象
4、字典
字典是一种用于保存键值对的抽象数据结构;在字典中,一个key可以和一个value进行关联(或者说将键映射为值),这些关联的键和值就称为键值对。
4.1、字典结构
文件路径:src/dict.h
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
int iterators; /* number of iterators currently running */
} dict;
4.2、哈希表的结构
文件路径:src/dict.h
typedef struct dictht {
// 哈希表数组
dictEntry **table;
// 哈希表大小
unsigned long size;
// 哈希表大小掩码,用于计算索引值
unsigned long sizemask;
// 哈希表已有的节点数量
unsigned long used;
} dictht;
4.3、哈希表节点值结构
文件路径:src/dict.h
typedef struct dictEntry {
// 健
void *key;
// 值
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
// 指向下个哈希表节点,形成链表,主要用来解决哈希冲突问题
struct dictEntry *next;
} dictEntry;
4.4、redis如何解决哈希冲突
Redis的哈希表使用链地址法来解决键冲突,每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表连接起来,这就解决了哈希冲突的问题。
4.5、重新哈希
当哈希冲突的key逐渐增多的时候,哈希表的负载因子也会超出合理的范围,同样会影响字典接口使用的性能,无论新增还是读取哈希值,性能都会随着负载因子增高而下降;
同样随着不断的操作哈希表,保存的哈希键值对数可能过多,也可能变少,程序需要对哈希表的大小进行响应的扩展和缩容;
redis的通过执行rehash来操作就能实现扩容、缩容,并保证哈希因子在合理范围内。
4.6、负载因子
负载因子 = 哈希表已保存的节点数量/哈希表的大小
load_factor = used / size
4.7、哈希表满足什么条件会扩容和缩容呢
- 服务器目前没有在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于1。
- 服务器目前正在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于5。
- 当哈希表的负载因子小于0.1时,程序自动开始对哈希表执行收缩操作
4.8、应用范围
- redis数据库
- 哈希对象
- 集合对象
5、跳跃表
跳跃表是一个有序数据结构,它通过在每个节点中维护多个指向其他节点的指针,从而达到快速访问节点的目的。
5.1、基本结构
文件路径:src/redis.h
// 跳跃表节点值
typedef struct zskiplistNode {
// 各节点中所保存的成员对象
robj *obj;
// 节点按各自所保存的分值从小到大排列
double score;
// 后退指针在程序从表尾向表头遍历时使用
struct zskiplistNode *backward;
// 层
struct zskiplistLevel {
// 前进指针用于访问位于表尾方向的其他节点
struct zskiplistNode *forward;
// 跨度记录了前进指针所指向节点和当前节点的距离
unsigned int span;
} level[];
} zskiplistNode;
// 跳跃表列表
typedef struct zskiplist {
// 跳跃表的表头节点、表尾节点
struct zskiplistNode *header, *tail;
// 记录跳跃表的长度,也即是,跳跃表目前包含节点的数量(表头节点不计算在内)。
unsigned long length;
// 记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内)
int level;
} zskiplist;
5.2、跳跃表的规则
- 每个跳跃表节点的层高都是1至32之间的随机数。
- 在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的成员对象必须是唯一的。
- 跳跃表中的节点按照分值大小进行排序,当分值相同时,节点按照成员对象的大小进行排序。
5.3、应用范围
- 有序集合【有序结合包含的元素数量比较多或者包含的元素是比较长的字符串】
- 集群节点
6、整数集合
整数集合(intset)是Redis用于保存整数值的集合抽象数据结构,它可以保存类型为int16_t、int32_t或者int64_t的整数值,并且保证集合中不会出现重复元素。
6.1、整数结合基本结构
typedef struct intset {
// 编码方式 编码方式决定contents数组存储的类型
uint32_t encoding;
// 集合包含的元素数量
uint32_t length;
// 保存元素的数组
int8_t contents[];
} intset;
6.2、整数集合升级
当一个新元素插入到整数结合中时,当前整数集合的编码不满足新插入的值,会引发整数集合升级,然后才能将新会员数添加到整数集合里面;
整体流程如下
- 根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元素分配空间。
- 将底层数组现有的所有元素都转换成与新元素相同的类型,并将类型转换后的元素放置到正确的位上,而且在放置元素的过程中,需要继续维持底层数组的有序性质不变。
- 将新元素添加到底层数组里面。
6.3、整数集合使用的规则
- 整数集合只支持升级操作,不支持降级操作。
- 升级操作为整数集合带来了操作上的灵活性,并且尽可能地节约了内存。
6.4、应用范围
- 集合对象【只包含整数值元素,并且这个集合的元素数量不多时】
7、压缩列表
压缩列表是redis为了节约内存而开发的,由一系列特殊编码的连续内存块组成的顺序型数据结构;一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值。
7.1、压缩列表结构
7.2、压缩列表节点值
previous_entry_length: 以字节为单位,记录了压缩列表中前一个节点的长度。 previous_entry_length属性的长度可以是1字节或者5字节;
encoding:属性记录了节点的content属性所保存数据的类型以及长度;
content:属性负责保存节点的值,节点值可以是一个字节数组或者整数,值的类型和长度由节点的encoding属性决定。
7.3、应用范围
- 列表对象【列表中仅包含少量列表项,并且每个列表项要么是小整数,要么是比较短的字符串】
- 哈希对象【哈希仅包含少量键值对,并且每个键值对的键和值要么是小整数,要么就是长度比较短的字符串】