目录
前言
本文基于 黄建宏-《Redis设计与实现》总结。第一部分为Redis 数据结构解析
1. SDS 的定义
struct sdshdr{
int len; //buf数组中已用字节长度
int free;//buf数组中未用字节长度
char buf[]//buf数组
}
2. 动态字符串
常数复杂度获取字符串长度
O(1),相较于C语言的O(n)
杜绝缓存区溢出
字符串拼接时,SDS API先检查SDS空间,空间不足则扩展空间,再拼接
SDS 两种优化策略
空间预分配
对SDS进行空间扩展时,如果修改后的SDS的长度将小于1MB,则分配和len属性同样大小的未使用空间,即SDS的len属性和free属性相同
如果修改后的SDS的长度将大于1MB,则分配1MB的未使用空间
通过空间预分配策略,Redis可以减少连续执行字符串增长操作所需的内存重分配次数
惰性空间释放
当SDS API需要缩短SDS保存的字符串时,程序不立即使用内存重分配来回收多出来的字节,而是使用free属性记录数量
C字符串 | SDS | 备注 |
获取字符串长度的复杂度为O(N) | 获取字符串长度的复杂度为O(1) | 因为SDS有属性len,而C字符串需要遍历直至找到空字符串 |
API是不安全的,可能会造成缓冲区溢出 | API是安全的,不会造成缓冲区溢出 | SDS API先检查空间,空间不足扩展后在拼接 |
修改字符串长度N次必然需要执行N次内存重分配 | 修改字符串长度N次最多需要执行N次内存重分配 | 空间预分配策略 |
只能保存文本数据 | 可以保存文本或者二进制数据 | C字符串除了末尾不能有空字符,因此不能八平村二进制数据 |
可以使用所有<string.h>库中的函数 | 可以使用一部分<string.h>库中的函数 | 因为SDS遵循了C字符串以空字符结尾的惯例 |
3. 链表和链表节点
每个链表节点用listNode结构表示
typedef struct listNode {
//前置节点
struct listNode *prev;
//后置节点
struct listNode *next;
//节点的值
void *value;
}listNode;
链表结构用list结构表示
typedef struct list {
//表头节点
listNode *head;
//表尾节点
listNode *tail;
//链表所包含的节点数量
unsigned long len;
//节点值复制函数
void *(*dup)(void *ptr);
//节点值释放函数
void (*free)(void *ptr);
//节点值对比函数
int (*match)(void *ptr,void *key);
}list;
Redis的链表实现的特性:
双端
链表节点带有prev和next指针,获取某个节点的前置节点和后置节点的复杂度都是O(1)。
无环
表头节点的prev指针和表尾节点的next指针都指向NULL,对链表的访问以NULL为终点。
带表头指针和表尾指针
通过list结构的head指针和tail指针,程序获取链表的表头节点和表尾节点的复杂度为O(1)。
带链表长度计数器
程序使用list结构的len属性来对list持有的链表节点进行计数,程序获取链表中节点数量的复杂度为O(1)。
多态
链表节点使用void*指针来保存节点值,并且可以通过list结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。
4. 字典的实现
4.1 字典结构
哈希表
typedef struct dichht {
//哈希表数组
dictEntry **table;
//哈希表大小
unsigned long siez;
//哈希表大小掩码,用于计算索引值,总是等于size -1
unsigned long sizemark;
//该哈希表已有节点的数量
unsigned long used;
}dictht;
哈希表节点
typedef struct dictEntry {
//键
void *key;
//值
union {
void *val;
uint64_tu64;
int64_ts64;
}v;
//指向下个哈希表节点,形成链表
struct dictEntry *next;
}dictEntry;
v属性保存键值对中的值,其中键值对的值可以是以一个指针,或者一个uint64_t整数,又或者是一个int64_t整数
字典
typedef struct dict {
//类型特定函数
dictType *type;
//私有数据
void *privdata;
//哈希表
dictht ht[2];
//rehash索引,当rehash不在进行时,值为-1
int rehashidx;
}dict;
type属性和privdata属性是针对不同类型的键值对,为创建多态字典而设置的:
type属性是以恶搞只想dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数,Redis会为用途不同的字典设置不同的类型特定函数
privdata属性保存了需要传给那些类型特定函数的可选参数
ht属性是一个包含两项的数组,数组的每一项都是一个dictht哈希表,一般情况下,字典只用ht[0]哈希表,ht[1]哈希表只在对ht[0]哈希表进行rehash时使用
4.2 哈希算法
先根据键值对的key计算出hash和index,再根据index将该哈希表节点放到哈希表数组的指定index
hash = dict -> type -> hashFunction(key)
index = hash & dict ->ht[x].sizemark (rehash 时 x= 1,没有rehash时 x =0)
当字典被用于数据库的底层实现,或者哈希键的底层实现时,Redis使用MurmurHash2算法来计算键的哈希值
4.3 哈希冲突
Redis的哈希表通过每个哈希表节点的next指针来连接索引相同的哈希表节点,采取头插法
4.4 rehash
随着操作的不断执行,哈希表保存的键值对会增多或减少,为了保证哈希表