深度解析单线程的 Redis 如何做到每秒数万 QPS 的超高处理能力( 三 )

函数 aeCreateFileEvent 一开始 , 从 eventLoop->events 获取了一个 aeFileEvent 对象 。在 2.1 中我们介绍过 eventLoop->events 数组 , 注册的各种事件处理器会保存在这个地方 。
接下来调用 aeApiAddEvent 。这个函数其实就是对 epoll_ctl 的一个封装 。主要就是实际执行 epoll_ctl EPOLL_CTL_ADD 。
//file:src/ae_epoll.cstatic int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {// add or modint op = eventLoop->events[fd].mask == AE_NONE ?EPOLL_CTL_ADD : EPOLL_CTL_MOD;......// epoll_ctl 添加事件epoll_ctl(state->epfd,op,fd,&ee);return 0;}每一个 eventLoop->events 元素都指向一个 aeFileEvent 对象 。在这个对象上 , 设置了三个关键东西

  • rfileProc:读事件回调
  • wfileProc:写事件回调
  • clientData:一些额外的扩展数据
将来 当 epoll_wait 发现某个 fd 上有事件发生的时候 , 这样 redis 首先根据 fd 到 eventLoop->events 中查找 aeFileEvent 对象 , 然后再看 rfileProc、wfileProc 就可以找到读、写回调处理函数 。
回头看 initServer 调用 aeCreateFileEvent 时传参来看 。
//file: src/server.cvoid initServer() {......// 2.1.3 注册 accept 事件处理器for (j = 0; j < server.ipfd_count; j++) {aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,acceptTcpHandler,NULL);}}listen fd 对应的读回调函数 rfileProc 事实上就被设置成了 acceptTcpHandler , 写回调没有设置 , 私有数据 client_data 也为 null 。
三、Redis 事件处理循环在上一节介绍完了 Redis 的启动初始化过程 , 创建了 epoll , 也进行了绑定监听 , 也注册了 accept 事件处理函数为 acceptTcpHandler 。
//file: src/server.cint main(int argc, char **argv) {......// 启动初始化initServer();// 运行事件处理循环 , 一直到服务器关闭为止aeMain(server.el);}接下来 , Redis 就会进入 aeMain 开始进行真正的用户请求处理了 。在 aeMain 函数中 , 是一个无休止的循环 。在每一次的循环中 , 要做如下几件事情 。
深度解析单线程的 Redis 如何做到每秒数万 QPS 的超高处理能力

文章插图
 
  • 通过 epoll_wait 发现 listen socket 以及其它连接上的可读、可写事件
  • 若发现 listen socket 上有新连接到达 , 则接收新连接 , 并追加到 epoll 中进行管理
  • 若发现其它 socket 上有命令请求到达 , 则读取和处理命令 , 把命令结果写到缓存中 , 加入写任务队列
  • 每一次进入 epoll_wait 前都调用 beforesleep 来将写任务队列中的数据实际进行发送
  • 如若有首次未发送完毕的 , 当写事件发生时继续发送
//file:src/ae.cvoid aeMain(aeEventLoop *eventLoop) {eventLoop->stop = 0;while (!eventLoop->stop) {// 如果有需要在事件处理前执行的函数 , 那么运行它// 3.4 beforesleep 处理写任务队列并实际发送之if (eventLoop->beforesleep != NULL)eventLoop->beforesleep(eventLoop);// 开始等待事件并处理// 3.1 epoll_wait 发现事件// 3.2 处理新连接请求// 3.3 处理客户连接上的可读事件aeProcessEvents(eventLoop, AE_ALL_EVENTS);}}以上就是 aeMain 函数的核心逻辑所在 , 接下来我们分别对如上提到的四件事情进行详细的阐述 。
3.1 epoll_wait 发现事件Redis 不管有多少个用户连接 , 都是通过 epoll_wait 来统一发现和管理其上的可读(包括 liisten socket 上的 accept事件)、可写事件的 。甚至连 timer , 也都是交给 epoll_wait 来统一管理的 。
深度解析单线程的 Redis 如何做到每秒数万 QPS 的超高处理能力

文章插图
 
每当 epoll_wait 发现特定的事件发生的时候 , 就会调用相应的事先注册好的事件处理函数进行处理 。我们来详细看 aeProcessEvents 对 epoll_wait 的封装 。
//file:src/ae.cint aeProcessEvents(aeEventLoop *eventLoop, int flags){// 获取最近的时间事件tvp = xxx// 处理文件事件 , 阻塞时间由 tvp 决定numevents = aeApiPoll(eventLoop, tvp);for (j = 0; j < numevents; j++) {// 从已就绪数组中获取事件aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];//如果是读事件 , 并且有读回调函数fe->rfileProc()//如果是写事件 , 并且有写回调函数fe->wfileProc()}}//file: src/ae_epoll.cstatic int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {// 等待事件aeApiState *state = eventLoop->apidata;epoll_wait(state->epfd,state->events,eventLoop->setsize,tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);...}


推荐阅读