Redis源码解析1 - 程序框架

前言

(转载请注明出处:http://www.cnblogs.com/curve/archive/2012/09/18/2683226.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 结构来表示一个事件框架,分析如下:

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

2. 事件函数分发流程

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

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

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

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

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

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

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

具体如下图所示:

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

         

2. 服务端socket建立流程

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

1     if (server.port != 0) {
2         server.ipfd = anetTcpServer(server.neterr,server.port,server.bindaddr);
3         ... ... 
4     }
5     if (server.unixsocket != NULL) {
6         unlink(server.unixsocket); /* don't care if this fails */
7         server.sofd = anetUnixServer(server.neterr,server.unixsocket,server.unixsocketperm);
8         ... ...
9     }

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

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

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

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

 1 void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
 2     int cport, cfd;
 3     char cip[128];
 4     REDIS_NOTUSED(el);
 5     REDIS_NOTUSED(mask);
 6     REDIS_NOTUSED(privdata);
 7 
 8     // cfd是新连接产生的 socket 句柄
 9     cfd = anetTcpAccept(server.neterr, fd, cip, &cport);
10     if (cfd == AE_ERR) {
11         redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
12         return;
13     }
14     redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);
15     acceptCommonHandler(cfd);  // 继续到这个函数里创建redisClient
16 }

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

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

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

 1 redisClient *createClient(int fd) {
 2     redisClient *c = zmalloc(sizeof(redisClient));
 3     c->bufpos = 0;
 4 
 5     anetNonBlock(NULL,fd);
 6     anetTcpNoDelay(NULL,fd);
 7     if (aeCreateFileEvent(server.el,fd,AE_READABLE,
 8         readQueryFromClient, c) == AE_ERR) // 在这里注册了Read事件的回调处理函数
 9     {
10         close(fd);
11         zfree(c);
12         return NULL;
13     }

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

3. Timer事件

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

 1 static int processTimeEvents(aeEventLoop *eventLoop) {
 2     int processed = 0;
 3     aeTimeEvent *te;
 4     long long maxId;
 5 
 6     te = eventLoop->timeEventHead;
 7     maxId = eventLoop->timeEventNextId-1;
 8     while(te) {
 9         long now_sec, now_ms;
10         long long id;
11 
12         // 请注意这一处条件判断
13         // 它的作用是防止死循环,限定当前帧只处理以前帧加入的Timer事件
14         if (te->id > maxId) {
15             te = te->next;
16             continue;
17         }
18         aeGetTime(&now_sec, &now_ms);
19         if (now_sec > te->when_sec ||
20             (now_sec == te->when_sec && now_ms >= te->when_ms))
21         {
22             int retval;
23 
24             id = te->id;
25             retval = te->timeProc(eventLoop, id, te->clientData);
26             processed++;
27 
28          ... ...

从代码里可以看出,Redis中对Timer的处理是非常粗糙的,并没有用传统的小根堆之类的数据结构
而是简单的用一个链表
这可能是因为Redis中对Timer的依赖程度较低,事件很少
不像 libevent 等通用库,允许用户自由添加timer,无法控制数量,所以要用较高效的数据结构

 

 

posted on 2012-09-18 17:41  curve  阅读(1356)  评论(1编辑  收藏  举报

导航