在上一篇redis启动流程(一)介绍了redis启动过程中几个主要的函数,预留了两个函数initServer()aeMain() 还没有分析,因为这两个函数做的事情比较多,要讲的篇幅也比较大所以就新开一篇专门来讲。

以下是initServer()aeMain() 函数的执行流程图,带颜色的函数是比较重要的函数。

    

initServer()

1.createSharedObjects()

这个函数主要是创建一些共享的全局对象,我们平时在跟redis服务交互的时候,如果有遇到错误,会收到一些固定的错误信息或者字符串比如:-ERR syntax error,-ERR no such key。这些字符串对象都是在这个函数里面进行初始化的。

2.adjustOpenFilesLimit()

该函数主要检查下系统的可允许打开文件句柄数,对于redis来说至少要32个文件句柄,如果检测到环境不合适,会去修改环境变量,以适合redis的运行。

3.aeCreateEventLoop()

这个函数很重要,redis的事件对象就是在这个函数里面创建的,包括一些高并发异步机制对象也是在这里面初始化的,对于异步机制对象的选择可以看到redis是这样一个顺序evport->epoll->kqueue->select,这里我们只分析epoll的这个样例其它样例八九不离十。

4.listenToPort()

从函数名可以很清楚知道这个函数就是为redis启动监听TCP端口。

5.anetUnixServer()

这个函数则是启动uinx socket的监听。

6.aeCreateTimeEvent()

这个函数主要作为定时任务的注册,在这里redis注册了serverCron() 的定时任务,时间间隔是1毫秒,这个是首次,再执行一次之后就会对时间间隔进行重新设定。

7.aeCreateFileEvent()

这个函数主要作为事件任务的注册,这里首先对刚才监听的socket句柄的事件和unix socket句柄的事件加入到事件链表当中,而且是只读事件。

aeMain()

1.aeProcessEvents()

这个函数让redis进入了事件循环监听,定时任务事件和读写事件都监听,从代码上可以看出如果有读写事件则优先执行,然后才是执行定时任务事件。

2.acceptTcpHandler()

当有新的连接接入的时候,该函数就会被调用,在比较新的版本里面作者为了提高效率,在该函数中最多可以处理MAX_ACCEPTS_PER_CALL=1000次的连接接入。以前的版本是调用一次只处理一个新连接。处理完一个连接后,该函数会把readQueryFromClient这个函数和连接的socket关联上然后注册到事件队列里面。

3.acceptUnixHandler()

该函数执行的逻辑和acceptTcpHandler() 函数差不多,唯一的区别就是从unix socket 来接收新连接,其它逻辑都一样。

4.serverCron()

该函数是redis的定时任务,也是唯一的定时任务,第一次执行的时间间隔是1毫秒,我看了下代码后面按默认的配置是100毫秒执行一次,可是网上有文章说是10毫秒一次,这个就比较有意思了。这个定时任务执行的事情非常多,大致罗列下:

  • 更新了下cache的时间

  • 更新LRU锁的值

  • 记录内存使用最高值

  • 打印一些数据库的信息

  • 打印连接的客户端信息

  • 对客户端一些状态进行检测

  • 对数据库一些状态进行检测

  • 维护RDB文件和AOF文件

  • 按一定条件删除一些客户端

  • 主备数据同步

5.readQueryFromClient()

该函数就是处理从客户端那边读到的请求,根据相应的请求做出相应处理,需要响应的话,填充好发送缓冲区,然后把sendReplyToClient() 加入到事件链表中。

6.sendReplyToClient()

该函数把发送缓冲区的数据发送到客户端去,如果没有任何可发送的内容,就把自己从事件链表中删除。

总结

上面的函数调用流程图和函数功能解释,基本把redis进行网络服务前的准备工作和服务流程介绍了个大概,让我比较震撼的是redis是靠单线程在进行网络服务,依靠高效的异步机制来提供高性能的服务,在它的任务里面既要处理读写事件,又要执行定时任务,任何一环节的效率低下都会影响到整个服务的质量,所以每个环节都要尽量做到高效。对作者很是钦佩,redis源码也是一个很好的学习样例。