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,无法控制数量,所以要用较高效的数据结构
相关文章推荐
- 【安卓网络请求开源框架Volley源码解析系列】初识Volley及其基本用法
- String系列源码解析01 - 总体框架
- TakePhoto框架源码流程解析(一)
- Codis源码解析——proxy监听redis请求
- Android源码解析之应用程序框架层和系统运行库层日志系统
- 安全认证框架Shiro(三)- 源码角度解析shiro的权限验证
- [置顶] LeakCanary框架源码解析
- 关于volley框架源码解析
- EventBus框架原理解析(结合源码)(下)
- 一步一步解析集合框架ArrayList源码(2)
- MFC框架程序解析
- JDK源码及其他框架源码解析随笔地址导航
- CI框架源码阅读----程序入口文件
- 用Vue.js开发微信小程序:开源框架mpvue解析
- Java基础知识强化之集合框架笔记11:Collection集合之迭代器的原理及源码解析
- [置顶] 阿里路由框架--ARouter 源码解析之初始化ARouter
- Android Butterknife 框架源码解析(2)——谈谈Java的注解
- Android-AndFix 热修复框架原理及源码解析
- NIO框架之MINA源码解析(二):mina核心引擎
- andorid网络框架retrofit源码解析三