Redis使用五种类型对象实现实现键值对数据库:字符串、列表、哈希、集合、有序集合
- 字符串编码 int, raw或embstr
- embstr编码将创建字符串对象所需的内存分配次数从raw编码的两次降低为一次
- 释放embstr编码的字符串对象只需要调用一次内存释放函数,而raw需要两次
- embstr编码的字符串对象所有数据都保存在一块连续内存中,比raw编码方式能够更好地利用缓存带来的优势
- 列表编码 ziplist或linkedlist
- 哈希编码 ziplist或hashtable
- 集合编码 intset或hashtable
- 有序集合编码 ziplist或skiplist
SDS(simple dynamic string)
struct sdshdr{
int len;
int free;
char buf[];
};
与C字符串的区别
- 常数复杂度获取字符串长度
- 杜绝缓冲区溢出
- 减少修改字符串时带来的内存重分配次数
- 空间预分配
- 惰性空间释放
- 二进制安全
- 兼容部分C字符串函数
双端链表
节点结构
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value; // void* 指针可以用于保存各种不同类型的值
}listNode;
持有结构
typedef struct list {
listNode *head;
listNode *tail;
unsigned long len;
// 复制链表节点保存的值
void *(*dup)(void *ptr);
// 释放链表节点保存的值
void (*free)(void *ptr);
// 对比链表节点保存的值和另一个输入是否相等
void (*match)(void *ptr, void *key);
}list;
特性
- 双端
- 无环
- 带表头指针和表尾指针
- 带链表长度计数器
- 多态
哈希表
typedef struct dictht {
// 哈希表数组
dictEntry **table;
// 哈希表大小
unsigned long size;
// 哈希表大小掩码,用于计算索引值
// 总是等于size-1
unsigned long sizemask;
// 已有节点数量
unsigned long used;
} dictht;
哈希表节点
typedef struct dictEntry {
void *key;
union{
void *val;
uint64_t u64;
int64_t s64;
} v;
struct dictEntry *next; // 解决冲突
}dictEntry;
字典
typedef struct dict {
// 类型特定函数
dictType *type;
// 私有数据
void *privdata;
// 哈希表
dictht ht[2];
// rehash 索引
// 当rehash不在进行时,值为-1
int trehashidx;
} dict;
type是一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数,Redis会为用途不同的字典设置不同的类型特定函数。
typedef struct dictType {
// 计算哈希值的函数
unsigned int (*hashFunction)(const void *key);
// 复制键的函数
void *(*keyDup)(void *private, const void *key);
// 复制值
void *(*valDup)(void *private, const void *obj);
// 对比键的函数
void (*keyCompare)(void *private, const void *key1, const void *key2);
// 销毁键的函数
void (*keyDestructor)(void *private, void *key);
// 销毁值的函数
void (*valDestructor)(void *private, void *obj);
} dictType;
hash算法
# 使用字典设置的哈希函数,计算键key的哈希值
hash = dict->type->hashFunction(key);
# 使用哈希表的sizemask属性和哈希值,计算出索引值
# 根据情况不同,ht[x] 可以是 ht[0] 或 ht[1]
index = hash & dict->ht[x].sizemask
Redis使用MurmurHash2算法来计算键的哈希值
解决键冲突
Redis使用开链法(separate chaining)来解决地址冲突
渐进式rehash
扩展或收缩哈希表时进行
扩展条件:
- 没有执行BGSAVE或BGREWRITEAOF命令,哈希表的负载因子大于等于1
- 正在执行BGSAVE或BGREWRITEAOF命令,哈希表的负载因子大于等于5
收缩条件:
哈希表负载因子小于0.1
步骤:
- 为ht[1]分配空间
- 将rehashidx设为0
- rehash期间,每次对字典执行添加、删除、查找或更新操作时,程序除了执行指定操作外,还会顺带将ht[0]中rehashidx索引上的所有键值对rehash到ht[1],当rehash工作完成之后,程序将rehashidx属性的值增1。(单次rehash)
- 随着字典操作的不断执行,最终在某个时间点上,ht[0]的所有键值对都会被rehash至ht[1],这时程序将rehashidx属性值设为-1,rehash完成。
rehash期间,字典的删、改、查会在两个哈希表上进行。新添加到字典的键值对一律保存到ht[1],保证ht[0]包含的键值对数量只会减少不会增加
跳跃表
整数集合
typedef struct intset {
// 编码方式
uint32_t encoding;
// 元素数量
uint32_t length;
// 元素
// 虽为int8_t类型,但实际contents不保存任何int8_t类型的值,
// contents数组的真正类型取决于encoding属性的值
int8_t contents[];
} intset;
压缩列表
一系列特殊编码的连续内存块组成的顺序型(sequential)数据结构
组成:
zlbytes | zltail | zllen | entry1 | entry2 | … | entryN | zlend |
---|
zlbytes: 占用内存字节数。4字节
zltail: 表尾节点(entryN)距压缩列表起点偏移。4字节
zllen: 节点数量。2字节
entryX: 节点。不定
zlend: 标记列表末端,1字节,为0xff