Nginx进程模型分析
架构驿站,让更多人可以因为学习和分享而受益。
大师兄
2012年入行,毕业于北京交通大学计算机科学与技术专业,曾服役于广联达、三快在线(美团)、拉勾网,技术专家。
课程大纲:
课程内容节选:
事件驱动模型
通常,事件驱动框架主要由3部分组成:事件收集器(收集事件)、事件发生器/分发器/分发者(分发事件)、事件处理器/消费者。
通常,我们在编写服务器处理模型的程序时,基于事件驱动模型,“目标对象”中的“事件处理器”可以有以下几种实现办法:
“事件发送器”每传递过来一个请求,“目标对象”就创建一个新的进程,调用“事件处理器”来处理该请求。
“事件发送器”每传递过来一个请求,“目标对象”就创建一个新的线程,调用“事件处理器”来处理该请求。
“事件发送器”每传递过来一个请求,“目标对象”就将其放入一个待处理事件的列表,使用非阻塞I/O方式调用“事件处理器”来处理该请求(Nginx)。
上面的三种处理方式,各有特点:
第一种,创建进程开销比较大,导致服务器性能比较差,编码实现较为简单。
第二种,涉及到多线程开发、编码比较复杂,涉及到线程同步、有可能面临死锁等一系列的问题。
第三种,编码逻辑比前两种都要复杂,大多数网络服务器都采用第三种方式,逐渐形成了所谓的“事件驱动处理库”。
不同操作系统提供了不同事件驱动模型,例如Linux 2.6系统同时支持epoll、poll、select模型,FreeBSD系统支持kqueue模型,Solaris 10系统支持eventport模型。为了保持其跨平台特性,Nginx的事件驱动框架完美地支持各类操作系统的事件驱动模型。
针对每一种模型,Nginx设计了一个Event模块,包括ngx_epoll_module、ngx_poll_module、ngx_select_module、ngx_kqueue_module等事件驱动模块。
事件驱动框架会在模块初始化时(Nginx启动时)选取其中一个作为Nginx进程的事件驱动模块。对于大多数生产环境中Liunx系统的Web服务器,Nginx默认选取最强大的事件驱动模型epoll。
(1)epoll原理详解
当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关,如下所示:
struct eventpoll {
...
/*红黑树的根节点,这棵树中存储着所有添加到epoll中的事件,也就是这个epoll监控的事件*/
struct rb_root rbr;
/*双向链表rdllist保存着将要通过epoll_wait返回给用户的、满足条件的事件,需要处理的,有意义的*/
struct list_head rdllist;
...
};
我们在调用epoll_create时,内核除了帮我们在epoll文件系统里建了个file结点,在内核cache里建了个红黑树用于存储以后epoll_ctl传来的socket外,还会再建立一个rdllist双向链表,用于存储准备就绪的事件。当epoll_wait调用时,仅仅观察这个rdllist双向链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。所以,epoll_wait非常高效。
所有添加到epoll中的事件都会与硬件设备(如网卡)驱动程序建立回调关系,也就是说相应事件的发生时会调用这里的回调方法,它会把这样的事件放到上面的rdllist双向链表中。
一般来说,在程序的整个运行周期中只有一个epoll句柄。例如,nginx的每个工作进程只维护一个epoll句柄。句柄创建后,我们的程序监听的每个端口本质上都是文件描述符,可以在此上发生Accept事件,即接收客户端请求。因此,最初我们将添加与要通过epoll的ctl()方法监听的端口相对应的文件描述符。成功添加后,每个监听文件描述符对应于eventpoll红黑树中的节点。
我们在这里可以看到,在使用epoll的过程中,将有两种类型的文件描述符:
一种是与我们正在收听的端口相对应的文件描述符,我们通常监听其Accept事件以等待客户端连接
一种是与每个客户端连接对应的文件描述符,它监听其读写事件以接收数据并将其发送到客户端
在epoll中对于每一个事件都会建立一个epitem结构体,如下所示:
struct epitem {
...
//红黑树节点
struct rb_node rbn;
//双向链表节点
struct list_head rdllink;
//事件句柄等信息
struct epoll_filefd ffd;
//指向其所属的eventepoll对象
struct eventpoll *ep;
//期待的事件类型
struct epoll_event event;
...
}; // 这里包含每一个事件对应着的信息。
当调用epoll_wait检查是否有发生事件的连接时,只是检查eventpoll对象中的rdllist双向链表是否有epitem元素而已,如果rdllist链表不为空,则这里的事件复制到用户态内存(使用共享内存提高效率)中,同时将事件数量返回给用户。
epoll_ctl在向epoll对象中添加、修改、删除事件时,从rbr红黑树中查找事件也非常快,也就是说epoll是非常高效的,它可以轻易地处理百万级别的并发连接。
执行epoll_create()时,创建了红黑树和就绪链表
执行epoll_ctl()时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数
执行epoll_wait()时立刻返回准备就绪链表里的数据即可
公开笔记对他人可见,有机会被管理员评为“优质笔记”
{{ noteEditor.content.length }}/2000
讲师收到你的提问会尽快为你解答。若选择公开提问,可以获得更多学员的帮助。
课程大纲