深入分析Redis从源码的角度阅读(redis 源码阅读)

深入分析Redis:从源码的角度阅读

Redis是一个高性能内存键值数据库,常常用于缓存、消息队列等场景。Redis源码设计精妙,许多细节值得学习。本文将从源码的角度分析Redis的设计思路,帮助读者更深入地理解Redis。

1. Redis数据结构

Redis使用了多种数据结构,如字符串、列表、哈希表、集合等。不同数据结构的实现方式各不相同,下面以字符串为例,介绍Redis的实现方式。

Redis中的字符串是字节数组,使用SDS(Simple Dynamic Strings)作为实现。SDS具有以下特点:

1)自动扩容:当进行字符串的修改/拼接等操作时,SDS能自动扩容。

2)二进制安全:SDS不以’\0’结尾,可以存储任意二进制数据。

3)惰性空间释放:SDS中的空间不是即时释放的,而是等到需要重用空间时才释放,提高了空间的利用效率。

下面是SDS的结构体定义:

struct sdshdr {
// 记录buf数组中已使用字节的数量,不包括空闲部分
int len;
// 记录buf数组中未使用字节的数量,不包括已使用部分
int free;
// 字节数组,存储字符串
char buf[];
};

SDS的扩容实现:

/*
* 扩展空间,确保最少有addlen字节的剩余空间
* 思路:重新分配一块空间,其大小为len+addlen,然后将原字符串拷贝到新分配的空间中,
* 最后释放旧空间
*/
sds sdsMakeRoomFor(sds s, size_t addlen) {
struct sdshdr *sh, *newsh;
// 计算已使用空间和未使用空间大小之和
size_t free = sdsavl(s);
size_t len, newlen;
if (free >= addlen) {
// 原空间剩余空间足够,直接返回
return s;
}
// 计算新空间大小
len = sdslen(s);
sh = (void*)(s-(sizeof(struct sdshdr)));
newlen = (len+addlen);
// 扩容策略,每次扩容大小为原字符串长度的两倍
if (newlen
newlen *= 2;
else
newlen += SDS_MAX_PREALLOC;
// 重新分配一块新的空间
newsh = zrealloc(sh,sizeof(struct sdshdr)+newlen+1);
if (newsh == NULL) return NULL;
newsh->free = newlen - len;
return newsh->buf;
}

2. Redis事件处理器

Redis使用事件驱动模型来处理IO操作。Redis的事件处理器使用epoll作为内核对象,其结构体如下:

struct aeEventLoop {
// epoll句柄
int epfd;
// 注册的事件
aeFileEvent *events;
// 派发操作前存放需要操作的事件列表
aeFiredEvent *fired;
// 将被阻塞的最大毫秒数
int maxfd;
// 已注册的最大文件描述符
int beforeSleep;
// 销毁事件前执行的函数列表
aeBeforeSleepProc *beforesleep;
// 请求停止事件循环
int stop;
// 多路复用库名称
char *name;
// 使用的多路复用库
aeApiState *apidata;
// 定时器事件
list *timeEvent;
// 最近一次执行时间事件的时间
struct timeval lastTime;
};

下面是Redis的事件处理器的示例代码:

/* 创建事件处理器 */
aeEventLoop *aeCreateEventLoop(int setsize) {
aeEventLoop *eventLoop;
int i;
/* 创建事件状态结构 */
if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err;
...
eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize);
eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize);
...
/* 设置epoll句柄 */
if (aeApiCreate(eventLoop) == -1) goto err;
/* 初始化定时器 */
if (server.hz) {
if (aeCreateTimeEvent(eventLoop,1,serverCron,NULL,NULL) == AE_ERR)
goto err;
}
/* 设置默认命中概率 1:100 */
eventLoop->randseed = time(NULL);
eventLoop->setsize = setsize;
eventLoop->lastTime = time(NULL);
return eventLoop;
...
}
/* 事件循环 */
void aeMn(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
/* 在执行事件之前,执行休眠前置函数 */
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
/* 开始处理事件 */
aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
}
}

3. Redis对象模型

Redis的对象模型非常简洁灵活,分为字符串对象、列表对象、哈希对象、集合对象、有序集合对象。不同的对象有不同的行为方法,具体实现方式如下:

字符串对象:

struct redisObject {
// 对象类型
unsigned type:4;
// 对象编码
unsigned encoding:4;
// 对象指针
void *ptr;
// ...
};

列表对象:

typedef struct list {
// 表头
listNode *head;
// 表尾
listNode *tl;
// 节点数量
unsigned long len;
// 节点值复制函数指针,可以为NULL
void *(*dup)(void *ptr);
// 节点值释放函数指针,可以为NULL
void (*free)(void *ptr);
// 节点值对比函数指针,可以为NULL
int (*match)(void *ptr, void *key);
} list;

Redis的哈希对象:

typedef struct dictEntry {
// 键
void *key;
// 值
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
// 指向下一个节点的指针
struct dictEntry *next;
} dictEntry;
typedef struct dictht {
// 哈希表数组
dictEntry **table;
// 哈希表大小
unsigned long size;
// 哈希表大小掩码
unsigned long sizemask;
// 哈希表已有节点数量
unsigned long used;
} dictht;
typedef struct dictType {
// 计算哈希值的函数指针
unsigned int (*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;

集合对象:

typedef struct {
// 基于哈希表实现的集合
dict *dict;
} set;

有序集合对象:

typedef struct zset {
// 基于哈希表实现的有序集合
dict *dict;
// 跳跃表,按照分值从大到小维护元素
zskiplist *zsl;
} zset;

Redis源码精妙而庞大


数据运维技术 » 深入分析Redis从源码的角度阅读(redis 源码阅读)