我们谈起redis线程模型或者执行流程经常在网上查到事件处理器,事件分派器等等等事件XXXX,如下图(看不懂直接跳到过看下文解说):
实际上,这些糊弄玄虚的词汇反而加大我们学习成本。不如我们浅读源码了解这个单线程模型和请求执行过程。
redis启动初始化:
从main入手,initServer()函数见注释。
int main(int argc, char **argv) {
...
initServer(); //建立TCP连接,socket,bind,listen标准3步,返回文件描述符fd
...
aeCreateFileEvent(fd, acceptHandler, ...);
...
aeMain();
...
}
第二部,asCreateFileEvent()函数将刚刚TCP返回的fd插入到一个叫asFileEvent的链表中,同时我们将这个fd(也就是第一个fd)绑定acceptHandler()函数用于监听到后面用户连接请求就调用这个函数。注意:我们后续插入到asFileEvent链表并且绑定相应的函数都是通过这个asCreateFileEvent()。至此我们形成下面这个格局:
之后asmian()函数一个死循环用于监听请求连接。
void aeMain(aeEventLoop *eventLoop)
{
eventLoop->stop = 0;
while (!eventLoop->stop)
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
小总结,这里我们完成了初始化,并且,第一个fd是我们redis唯一一个监听客户请求连接的fd。注意:fd就是我们IO多路复用的入参。所以,我们说redis是IO多路复用模式的。不了解IO多路复用可以看看我这篇文章IO多路复用(阻塞IO,非阻塞IO,select,poll,epoll)_量子学习法的博客-CSDN博客
当客户建立连接
好了,当第一个fd监听到客户端请求连接时会调用刚才绑定的acceptHandler(),这个函数执行后,我们会创建新的fd放在刚刚创建的asFileEvent链表下并绑定一个用户处理请求的函数readQueryFromClient()。结果如下:
所以同样的道理,后面用户有请求就调用 readQueryFromClient()这个函数。
readQueryFromClient()这个函数会去一张表中寻找命令所对应的函数,这部分用的编码技巧叫命令模式。
static struct redisCommand cmdTable[] = {
{"get",getCommand,2,REDIS_CMD_INLINE},
{"set",setCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},
{"setnx",setnxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},
{"del",delCommand,-2,REDIS_CMD_INLINE},
{"exists",existsCommand,2,REDIS_CMD_INLINE},
...
}
小总结: 至此redi启动到监听的轮廓我们已经形成,由于只有一个线程在监听这些描述符,并做处理。所以即使客户端并发地发送命令,后面仍然是依次取出命令,顺序执行。所以我们说redis是单线程模型的(包括redis 6.0)
好了,我们已经和客户端建立连接
当客户发送请求,我们就会从readQueryFromClient()表中找到相应的函数。这个函数会应用很多redis底层数据结构。
处理完成后,我们将响应发送回客户端。但是不是简单粗暴直接发送也不是开启一个新线程
仍然是调用aeCreateFileEvent将sendReplyToClient 函数挂在需要响应的客户端连接的文件描述符上。
static void addReply(redisClient *c, robj *obj) {
...
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
sendReplyToClient, c, NULL) == AE_ERR);
}
那什么时候发送给客户端?这里我们需要分情况讨论:
* 如果是读事件,就调用读事件的acceptxxxx方法,读事件处理完成后不直接响应,而是放到下一次select的写事件中响应
* 如果是写事件,就调用写事件的的acceptxxxx方法,返回响应
也就是说 只有写事件才会相应。
总结(我线下跟人聊到这一部分通常挺激动)
以上事情,我们解释了redis启动流程和单线程模型。下面,我们根据以上函数解释所谓的XXX事件。总体上,上面的事情可以有下面这张图概括:
1,上文中asCreateFileEvent()用于插入到asFileEvent链表并且绑定相应的函数(acceptHandle(), readQueryFromClient())就是 事件处理器
2,上文监听事件的函数acceptHandle()就是连接应答处理器
3,处理请求函数readQueryFromClient()(刚才提到应用命令模式编码的函数)就是命令请求处理器
4,听客户端响应(写事件)的文件描述符绑定的函数 sendReplyToClient()就是命令回复处理器。
5,这种一个负责响应 IO 事件,一个负责交给相应的事件处理器去处理,就叫做 Reactor 模式。
参考文献: