Part 1: Basic Structure
aeTimeEvent
双链表结构
aeFileEvent & aeFiredEvent
aeApiState
aeEventLoop
Part 2: eventLoop Arch
aeMain
main data flow:
当 Redis Sever 启动之后,监听 socket ,开始 accept 客户端的连接
客户端连接上来之后,注册 client fd 到 ae 中,并注册读事件 callback
Ae 触发读事件之后,读取客户端数据
解析数据,获取 Redis 的不同命令
调用不同命令注册的 callback 函数,进行不同的处理
Note:
1. Socket fd均为nonblock模式,采用epoll进行LT模式触发,epoll_wait 返回
2. 客户端reply data,在read data处理之后,进行reply data的缓存
3. Reply data在beforeSleep中进行data send,继续进入 epoll_wait 的阻塞等待
acceptTCPHandler
handleMessage
Linux platform 上,Redis 采用 non-block socket + epoll LT + 异步回射 + 个别场景下 引入 poll with timeout的阻塞同步 的模型。另外,Redis中对epoll 触发出来的状态进行了记录以及转移。全部存储在Redis自身的 aeEventLoop结构中。通过 aeCreateTimeEvent 以及 aeCreateFileEvent 进行两种事件,timer事件以及 File事件的注册。
1. timer: Redis采用 epoll_wait 超时机制,实现了定时的返回触发。
示例如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
void cron_task(int cnthz)
{
if (cnthz % 3 == 0)
{
printf("handle task every 3 hz\n");
}
if (cnthz % 5 == 0)
{
printf("handle task every 5 hz\n");
}
if (cnthz % 8 == 0)
{
printf("handle task every 8 hz\n");
}
}
int main()
{
int hz = 500; // every 500 millisec, 0.5 sec.
int epfd = epoll_create(1024);
struct epoll_event events[10];
int cnthz = 0;
while (1)
{
epoll_wait(epfd, events, 10, hz);
cron_task(cnthz);
cnthz++;
// for auto exit.
if (cnthz == 20)
{
break;
}
}
close(epfd);
return 0;
}
这是个自实现的简单时钟。每隔500 毫秒震荡一次。Redis中称之为 hz,表示最小的时钟颗粒度。以上的代码用的是最简单的方式,就是记录震荡次数,然后,根据次数,去取模计时。这种方式,跟splee 500 毫秒,看似效果一致。但是,epoll_wait是可以同时监听fd事件的。就不会timeout返回。Redis中是通过get system time相关的接口去计时比较。对比两个时间戳直接的距离,计算时间流逝。
需要注意的是,redis的timer task被记录在EventLoop结构中,内中实现是一个双链表。每次都是需要去检索都是O(n)的复杂度。每次调用cron task的时候,都要去遍历,比较,更新时间戳等操作。所以,timer任务是不会太多的。
2. file event.
由于进程中能够打开的fd 是有限的。比如默认大多数都是1024。服务端程序可能改成 65535,或者更大。可采用ulimit -n 命令进行查看。0, 1, 2 作为系统的默认设备。redis日志回占用1个。然后 redis至少会启用一个fd, socket去accept client connection 然后 pipe 等等。所以Redis内部会保持一些自用,CONFIG_MIN_RESERVED_FDS 宏为 32 个,为自留用fd数量,基于安全层面,扩展层面的考虑,在此基础上,又增加了 96.保证保持在128以内。接下来的fd主要是就给 accpet connected 上来的client socket fd使用。就是配置中的 maxclients。所以 file 的 setsize 就是 maxclients+ RESERVERED_FDS (128).这就是EventLoop中的 file 以及 fired events 数组的 大小。(这里的数组表示一段连续可随机访问内存的说法,Redis中的comment一般称之为向量。)这种数组,以fd为索引。在初始时,这个状态flag 为AE_NONE。注册了事件了之后 AE_READABLE, AE_WRITABLE 等等。Redis 将事件注册状态记录在 file events中,同时向epoll注册,而触发状态记录在 fired 数组中。两者通过fd作为索引进行关联。关联之后,比较事件flag 去调用对应的 callback。