Redis源码阅读
文章目录
1 数据结构
1.1 动态字符串SDS
Redis自己实现了动态字符串,相关的源码包含在sds.c和sds.h两个文件中,先来看sds.h中的相关定义。
- 为了和C语言中的字符串兼容,redis中SDS的定义其实就是
char*
typedef char *sds;
- SDS完整的内存结构则是将字符串的头部分隐藏在了之际字符串的前面,以sdshdr8为例来看
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len;
uint8_t alloc;
unsigned char flags;
char buf[];
};
len
即是当前字符串已经占用的长度。alloc
用于表示当前给字符串分配的长度。flags
低三位用来区分当前结构的具体类型。- 各个header的定义中最后有一个
char buf[]
。我们注意到这是一个没有指明长度的字符数组,这是C语言中定义字符数组的一种特殊写法,称为柔性数组(flexible array member),只能定义在一个结构体的最后一个字段上。它在这里只是起到一个标记的作用,表示在flags字段后面就是一个字符数组,或者说,它指明了紧跟在flags字段后面的这个字符数组在结构体中的偏移位置。而程序在为header分配的内存的时候,数组本身并不占用内存空间。如果计算sizeof(struct sdshdr8)
的值,那么结果是3个字节。 - 结构体采用
__attribute__ ((__packed__))
的定义方式是为了让编译器紧凑地分配空间,而不是按照字节对齐地方式,这样做的目的是为了让结构体内的所有变量能在内存种上下紧密排列,以便快速寻址。
SDS_HDR_VAR
和SDS_HDR
这两个宏用于获取SDS头的位置
#define SDS_TYPE_5 0
#define SDS_TYPE_8 1
#define SDS_TYPE_16 2
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
- SDS的基本操作封装
static inline size_t sdslen(const sds s); /*获取sds字符串长度*/
static inline void sdssetlen(sds s, size_t newlen); /*设置sds字符串长度*/
static inline void sdsinclen(sds s, size_t inc); /*增加sds字符串长度*/
static inline size_t sdsalloc(const sds s); /*获取sds字符串容量*/
static inline void sdssetalloc(sds s, size_t newlen); /*设置sds字符串容量*/
static inline size_t sdsavail(const sds s); /*获取sds字符串空余空间*/
static inline int sdsHdrSize(char type); /*获取当前类型type的头长度*/
static inline char sdsReqType(size_t string_size); /*获取请求长度size对应的类型*/
/*
获取当前sds的已使用长度
*/
static inline size_t sdslen(const sds s) {
unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
return SDS_TYPE_5_LEN(flags);
case SDS_TYPE_8:
return SDS_HDR(8,s)->len;
case SDS_TYPE_16:
return SDS_HDR(16,s)->len;
case SDS_TYPE_32:
return SDS_HDR(32,s)->len;
case SDS_TYPE_64:
return SDS_HDR(64,s)->len;
}
return 0;
}
-
flags的获取使用了
s[-1]
的方式,这与之前进行结构体声明是的紧凑分配内存息息相关。 -
inline
关键字是在C语言中表示内联函数。在c中,为了解决一些频繁调用的小函数大量消耗栈空间或是叫栈内存的问题,特别的引入了inline修饰符,表示为内联函数。inline函数仅仅是一个建议,对编译器的建议,所以最后能否真正内联,看编译器的意思,它如果认为函数不复杂,能在调用点展开,就会真正内联,并不是说声明了内联就会内联,声明内联只是一个建议而已。
- sds.c中定义了一些对sds的申请,设置长度,更新长度,新分配长度等操作
len = sdslen(s);
sh = (char*)s-sdsHdrSize(oldtype);
newlen = (len+addlen);
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
有趣的是,当需要扩充长度时,如果新的长度不足SDS_MAX_PREALLOC=1024*1024
时,会将申请的长度乘以2作为新分配的长度,而当新长度超过SDS_MAX_PREALLOC
时则再新长度后扩增一段SDS_MAX_PREALLOC
。
1.2 双向链表ADLIST
双向链表的实现相对来说比较直白
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
typedef struct listIter {
listNode *next;
int direction;
} listIter;
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;
1.3 字典 DICT
Redis对字典的实现,首先是字典单个条项的定义:
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry;
dictEntry
是字典的一条记录,也就是所谓的桶(bucket)key
是键v
是值next
是指向筒内下一条记录的指针
字典类型的定义则包含了本类型的字典对应的相关处理函数,包括哈希计算函数和元素比较函数等。
typedef struct dictType {
/*哈希函数定义*/
uint64_t (*hashFunction)(const void *key);
void *(*keyDup)(void *privdata, const void *key);
void *(*valDup)(void *privdata, const void *obj);
/*比较函数定义*/
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
void (*keyDestructor)(void *privdata, void *key);
void (*valDestructor)(void *privdata, void *obj);
} dictType;
字典本身的定义以及哈希表的定义如下:
/* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
unsigned long iterators; /* number of iterators currently running */
} dict;
ditcht
是哈希表的定义,包括了一个指向若干个*dictEntry
类型的数组头指针table
,以及当前哈希表的大小size
,还有用于计算索引项的sizemask
,used
是当前哈希表中已经占用的数量。- 字典本身定义在
dict
中,包括当前字典的类型type
,两张哈希表ht[2]
,渐进式哈希的进度标识rehashidx
,iterators
则表示当前绑定到该字典上的遍历体个数。
dict在内存中的结构如下图:
Redis对于字典的设计里,一大特点就是采用了两张哈希表,并且在使用过程中不断地通过dictRehash
方法将一张哈希表的数据迁移到另一张表中,以达到扩容的目的,具体代码如下:
int dictRehash(dict *d, int n) {
int empty_visits = n*10; /* Max number of empty buckets to visit. */
if (!dictIsRehashing(d)) return 0;
while(n-- && d->ht[0].used != 0) {
dictEntry *de, *nextde;
/* Note that rehashidx can't overflow as we are sure there are more
* elements because ht[0].used != 0 */
assert(d->ht[0].size > (unsigned long)d->rehashidx);
while(d->ht[0].table[d->rehashidx] == NULL) {
d->rehashidx++;
if (--empty_visits == 0) return 1;
}
de = d->ht[0].table[d->rehashidx];
/* Move all the keys in this bucket from the old to the new hash HT */
while(de) {
uint64_t h;
nextde = de->next;
/* Get the index in the new hash table */
h = dictHashKey(d, de->key) & d->ht[1].sizemask;
de->next = d->ht[1].table[h];
d->ht[1].table[h] = de;
d->ht[0].used--;
d->ht[1].used++;
de = nextde;
}
d->ht[0].table[d->rehashidx] = NULL;
d->rehashidx++;
}
/