Nginx教程
01、关于 Nginx
02、什么是 Nginx
03、Nginx 的特点
04、初探 Nginx 架构
05、Nginx 基础概念
06、Nginx 基本数据结构
07、Nginx 的配置系统
08、Nginx 的模块化体系结构
09、Nginx 的请求处理
10、Nginx handler 模块简介
11、Nginx 模块的基本结构
12、Nginx handler 模块的基本结构
13、Nginx handler 模块的挂载
14、Nginx handler 的编写步骤
15、Nginx 示例_ hello handler 模块
16、Nginx handler 模块的编译和使用
17、Nginx 更多 handler 模块示例分析
18、Nginx 过滤模块简介
19、Nginx 过滤模块的分析
20、Nginx upstream 模块简介
21、Nginx 负载均衡模块
22、Nginx core 模块
23、Nginx event 模块
24、Nginx 配置文件nginx.conf中文详解
本文档使用 MrDoc 发布
-
+
首页
23、Nginx event 模块
### **event 模块** ------------ ### **event 的类型和功能** ------------ Nginx 是以 event(事件)处理模型为基础的模块。它为了支持跨平台,抽象出了 event 模块。它支持的 event 处理类型有:AIO(异步IO),/dev/poll(Solaris 和 Unix 特有),epoll(Linux 特有),eventport(Solaris 10 特有),kqueue(BSD 特有),poll,rtsig(实时信号),select 等。 event 模块的主要功能就是,监听 accept 后建立的连接,对读写事件进行添加删除。事件处理模型和 Nginx 的非阻塞 IO 模型结合在一起使用。当 IO 可读可写的时候,相应的读写事件就会被唤醒,此时就会去处理事件的回调函数。 特别对于 Linux,Nginx 大部分 event 采用 epoll EPOLLET(边沿触发)的方法来触发事件,只有 listen 端口的读事件是 EPOLLLT(水平触发)。对于边沿触发,如果出现了可读事件,必须及时处理,否则可能会出现读事件不再触发,连接饿死的情况。 ```python typedef struct { /* 添加删除事件 */ ngx_int_t (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags); ngx_int_t (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags); ngx_int_t (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags); ngx_int_t (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags); /* 添加删除连接,会同时监听读写事件 */ ngx_int_t (*add_conn)(ngx_connection_t *c); ngx_int_t (*del_conn)(ngx_connection_t *c, ngx_uint_t flags); ngx_int_t (*process_changes)(ngx_cycle_t *cycle, ngx_uint_t nowait); /* 处理事件的函数 */ ngx_int_t (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags); ngx_int_t (*init)(ngx_cycle_t *cycle, ngx_msec_t timer); void (*done)(ngx_cycle_t *cycle); } ngx_event_actions_t; ``` 上述是event 处理抽象出来的关键结构体,可以看到,每个 event 处理模型,都需要实现部分功能。最关键的是 add 和 del 功能,就是最基本的添加和删除事件的函数。 ### **accept 锁** Nginx 是多进程程序,80 端口是各进程所共享的,多进程同时 listen 80 端口,势必会产生竞争,也产生了所谓的“惊群”效应。当内核 accept 一个连接时,会唤醒所有等待中的进程,但实际上只有一个进程能获取连接,其他的进程都是被无效唤醒的。所以 Nginx 采用了自有的一套 accept 加锁机制,避免多个进程同时调用 accept。Nginx 多进程的锁在底层默认是通过 CPU 自旋锁来实现。如果操作系统不支持自旋锁,就采用文件锁。 Nginx 事件处理的入口函数是 ngx_process_events_and_timers(),下面是部分代码,可以看到其加锁的过程: ```python if (ngx_use_accept_mutex) { if (ngx_accept_disabled > 0) { ngx_accept_disabled--; } else { if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) { return; } if (ngx_accept_mutex_held) { flags |= NGX_POST_EVENTS; } else { if (timer == NGX_TIMER_INFINITE || timer > ngx_accept_mutex_delay) { timer = ngx_accept_mutex_delay; } } } ``` 在ngx_trylock_accept_mutex()函数里面,如果拿到了锁,Nginx 会把 listen 的端口读事件加入 event 处理,该进程在有新连接进来时就可以进行 accept 了。注意 accept 操作是一个普通的读事件。下面的代码说明了这点: ```python (void) ngx_process_events(cycle, timer, flags); if (ngx_posted_accept_events) { ngx_event_process_posted(cycle, &ngx_posted_accept_events); if (ngx_accept_mutex_held) { ngx_shmtx_unlock(&ngx_accept_mutex); ``` ngx_process_events()函数是所有事件处理的入口,它会遍历所有的事件。抢到了 accept 锁的进程跟一般进程稍微不同的是,它被加上了 NGX_POST_EVENTS 标志,也就是说在 ngx_process_events() 函数里面只接受而不处理事件,并加入 post_events 的队列里面。直到 ngx_accept_mutex 锁去掉以后才去处理具体的事件。为什么这样?因为 ngx_accept_mutex 是全局锁,这样做可以尽量减少该进程抢到锁以后,从 accept 开始到结束的时间,以便其他进程继续接收新的连接,提高吞吐量。 ngx_posted_accept_events 和 ngx_posted_events 就分别是 accept 延迟事件队列和普通延迟事件队列。可以看到 ngx_posted_accept_events 还是放到 ngx_accept_mutex 锁里面处理的。该队列里面处理的都是 accept 事件,它会一口气把内核 backlog 里等待的连接都 accept 进来,注册到读写事件里。 而ngx_posted_events 是普通的延迟事件队列。一般情况下,什么样的事件会放到这个普通延迟队列里面呢?我的理解是,那些 CPU 耗时比较多的都可以放进去。因为 Nginx 事件处理都是根据触发顺序在一个大循环里依次处理的,因为 Nginx 一个进程同时只能处理一个事件,所以有些耗时多的事件会把后面所有事件的处理都耽搁了。 除了加锁,Nginx 也对各进程的请求处理的均衡性作了优化,也就是说,如果在负载高的时候,进程抢到的锁过多,会导致这个进程被禁止接受请求一段时间。 比如,在 ngx_event_accept 函数中,有类似代码: ```python ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n; ``` ngx_cycle->connection_n 是进程可以分配的连接总数,ngx_cycle->free_connection_n 是空闲的进程数。上述等式说明了,当前进程的空闲进程数小于 1/8 的话,就会被禁止 accept 一段时间。 ### **定时器** ------------ Nginx 在需要用到超时的时候,都会用到定时器机制。比如,建立连接以后的那些读写超时。Nginx 使用红黑树来构造定期器,红黑树是一种有序的二叉平衡树,其查找插入和删除的复杂度都为 O(logn),所以是一种比较理想的二叉树。 定时器的机制就是,二叉树的值是其超时时间,每次查找二叉树的最小值,如果最小值已经过期,就删除该节点,然后继续查找,直到所有超时节点都被删除。
李智
2025年3月17日 13:32
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码