您的位置:首页 > 数据库 > Redis

Redis源码解析1 - 程序框架

2012-09-18 17:41 337 查看

前言

(转载请注明出处:/article/7160085.html

Redis(REmote DIctionary Server)是一个由Salvatore Sanfilippo写的key-value存储系统。
它有以下特点:

性能极高 – Redis能支持超过 100K+ 每秒的读写频率

丰富的数据类型 – Redis支持二进制兼容的 string、list、set、zset、hash 等数据结构

原子 – Redis的所有操作都是原子性的

事务 - 支持多条指令合并成事务执行

丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性

脚本 - 新版本Redis支持lua脚本,可以实现更复杂的逻辑

总体结构

Redis是一个单线程的服务器(除了写磁盘会开子进程,VM管理会开线程,先忽略这两块)
所以,它的服务器启动流程非常清晰,看下图,不再赘述



事件循环

1. 数据结构

通过 aeEventLoop 结构来表示一个事件框架,分析如下:

typedef struct aeEventLoop {
int maxfd; // 最大句柄号,只有select模型会用到
long long timeEventNextId; // timer事件的ID,初始化为0,使用时递增
aeFileEvent events[AE_SETSIZE]; // 所有注册的事件,通过aeCreateFileEvent函数增加,aeDeleteFileEvent函数删除。注意,该数组以 fd 为下标进行存取
aeFiredEvent fired[AE_SETSIZE];  // 当前帧激活事件,aeApiPoll函数将活跃事件加入到该数组。注意,该结构只保存fd,通过fd到events中取实际event指针
aeTimeEvent *timeEventHead; // timer事件链表
int stop;
void *apidata; /* This is used for polling API specific data */
aeBeforeSleepProc *beforesleep;
} aeEventLoop;


2. 事件函数分发流程

redis支持 epoll、kqueue、select 三种网络模型

网络框架的代码实现主要在以下几个文件中:

ae.c / ae.h // 网络框架
ae_epoll.c // epoll模型的实现
ae_kqueue.c // kqueue模型的实现
ae_select.c // select模型的实现

程序选择哪一种,是在编译期确定的

1 file ae.c
2
3 #ifdef HAVE_EPOLL
4 #include "ae_epoll.c"
5 #else
6     #ifdef HAVE_KQUEUE
7     #include "ae_kqueue.c"
8     #else
9     #include "ae_select.c"
10     #endif
11 #endif


框架函数非常简单,从初始化到结束,主要的函数就3个
aeCreateEventLoop、aeMain、aeDeleteEventLoop
其中,aeMain是事件循环的主体函数,它又会调用 aeProcessEvents函数

三个主体函数会调用 aeApiCreate、aeApiPool、aeApiFree三个接口函数进行处理
这三个接口函数又会映射到具体的某一种网络模型中,而这是在编译期确定下来的

具体如下图所示:



添加删除事件,由
aeCreateFileEvent、aeDeleteFileEvent函数完成,其函数分发流程如下图:



2. 服务端socket建立流程

1. 在服务器初始化时,建立侦听socket,并绑定IP、Port
支持 TCP连接 与本机的unixSocket连接

if (server.port != 0) {
server.ipfd = anetTcpServer(server.neterr,server.port,server.bindaddr);
... ...
}
if (server.unixsocket != NULL) {
unlink(server.unixsocket); /* don't care if this fails */
server.sofd = anetUnixServer(server.neterr,server.unixsocket,server.unixsocketperm);
... ...
}


2. 侦听socket绑定到事件句柄

if (server.ipfd > 0 && aeCreateFileEvent(server.el,server.ipfd,AE_READABLE,
acceptTcpHandler,NULL) == AE_ERR) oom("creating file event");
if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
acceptUnixHandler,NULL) == AE_ERR) oom("creating file event");


其中“acceptTcpHandler”、“acceptUnixHandler” 是事件回调函数
当新连接到达时,会触发进入这两个函数

3. 再来看看 accept***Handler 里做了什么

void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
int cport, cfd;
char cip[128];
REDIS_NOTUSED(el);
REDIS_NOTUSED(mask);
REDIS_NOTUSED(privdata);

// cfd是新连接产生的 socket 句柄
cfd = anetTcpAccept(server.neterr, fd, cip, &cport);
if (cfd == AE_ERR) {
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
return;
}
redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);
acceptCommonHandler(cfd);  // 继续到这个函数里创建redisClient
}


acceptCommonHandler中做的事很简单,调用 CreateClient函数,创建新的redisClient对象

static void acceptCommonHandler(int fd) {
redisClient *c;
if ((c = createClient(fd)) == NULL) {
redisLog(REDIS_WARNING,"Error allocating resoures for the client");
close(fd); /* May be already closed, just ingore errors */
return;
}
... ...
}


在 createClient函数中,将新连接绑定到事件循环中

redisClient *createClient(int fd) {
redisClient *c = zmalloc(sizeof(redisClient));
c->bufpos = 0;

anetNonBlock(NULL,fd);
anetTcpNoDelay(NULL,fd);
if (aeCreateFileEvent(server.el,fd,AE_READABLE,
readQueryFromClient, c) == AE_ERR) // 在这里注册了Read事件的回调处理函数
{
close(fd);
zfree(c);
return NULL;
}


当连接上有数据到达时,便会触发 readQueryFromClient函数,进行实际的网络数据读取与处理

3. Timer事件

Redis将所有Timer绑定到事件循环中进行处理
通过函数 aeCreateTimeEvent 创建新的Timer事件
每一帧事件循环中,通过processTimeEvents函数进行处理

static int processTimeEvents(aeEventLoop *eventLoop) {
int processed = 0;
aeTimeEvent *te;
long long maxId;

te = eventLoop->timeEventHead;
maxId = eventLoop->timeEventNextId-1;
while(te) {
long now_sec, now_ms;
long long id;

// 请注意这一处条件判断
// 它的作用是防止死循环,限定当前帧只处理以前帧加入的Timer事件
if (te->id > maxId) {
te = te->next;
continue;
}
aeGetTime(&now_sec, &now_ms);
if (now_sec > te->when_sec ||
(now_sec == te->when_sec && now_ms >= te->when_ms))
{
int retval;

id = te->id;
retval = te->timeProc(eventLoop, id, te->clientData);
processed++;

... ...


从代码里可以看出,Redis中对Timer的处理是非常粗糙的,并没有用传统的小根堆之类的数据结构
而是简单的用一个链表
这可能是因为Redis中对Timer的依赖程度较低,事件很少
不像 libevent 等通用库,允许用户自由添加timer,无法控制数量,所以要用较高效的数据结构
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: