Redis 源代码分析
文档审核人文档拟制人文档提交时间胡戊(huwu)
邹雨晗(yuhanzou) Jun 17, 2011
文档修改记录
文档更新时间变更内容变更提出部门变更理由Jun 17, 2011初稿
目录
1.Redis介绍!4
2.基本功能!4
2.1.链表(adlist.h/adlist.c)!4
2.2.字符串(sds.h/sds.c)!5
2.3.哈希表(dict.h/dict.c)!6
2.4.内存(zmalloc.h/zmalloc.h)!9
resize函数c++
3.服务器模型!11
3.1.事件处理(ae.h/ae.c)!11
3.2.套接字操作(anet.h/anet.c)!15
3.3.客户端连接(networking.h/networking.c, redis.c/redis.h)!15
3.4.命令处理!17
4.虚拟内存!19
4.1.数据读取过程!20
4.2.数据交换策略!23
5.备份机制!24
5.1.Snapshot!24
5.2.AOF!26
6.主从同步!27
6.1.建立连接!27
6.2.指令同步!30
6.3.主从转换!31
1.Redis介绍
Redis(redis.io)是一个开源的Key-Value存储引擎。它支持string、hash、list、set和sorted set等多种值类型。
2.基本功能
2.1.链表(adlist.h/adlist.c)
链表(list)是Redis中最基本的数据结构,由adlist.h和adlist.c定义。
基本数据结构
listNode是最基本的结构,标示链表中的一个结点。结点有向前(next)和向后(prev)连个指针,链表是双向链表。保存的值是void*类型。
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
链表通过list定义,提供头(head)、尾(tail)两个指针,分别指向头部的结点和尾部的结点;提供三个函数指针,供用户传入自定义函数,用于复制(dup)、释放(free)和匹配(match)链表中的结点的值(value);通过无符号整数len,标示链表的长度。typedef struct list {
listNode *head;
listNode *tail;
void *(*dup)(void *ptr);
void (*free)(void *ptr);
int (*match)(void *ptr, void *key);
unsigned int len;
} list;
listIter是访问链表的迭代器,指针(next)指向链表的某个结点,direction标示迭代访问的方向(宏AL_START_HEAD表示向前,AL_START_TAIL表示向后)。
typedef struct listIter {
listNode *next;
int direction;
} listIter;
使用方法
Redis定义了一系列的宏,用于访问list及其内部结点。
链表创建时(listCreate),通过Redis自己实现的zmalloc()分配堆空间。链表释放(listRelease)或删除结点(listDelNode)时,如果定义了链表(list)的指针函数free,Redis会使用它释放链表的每一个结点的值(value),否则需要用户手动释放。结点的内存使用Redis自己实现的zfree()释放。
对于迭代器,通过方法listGetIterator()、listNext()、listReleaseIterator()、listRewind()和listRewindTail()使用,例如对于链表list,要从头向尾遍历,可通过如下代码:
iter = listGetIterator(list, AL_START_HEAD); // 获取迭代器
while((node = listNext(iter)) != NULL) {
DoItWithValue(listNodeValue(node)); // 用户实现DoItWithValue
}
listReleaseIterator(iter);
listDup()用于复制链表,如果用户实现了dup函数,则会使用它复制链表结点的value。listSearchKey()
通过循环的方式在O(N)的时间复杂度下查值,若用户实现了match函数,则用它进行匹配,否则使用按引用匹配。
2.2.字符串(sds.h/sds.c)
字符串是Redis中最基本的数据。Redis使用key作为存取value的唯一标示符,而key的通俗理解就是字符串。Redis中的字符串分为两类:二进制安全(Binary Safe)和非二进制安全。二进制安全的字符串,是指字符串中所有字符均可用256个字符(8bit)编码[2]。Redis中的value都是通过二进制安全的字符串存储的,而key使用的是非二进制安全的。
基本数据结构
Redis内部实现了字符串类型,由sds.h和sds.c定义。
sds本质是char*:
typedef char *sds;
通过sdsnewlen()创建时,Redis内部会创建sdshdr结构,这个结构的定义如下,其中len表示字符串的实际长度,free表示可以额外使用的字节数。sdshdr分配的内存空间,为sizeof(int)+sizeof(int)+sizeof(char*)+len+free。
struct sdshdr {
int len;
int free;
char buf[];
};
在sdsnewlen(const void *init, size_t initlen)中,会分配sizeof (struct sdshdr)+initlen+1的空间,并将*init指向的字符串拷贝到buf中,在buf 末尾补上’\0’。但是sdsnewlen()中返回给外部的,只有sdshdr->buf。
假如用户调用了sdsnewlen(“qqmail”, 6),则64位系统中,Redis会分配24+6+1字节的空间,在内存中的结构如下:
60qqmail
返回给用户的sdshdr->buf,指向内存中”qqmail”的起点,想通过buf获得指向sdshdr的指针是很容易的,例如方法sdslen()中:
size_t sdslen(const sds s) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
return sh->len;
}
通过(s-(sizeof(struct sdshdr))即可得到sdshdr的地址。
使用方法
了解了sdshdr的结构之后,理解相关的方法就很容易了。而Redis这样仅暴露buf的做法,使得应用程序可以将sds简单地当成char*使用。