Nginx的事件模块

原创:https://blog.csdn.net/ndzjx/article/details/89191193

Nginx是一个事件驱动架构的Web服务器。

事件处理框架所要解决的问题是如何收集、管理、分发事件。(主要是网络事件、定时器事件)

由于网络事件与网卡中断处理程序、内核提供的系统调用密切相关,所以网络事件的驱动既取决于不同的操作系统平台,同一操作系统中也受制于不同内核版本。基本上每个操作系统提供的事件驱动机制(I/O多路复用)都是不同的。

核心模块:ngx_events_module

        1)为所有事件模块解析“events{}”中的配置项

        2)管理这些事件模块存储配置项的结构体

事件模块:ngx_event_core_module,决定使用哪种事件驱动机制,以及如何管理事件。

Nginx定义了一系列运行在不同操作系统、不同内核版本上的事件驱动模块:如ngx_epoll_module、ngx_kqueue_module、ngx_poll_module、ngx_select_module、ngx_eventport_module等

ngx_module_t表示Nginx模块的基本接口。

一个结构体来描述一类模块的通用接口,这个接口保存在ngx_module_t结构体的ctx成员中。例如:核心模块的通用接口是ngx_core_module_t结构体,而事件模块的通用接口是ngx_event_module_t

事件定义结构体:ngx_event_s

事件不需要创建,因为Nginx在启动时已经在ngx_cycle_t的read_events/write_events中预分配了所有的读/写事件。

怎么把事件添加到epoll等事件驱动模块中呢?ngx_handle_read_event/ngx_handle_write_event内部调用了ngx_event_actions_t结构体的add/del方法,做了许多通用性的工作。

连接结构体:ngx_connection_t,表示被动连接:客户端主动发起,Nginx被动接受的TCP连接。

ngx_peer_connection_t表示主动连接,Nginx发起,主动向其他上游服务器建立的连接。

两种连接都不可以随意创建,必须从连接池中获取。

ngx_connection_t连接池示意图:

ngx_events_module模块是一个核心模块,定义了一类新模块:事件模块。功能:

    1)定义新的事件类型

    2)定义事件模块都需要实现的ngx_event_module_t接口

    3)管理事件模块生成的配置项结构体

    4)解析事件类配置项

ngx_command_t数组,决定模块如何处理自己感兴趣的配置项。

模块定义大体如下:

struct ngx_command_t ngx_events_command[] = {…}

static ngx_core_module_t ngx_events_module_ctx = {….}//核心模块的公共接口,事件模块的接口又是别的了ngx_event_module_t

ngx_module_t ngx_events_module = {….} // 定义核心模块

模块配置项结构体的指针如何管理?以事件模块为例:

ngx_cycle_t结构体中的conf_ctx定义:

void ****conf_ctx;

每一个事件模块如何获取它在create_conf中分配的结构体指针呢?通过宏ngx_event_get_conf:

#define ngx_event_get_conf(conf_ctx, module) \

(*(ngx_get_conf(conf_ctx, ngx_events_module))) [module.ctx_index]

#define ngx_get_conf(conf_ctx, module) conf_ctx[module.index]

Nginx各模块在ngx_modules数组中的顺序很重要(index)。Nginx又允许定义子类型,如事件类型、HTTP类型,mail类型,统一类型的模块间依靠ctx_index区分顺序。index是区分所有模块的。决定以后加载个模块的顺序。

对于事件驱动机制,更多的工作在ngx_event_core_module事件模块中进行。

    1)创建连接池

    2)决定使用那种事件机制

    3)初始化将要使用的事件模块

static ngx_command_t ngx_event_core_commands[] = {…}

ngx_event_module_t ngx_event_core_module_ctx = {…}

ngx_module_t ngx_event_core_module = {….},其中有ngx_event_module_init/ngx_event_process_init回调函数。

用于存储配置项参数的结构体:ngx_event_conf_t,里面有muti_accept/accept_mutex与负载均衡相关。

Nginx启动时,还没有fork出worker子进程时,会调用ngx_event_module_init(初始化一些变量,利用共享内存的统计变量)。在fork出worker子进程后,每一个worker进程会在ngx_event_core_module模块的ngx_event_process_init方法后进入正式的工作循环。

epoll事件驱动模块:

如果一个进程100万个TCP连接,只有几百个时活跃的。

进程在每次询问操作系统收集有事件发生的TCP连接时:

    1)Linux2.4内核以前的select/poll事件驱动方式:把这100万个连接告诉操作系统(用户态内存到内核态内存的大量复制),由操作系统找出其中有事件发生的几百个连接。他们只能处理几千个并发连接。

    2)epoll:在Linux内核中申请了一个简易的文件系统,epoll_create创建epoll对象,在文件系统中给这个句柄分配资源,epoll_ctl向epoll对象中加这100万个连接的套接字,epoll收集发生事件的连接。因为在epoll_wait时没有向它传递这100万个连接(中途需要时逐个加入的),内核也不需要遍历全部的连接。

所有添加到epoll中的事件都会与设备(如网卡)驱动程序建立回调关系(ep_poll_callback),它会把这样的事件放到rdllist双向链表中。

epoll_wait时只是检查rdllist双向链表是否由epitem元素而已。

每个epoll对象都有一个独立的eventpoll结构体

epoll有两种工作模式:

    1)水平触发LT:可以处理阻塞/非阻塞套接字。

    2)边缘触发ET:Nginx默认。效率高,只支持非阻塞套接字。

区别在于:新事件到来时,ET模式可以epoll_wait获取到这个事件,可以如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字没有新的事件再次到来时,在ET模式下时无法再次从epll_wait调用中获取到这个事件的;

而LT模式则相反,只要一个事件对应的套接字缓冲区还有数据,就总能从epoll_wait中获取这个事件。

ngx_epoll_create做了两件事:

    1)创建epoll对象。

    2)创建event_list数组,用于进行epoll_wait时传递(接收)内核态的事件

其中ngx_epoll_module_ctx中ngx_event_actions_t中的process_events由ngx_epoll_process_events实现(收集、分发事件的功能),里面回调用epoll_wait,还可能ngx_time_update,或向post队列添加事件。

因为地址的最后一位肯定是0,用ngx_connection_t的地址最后一个记录了instance标志,表示连接是否过期,ngx_get_connection从连接池中获取的新连接,instance标志位会置反(标为过期),防止废弃的连接被重用。解决了服务器开发一定会出现的过期事件问题。

定时器事件:

网络事件的触发由内核完成。定时器事件则完全时由Nginx自身实现的,与内核完全无关。

出于性能考虑,Nginx使用的时间是缓存在内存中的。对于worker而言,除了Nginx启动时更新一次,任何更新时间的操作都只能由ngx_epoll_process_events方法执行。

ngx_event_timer_rbtree定时器事件组成的红黑树。根据超时时间的大小组成了二叉排序树。ngx_event_find_timer函数找出最左边的节点(最早可能超时的事件),

ngx_event_expire_timers可以触发所有超时事件。

Nginx设计了两个post队列,(post表示允许事件延后执行)

    1)ngx_posted_accept_events队列,由被触发的监听连接的读事件构成,此队列优先执行

    2)ngx_posted_events队列,由普通的读写事件构成

惊群:配合accept_mutex,避免了“惊群”现象(多个worker监听同一端口),同一时刻,只能有唯一一个worker子进程监听web端口。

负载均衡:配合accept_mutex,使用ngx_accept_disabled为正数时,当前进程不再处理新连接事件,仅仅将值减1。初始值为复数,值为总连接数的7/8,只有ngx_accept_disabled预示,连接数降到总连接数的7/8以下时,才会调用ngx_trylock_accept_mutex试图去处理新连接事件。

ngx_process_events_and_timers就是Nginx处理Web服务的方法,所有业务的执行都是由它开始的。设计Nginx完整的事件驱动机制。

ngx_process_events_and_timers核心操作由3个:

    1)调用使用的事件驱动模块的process_events,处理网络事件(ngx_epoll_process_event→epoll_wait)

    2)处理两个post事件队列中的事件。

    3)处理定时器事件。

猜你喜欢

转载自blog.csdn.net/ndzjx/article/details/89191193