详细的过程
了解了 redis 的事件驱动模型后,带着命令是如何被处理的这个问题去读代码。刚开始的时候,会有一堆的变量和函数等着读者,但只要抓住主干就好了,下面就是 redis 的主干部分。
int main(int argc, char **argv) {
......
// 初始化服务器配置,主要是填充 redisServer 结构体中的各种参数
initServerConfig();
......
// 初始化服务器
initServer();
......
// 进入事件循环
aeMain(server.el);
}
分别来看看它们主要做了什么?
initServerConfig()
initServerConfig() 主要是填充 struct redisServer 这个结构体,redis 所有相关的配置都在里面。
initServer()
void initServer() {
// 创建事件循环结构体
server.el = aeCreateEventLoop(server.maxclients+REDIS_EVENTLOOP_FDSET_INCR);
// 分配数据集空间
server.db = zmalloc(sizeof(redisDb)*server.dbnum);
/* Open the TCP listening socket for the user commands. */
// listenToPort() 中有调用 listen()
if (server.port != 0 &&
listenToPort(server.port,server.ipfd,&server.ipfd_count) == REDIS_ERR)
exit(1);
......
// 初始化 redis 数据集
/* Create the Redis databases, and initialize other internal state. */
for (j = 0; j < server.REDIS_DEFAULT_DBNUM; j++) { // 初始化多个数据库
// 哈希表,用于存储键值对
server.db[j].dict = dictCreate(&dbDictType,NULL);
// 哈希表,用于存储每个键的过期时间
server.db[j].expires = dictCreate(&keyptrDictType,NULL);
server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
server.db[j].ready_keys = dictCreate(&setDictType,NULL);
server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);
server.db[j].id = j;
server.db[j].avg_ttl = 0;
}
......
// 创建接收 TCP 或者 UNIX 域套接字的事件处理
// TCP
/* Create an event handler for accepting new connections in TCP and Unix
* domain sockets. */
for (j = 0; j < server.ipfd_count; j++) {
// acceptTcpHandler() tcp 连接接受处理函数
if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
acceptTcpHandler,NULL) == AE_ERR)
{
redisPanic(
"Unrecoverable error creating server.ipfd file event.");
}
}
......
}
在这里,创建了事件中心,是 redis 的网络模块,如果你有学过 linux 下的网络编程,那么知道这里一定和 select/epoll/kqueue 相关。
接着,是初始化数据中心,我们平时使用 redis 设置的键值对,就是存储在里面。这里不急着深入它是怎么做到存储我们的键值对的,接着往下看好了,因为我们主要是想把大致的脉络弄清楚。
接着,是初始化数据中心,我们平时使用 redis 设置的键值对,就是存储在里面。这里不急着深入它是怎么做到存储我们的键值对的,接着往下看好了,因为我们主要是想把大致的脉络弄清楚。
在最后一段的代码中,redis 给 listen fd 注册了回调函数 acceptTcpHandler,也就是说当新的客户端连接的时候,这个函数会被调用,详情接下来再展开 。
aeMain()
接着就开始等待请求的到来。
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
// 进入事件循环可能会进入睡眠状态。在睡眠之前,执行预设置
// 的函数 aeSetBeforeSleepProc()。
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
// AE_ALL_EVENTS 表示处理所有的事件
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
}
前面的两个函数都属于是初始化的工作,到这里的时候,redis 正式进入等待接收请求的状态。具体的实现,和 select/epoll/kqueue 这些 IO 多路复用的系统调用相关,而这也是网络编程的基础部分了。继续跟踪调用链:
可以看到,aeApiPoll 即是 IO 多路复用调用的地方,当有请求到来的时候,进程会觉醒以处理到来的请求。如果你有留意到上面的定时事件处理,也就明白相应的定时处理函数是在哪里触发的了。