Redis服务器启动之后,会调用initServer进行初始化,并创建一个空的事件循环(EventLoop), 由一个事件循环的结构体保存事件循环的各种数据:
aeEventLoop结构体
1 | /* 事件循环结构体 */ |
创建处理新连接的文件事件
之后initServer会创建接收TCP或者UNIX域套接字的文件事件,在可读时调用acceptTcpHandler:
1 | for (j = 0; j < server.ipfd_count; j++) { |
acceptTcpHandler
acceptTcpHandler就是新的连接到来时的处理函数,下面来看这个函数的代码:
1 | void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) { |
anetTcpAccept用来接收客户端请求,acceptCommonHandler会调用createClient, createClient非常重要,值得看一下代码:
1 | client *createClient(int fd) { |
createClient主要做了两件事,一个是为新的连接创建了redisClient结构体来保存这个连接的信息,另一个是创建了一个文件事件,在文件可读的时候调用处理函数readQueryFromClient,readQueryFromClient顾名思义就是读取客户端发来的命令的,这下我们知道了Redis是如何读取客户端的请求的。
命令的执行
readQueryFromClient读取客户端发来的命令,随后调用processInputBuffer解析命令,processInputBuffer又调用processCommand,processCommand中会根据是单条命令还是多个来选择是直接执行还是放入队列:
1 |
|
如果是多个命令则放入队列,是单个命令则调用call来执行,call是核心的执行命令的函数:
1 | oid call(redisClient *c, int flags) { |
c->cmd->proc(c)就是执行命令的代码。Redis初始化的时候创建了一个包含所有命令及其实现的数组,叫做redisCommand:
1 | struct redisCommand redisCommandTable[] = { |
redisCommand中包含了命令的字符串表示、具体实现函数以及默认参数等,
c->cmd->proc(c)就是通过在这个数组中查找到对应命令,然后执行其实现函数。
结果的返回
前面提到每个连接都创建了一个redisClient结构体对象,这个对象中的两个字段buffer和reply用来保存命令的返回值,buffer是一个固定大小的输出缓冲,reply是一个返回对象组成的链表,执行命令获得的结果会尝试放到这两个字端中,然后再返回给客户端。
在每个命令执行完毕之后都会调用到addReply一类的函数,以addReply为例:
1 | void addReply(client *c, robj *obj) { |
addReply调用prepareClientToWrite, prepareClientToWrite根据client的flag判断是否将client放到server.clients_pending_write链表中。addReply随后尝试将结果放到输出缓冲中,如果超出缓冲的大小或者reply链表中已经有内容了,就将结果放到reply链表中。
随后在下一轮事件循环开始的时候,会执行eventLoop->beforesleep(eventLoop)这个函数,beforesleep会调用handleClientsWithPendingWrites函数:
1 | int handleClientsWithPendingWrites(void) { |
handleClientsWithPendingWrites遍历server.clients_pending_write,将每个client中保存的结果通过调用writeToClient发送给客户端。
1 | int writeToClient(int fd, client *c, int handler_installed) { |
如果writeToClient执行完之后输出缓冲和reply中还有内容,则会注册一个写事件,并关联处理函数sendReplyToClient,在后续的事件循环中会继续调用sendReplyToClient,sendReplyToClient内部调用了writeToClient继续向客户端发送数据。
事件循环
initServer执行完之后,main函数会调用aeMain进入一个包含while的事件循环(eventLoop),这个事件循环可以说是Redis的核心了,循环会一直进行,直到事件循环的stop属性被设置为true时停止。
1 | void aeMain(aeEventLoop *eventLoop) { |
事件处理器
可以看到aeMain中只是做了下判断是不是需要停止,不需要停止的话就一直调用aeProcessEvents来处理事件:
1 | /* 事件处理函数,返回处理的事件的个数 */ |
aeProcessEvents主要的流程包括:
- 计算等待时间,调用IO复用函数aeApiPoll等待相应的时间,来获取这段时间里就绪的文件事件
- 对就绪的文件事件根据是可读还是可写分别调用其处理函数
- 处理时间事件
注意到等待时间其实就是距离下一个时间事件的时间间隔。
参考:
redis设计与实现