首先我们要获取源码,使用 git
拉取 redis
的代码,使用 git checkout
跳到标签为 1.3.6
处,然后就可以愉快的阅读源码了。
推荐 b 站视频 手写redis ,在阅读源码时,从架构层面分析,忽略一些细枝末节的处理。
主要的代码逻辑集中在 redis.c
大约有一万行左右,从 main()
函数开始分析,一些无关紧要的代码就没有显示出来,以后不再说明。
int main(int argc, char **argv) {
time_t start;
initServerConfig(); // 对服务器配置的初始化
initServer(); 初始化服务器,同时注册第一个事件
aeSetBeforeSleepProc(server.el,beforeSleep); // redis 引入vm 带来的
aeMain(server.el); // 主循环
aeDeleteEventLoop(server.el);
return 0;
}
下面我们就进入 initServer()
看看。
static void initServer() {
aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL); // 这是定时的任务,现在不做讨论。
if (aeCreateFileEvent(server.el, server.fd, AE_READABLE,
acceptHandler , NULL) == AE_ERR) oom("creating file event");
// 创造了一个事件,并将 acceptHandler 函数注册为回调函数。
if (server.vm_enabled) vmInit(); // 关于redis 的虚拟内存,在 redis 2.4 后就已经废弃
}
这让我们想要知道 aeCreateFileEvent
和 acceptHandler
发生了什么。
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
aeFileProc *proc, void *clientData)
{
if (fd >= AE_SETSIZE) return AE_ERR;
aeFileEvent *fe = &eventLoop->events[fd];
if (aeApiAddEvent(eventLoop, fd, mask) == -1)
// 将回调函数使用epoll_ctl注册到 epoll对象上。
return AE_ERR;
fe->mask |= mask;
if (mask & AE_READABLE) fe->rfileProc = proc;
if (mask & AE_WRITABLE) fe->wfileProc = proc;
// 将回调事件赋值到处理上,比如上文的 acceptHandler
fe->clientData = clientData;
if (fd > eventLoop->maxfd)
eventLoop->maxfd = fd;
return AE_OK;
}
static void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
cfd = anetAccept(server.neterr, fd, cip, &cport);
// 通过网络包获取客户端 fd
if ((c = createClient(cfd)) == NULL) {
redisLog(REDIS_WARNING,"Error allocating resoures for the client");
close(cfd); /* May be already closed, just ingore errors */
return;
}
// 创建客户端对象
}
可以很清楚的知道 createClient
是重点,是客户端注册事件。
static redisClient *createClient(int fd) {
redisClient *c = zmalloc(sizeof(*c));
if (aeCreateFileEvent(server.el, c->fd, AE_READABLE,
readQueryFromClient, c) == AE_ERR) {
freeClient(c);
return NULL;
}
//在这里可以看到,同样调用aeCreateFileEvent,注册事件。
listAddNodeTail(server.clients,c);
initClientMultiState(c);
return c;
}
好的下面基本事件已经注册完成,我们可以看看调用 aeMain
void aeMain(aeEventLoop *eventLoop) {
// 显而易见的是这是循环
eventLoop->stop = 0;
while (!eventLoop->stop) {
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop); // vm 的遗留
aeProcessEvents(eventLoop, AE_ALL_EVENTS); // 执行事件
}
}
主要看 aeProcessEvents
。
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
numevents = aeApiPoll(eventLoop, tvp);
// I/O多路复用获得,可执行的事件
for (j = 0; j < numevents; j++) {
aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
int mask = eventLoop->fired[j].mask;
int fd = eventLoop->fired[j].fd;
int rfired = 0;
/* note the fe->mask & mask & ... code: maybe an already processed
* event removed an element that fired and we still didn't
* processed, so we check if the event is still valid. */
if (fe->mask & mask & AE_READABLE) {
rfired = 1;
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
// 具体执行回调函数的地方。
}
if (fe->mask & mask & AE_WRITABLE) {
if (!rfired || fe->wfileProc != fe->rfileProc)
fe->wfileProc(eventLoop,fd,fe->clientData,mask);
// 具体执行回调函数的地方。
}
processed++;
}
}
/* Check time events */
if (flags & AE_TIME_EVENTS)
processed += processTimeEvents(eventLoop);
return processed; /* return the number of processed file/time events */
}
最后的绝唱 aeApiPoll
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
aeApiState *state = eventLoop->apidata;
int retval, numevents = 0;
retval = epoll_wait(state->epfd,state->events,AE_SETSIZE,
tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
// 调用 epoll_wait获得准备好的事件,由上文注册的
if (retval > 0) {
int j;
numevents = retval;
for (j = 0; j < numevents; j++) {
int mask = 0;
struct epoll_event *e = state->events+j;
if (e->events & EPOLLIN) mask |= AE_READABLE;
if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
eventLoop->fired[j].fd = e->data.fd;
eventLoop->fired[j].mask = mask;
// 将结果转化为 redis格式的。
}
}
return numevents;
}