一 字符串对象(String)
字符串在Redis的内存中是以char数组的形式存在的。具体是一个叫做“SDS”的结构体,根据存储数据的char数组的实际使用长度大小,有多种不同的SDS结构体。
/*
注释:姚子威
内容:char数组的实际使用长度大于2的32次方小于2的64次方时的SDS结构体
*/
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; //char数组实际使用的长度
uint64_t alloc; //分配给存储数据的char数组总长度
unsigned char flags; //SDS结构体类型的标志位
char buf[]; //真正存储数据的char数组
};
在Redis中,所有对象都有对象头结构体。
/*
注释:姚子威
内容:Redis对象头
*/
typedef struct redisObject {
unsigned type:4; //对象类型
unsigned encoding:4; //对象编码
unsigned lru:LRU_BITS; //时间戳 用于LRU算法
int refcount; //引用计数
void *ptr; //实际指向的类型
} robj;
SDS和RedisObject有两种组合形式,当长度特别短的时候,采用embstr形式存储,当长度超过44字节的时候,采用raw形式存储。在内存关系上,采用embstr形式时,RedisObject和SDS内存空间连续,而raw形式下,则不连续。
二 列表对象(List)
列表在使用上可以理解为双向链表。在列表的内部结构是快速列表(quicklist)结构,快速列表的节点是压缩列表(ziplist),一个个压缩列表以链表的形式组合起来形成快速列表。
/*
注释:姚子威
内容:快速列表头
*/
typedef struct quicklist {
quicklistNode *head;
quicklistNode *tail;
unsigned long count; //所有ziplist的槽的总和
unsigned long len; //节点的数量
int fill : QL_FILL_BITS; //节点中的ziplist槽的数量的最大值
unsigned int compress : QL_COMP_BITS; //快速列表的压缩程度
unsigned int bookmark_count: QL_BM_BITS; //书签数组大小
quicklistBookmark bookmarks[]; //书签数组
} quicklist;
/*
注释:姚子威
内容:快速列表节点
*/
typedef struct quicklistNode {
struct quicklistNode *prev;
struct quicklistNode *next;
unsigned char *zl; //指向ziplist或者quicklistLZF
unsigned int sz; //节点中的ziplist占用了多少比特
unsigned int count : 16; //节点中的ziplist槽的数量
unsigned int encoding : 2; //编码形式
unsigned int container : 2; //节点中存放数据的形式
unsigned int recompress : 1; //节点之前是否被压缩过
unsigned int attempted_compress : 1; //测试时使用
unsigned int extra : 10; //额外分配
} quicklistNode;
三 哈希对象(Hash)
(1)当哈希对象中的元素比较多时,采用字典这种数据结构存储,字典数据结构在Redis中有两个哈希表,正常情况下只有一个哈希表被使用,但是当发生扩容的时候,另外一个哈希表会被启用,用来保存新加入的键值对和从原来的哈希表中迁移过来的键值对。另外值得一提的是,rehash的过程是渐进的,并不是一次性迁移,而是分配在每次对哈希对象增删改查操作中。
(2)当哈希对象中的元素较少时,采用压缩列表的结构存储。
/*
注释:姚子威
内容:哈希表
*/
typedef struct dictht {
dictEntry **table; //实际数据
unsigned long size; //哈希表大小
unsigned long sizemask; //掩码 用于计算键值对的索引
unsigned long used; //哈希表的实际节点数量
} dictht;
/*
注释:姚子威
内容:字典
*/
typedef struct dict {
dictType *type; //dictType结构体包含一堆特定类型函数指针 可以实现dictType中函数指针的多态 进而实现字典的多态
void *privdata; //传给特定类型函数的参数
dictht ht[2]; //两个HashTable
long rehashidx; //rehash进度 大于0说明进行中 为-1时结束
int16_t pauserehash; //rehash是否暂停 为0说明rehash进行中 大于0说明rehash暂停
} dict;
四 集合对象(Set)
(1)底层结构和哈希表一样,不过里面的键值对的值都为NULL值。
(2)当集合中的元素都是整数且比较少的时候,采用整数集合的数据结构。
/*
注释:姚子威
内容:整数集合
*/
typedef struct intset {
uint32_t encoding; //编码方式
uint32_t length; //包含的元素数量
int8_t contents[]; //实际保存元素的数组
} intset;
五 有序集合对象(Zset)
Zset在Set的基础上为集合中的每个元素添加了一个score,通过score可以对元素进行排序。
Zset结构体由一个字典和跳跃链表组合而成,字典的键就是Set集合中的元素,值就是score,同时用跳跃链表来管理每个元素的逻辑关系。
/*
注释:姚子威
内容:有序集合
*/
typedef struct zset {
dict *dict; //使用字典存储,方便通过指定元素查找score
zskiplist *zsl; //管理跳跃列表节点,方便范围查找
} zset;
/*
注释:姚子威
内容:跳跃链表
*/
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length; //跳跃链表长度 除去头节点
int level; //跳跃链表高度 除去头节点
} zskiplist;
/*
注释:姚子威
内容:跳跃链表节点
*/
typedef struct zskiplistNode {
struct zskiplistNode *backward; //后退指针
double score; //分值
sds ele; //元素值
struct zskiplistLevel {
struct zskiplistNode *forward; //前进指针
unsigned int span; //跨度
} level[];
} zskiplistNode;
下面用一个具体例子来说明zskiplist的查找过程。
(1)头节点的socre是0,元素是NULL。
(2)在跳跃链表中,从头节点到尾节点,在逻辑上是有序的,即从小到大排序。在上图中,所有的score不相同,这样跳跃链表节点之间只比较score的大小就行,如果score相同,还要比较元素的大小,按字符串大小排序。
(3)跳跃链表的查找过程是这样的,定义一个指针X指向头节点,用目标元素比较指针X的最高层的forward指针指向的下一个节点,如果前者大,就更新X指针指向后者,否则就不更新指针X,而是让指针X指向的node节点降层,继续比较大小。
(4)例如,我们要查找的是sds=“i”,那么在上图中,就可以跳过sds="e"直接找到sds=“i”。
(5)每个节点的层数都是在创建时随机确定的。
(6)当Zset中的元素较少时,使用压缩列表的结构保存元素和score。