深入分析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源码精妙而庞大