本章我们讲述一下nginx中event的实现。Nginx中的event对象ngx_event_t
提供了一种机制,能够通知程序发生了某个事件。这里的event主要包括两大种类:
1. ngx_event_ovlp_t数据结构
当前我们不支持NGX_HAVE_IOCP
宏定义,因此这里不做介绍。
2. ngx_event_s数据结构
Nginx中的event对象ngx_event_t
提供了一种机制,能够通知程序发生了某个事件。下面我们详细介绍一下各字段的含义:
-
data: 指向用于event handlers的任何事件上下文,通常是指向与该event关联的connection对象。
-
write: 用于指示写事件
发生的标志。缺省状态下表示的是一个读事件
-
accept: 用于标识该事件是一个accept事件,还是一个posted事件。这两种不同的事件会放到不同的队列来进行处理
-
instance: 用于侦测kqueue和epoll中的陈旧事件
(stale event)
-
active: 该标志(flag)用于指示本event被登记(registered)为接收IO通知,通常是来自于epoll、kqueue、poll这样的通知机制
-
disabled: 该标志用于指明是否禁止本事件(读事件/写事件)
-
ready: 该标记用于指明本event收到了一个IO通知
-
oneshot: 一般用于指明当前event是否是属于一次性事件
-
complete: 当进行异步处理本事件时,标志是否处理完成
-
eof: 用于标记在进行读数据
(read data)的时候,读到了EOF
-
error: 用于标记在进行读(read event)或者写(write event)时,是否发生了错误
-
timedout: 用于标记事件定时器(event timer)是否已经超时
-
timer_set: 用于标记事件定时器(event timer)被设置,并且没有超时
-
delayed: 用于标记由于速率限制
(rate limiting)的原因导致IO被延迟
-
deferred_accept: 是否进行延迟accept,主要是为了提高程序的性能方面考虑
-
pending_eof: 本标志用于指示所对应socket上有未处理的EOF,即使在EOF之前可能仍存在一些可用数据。这通常是由epoll的EPOLLRDHUP
时间产生的或者kqueue的EV_EOF
标志产生的。
-
posted: 用于指示该事件是否投递到了一个队列
-
closed: 用于指示本事件所关联的socket句柄或文件句柄是否被关闭
-
channel: 指示本事件用于nginx中master与worker之间的通信,以反应子进程是否已经退出
-
resolver: 指示本事件用于nginx中的域名解析,判断在nginx的worker进程退出时,是否仍然还有连接处于resolver状态
-
cancelable: 本标志用于定时器事件,用于指示当worker进程退出时,本事件应该被忽略。优雅的关闭worker子进程(Graceful worker shutdown)时,如果有不可取消
(none-cancelable)的定时器事件正处于scheduled
,那么关闭会被延迟
-
accept_context_updated: 当前我们不支持NGX_WIN32
宏定义,暂不使用
-
kq_vnode: 当前我们不支持NGX_HAVE_KQUEUE
宏定义,在kqueue中EVFILT_VNODE过滤器
用于检测对文件系统上一个文件的某种改动
-
kq_errno: 当前我们不支持NGX_HAVE_KQUEUE
宏定义。kqueue报告的处于pending状态的错误码
-
available: 我们当前并不支持NGX_HAVE_KQUEUE
以及NGX_HAVE_IOCP
宏定义。available:1
通常用于控制accept()是否仍可以继续接受新的连接;
#if (NGX_HAVE_KQUEUE) || (NGX_HAVE_IOCP)
int available;
#else
unsigned available:1;
#endif
- handler: 当事件发生时所执行的回调函数。该回调函数的原型在core/ngx_core.h头文件中定义
typedef void (*ngx_event_handler_pt)(ngx_event_t *ev);
-
ovlp: 当前我们并不支持NGX_HAVE_IOCP
宏定义
-
index: 其通常用于表示本event属于某一个event数组中的哪个索引处
-
log: 本event所关联的日志对象
-
timer: 红黑树节点,用于插入当前事件(event)到timer树中
-
queue: Queue节点,用于将本事件post到一个队列中(不同类型的事件,可能会投递到不同的队列)
3. ngx_event_aio_s数据结构
当前我们并不支持NGX_HAVE_FILE_AIO
宏定义,这里不做介绍。
4. ngx_event_actions_t数据结构
本数据结构定义了一些函数指针,用于处理与事件
相关的一些操作。每一个事件模块(event module)都需要实现如下10个方法。下面简单介绍一下这些函数指针:
-
add: 将一个事件添加到 事件驱动机制
(如select、poll、epoll、kqueue)中
-
del: 将一个事件从事件驱动机制中删除
-
enable: 启用一个事件。通常情况下,我们并不会使用到,因为将事件添加到事件驱动机制
中以后,一般并不需要另行启用
-
disable: 禁用一个事件。通常情况下,我们并不会使用到
-
add_conn: 将一个connection
(连接)添加到事件驱动机制中,连接上的读、写事件即被加入到了事件驱动机制中
-
del_conn: 从事件驱动机制中删除一个连接的读、写事件
-
notify: 主要是实现某种类似与pipe
这样的通知机制。当前主要用在nginx线程池
的实现方面,当任务处理完成,异步通知主线程。
-
process_events: 处理事件的方法。由于select、poll、epoll等不同的事件驱动机制,对事件的处理也有些不同。
-
init: 一般用于初始化事件驱动机制
-
done: 事件驱动机制退出时的回调函数
接着声明了一个全局的ngx_event_actions_t类型的对象: ngx_event_actions。
5. 事件驱动机制特性掩码
上面我们介绍到有select、poll、epoll、kqueue、eventport等不同的事件驱动机制。每一种事件驱动机制所能支持的一些特性可能也有些不同。下面我们分别介绍一些这些标志:
-
NGX_USE_LEVEL_EVENT: 表示事件驱动机制的event filter支持水平触发。select, poll, /dev/poll, kqueue, epoll均支持这一模式
-
NGX_USE_ONESHOT_EVENT: 表示事件驱动机制的event filter支持oneshot,即表示某个事件触发一次之后,对应的event filter就会被自动的移除,可以确保事件只触发一次。kqueue, epoll支持本选项。
-
NGX_USE_CLEAR_EVENT: 表示事件驱动机制的event filter支持边沿触发。epoll、kqueue支持本选项
-
NGX_USE_KQUEUE_EVENT: 表示对应的事件驱动机制的event filter支持kqueue特性: eof flag、errno、available data等。一般kqueue支持本选项
-
NGX_USE_LOWAT_EVENT: 表示对应的事件驱动机制的event filter支持低水位标志
(low water mark)。一般kqueue支持本选项
-
NGX_USE_GREEDY_EVENT: 表示对应的事件驱动机制的event filter需要重复执行IO操作,直到EAGAIN。一般epoll支持本选项。
通常情况下,我们读取数据时,如果实际读取的数据字节数小于我们的请求数,那么此时我们可以直接将event.ready设置为0;
但是对于支持本选项的事件机制,即使遇到此情形,通常还是要继续进行读取,直到返回的EAGAIN为止,才将event.ready设置为0.
-
NGX_USE_EPOLL_EVENT: 本event filter用于epoll
-
NGX_USE_RTSIG_EVENT: 当前已经过时,不再使用
-
NGX_USE_AIO_EVENT: 当前已经过时,不再使用
-
NGX_USE_IOCP_EVENT: 本event filter用于IOCP
-
NGX_USE_FD_EVENT: 表示本event filter没有透明数据,并需要一个文件描述符表。本标志通常用于poll、dev/poll。下面我们以poll为例来说明一下为何要使用此选项:
上面代码,当有读写事件到来时,我们只能够通过pfds
获取到文件的句柄信息,并没有透明数据
。但是一般我们可能还需要获取到更为详细的信息(透明数据),此时我们可能还需要绑定另外一个结构,通过fd就可以简单快捷的定位到。
6. 事件驱动机制中的各种事件
这里主要是定义了各事件驱动机制
中的一些事件。通过宏屏蔽不同事件驱动机制的差异。
7. 相关函数的宏定义
这里主要是对相关函数指针进行重新定义,方便使用。
8. event模块宏
这里主要是nginx event模块相应的标识
9. ngx_event_conf_t数据结构
本数据结构用于保存event模块相关配置。下面简要介绍一下各字段的含义:
-
connections: 指定一个worker进程可以打开的最大连接数。注意这不仅包括客户端的连接数,还包括nginx到后端代理服务器之间的连接等
-
use: 保存当前事件模块采用的是哪一种事件处理方法(connection_processing)。通常配置文件中不会进行指定,nginx会自己选择当前所支持的最高效的方法,然后保存到此变量中。
-
multi_accept: 假如multi_accept
被禁用的话,worker进程一次只会accept一个新的连接(new connection)。否则,worker进程一次会accept所有新的连接。
-
accept_mutex: 假如accept_mutex
被启用的话,worker进程之间将会轮流地accept
新的连接(connection)。否则,在新连接(connections)到来时,所有的worker进程都会被唤醒,但可能只有某些进程能够获取到新连接(connections),其他的worker进程会继续进入休眠状态。这就是所谓的惊群现象
。
注:
1. 高版本的Linux中,accept不存在惊群问题,不过epoll_wait等操作还有
2. 在nginx 1.11.3版本之前,本选项是默认启用的
-
accept_mutex_delay: 当accept_mutex
被启用的话,如果当前有另一个worker进程正在accept新连接(connections),则当前worker进程最长会等待多长时间尝试重新开始accept新连接。其实就是获取互斥锁的最大延迟时间
-
name: 用于存放当前event_module的名称(select、poll、epoll、kqueue等)
-
debug_connection: 为所指定的客户端连接启用调试日志(debugging log)。而对于其他的连接则会使用error_log
指令所设置的日志级别。调试的连接可以通过IPv4或者IPv6来指定,也可以通过主机名来指定。此外,也支持unix域socket。参看如下示例:
events {
debug_connection 127.0.0.1;
debug_connection localhost;
debug_connection 192.0.2.0/24;
debug_connection ::1;
debug_connection 2001:0db8::/32;
debug_connection unix:;
...
}
10. ngx_event_module_t数据结构
本数据结构作为事件模块
的上下文环境。下面我们简要介绍一下各字段的含义:
-
name: 用于保存所使用的事件驱动机制
的名称(select、poll、epoll、kqueue等)
-
create_conf: 用于创建event模块相关配置(ngx_event_conf_t)的回调函数
-
init_conf: 用于初始化event模块相关配置(ngx_event_conf_t)的回调函数
-
action: event模块的actions操作函数
11. 相关全局变量声明
下面简要介绍一下各变量的含义:
-
ngx_connection_counter: 用于统计当前的连接数
-
ngx_accept_mutex_ptr: 存放互斥量内存地址的指针
-
ngx_accept_mutex: accept互斥锁
-
ngx_use_accept_mutex: 表示是否需要通过对accept加锁来解决惊群问题。当nginx worker进程数大于1
时,且配置文件中开启了accept_mutex
时,这个标志置为1
-
ngx_accept_events: 表示在获取accept互斥锁的时候,是否还需要调用ngx_enable_accept_events()来使能accept events。通常采用eventport
这一事件驱动机制时才需要。
-
ngx_accept_mutex_held: 用于指示当前是否拿到了锁
-
ngx_accept_mutex_delay: 当accept_mutex
被启用的话,如果当前有另一个worker进程正在accept新连接(connections),则当前worker进程最长会等待多长时间尝试重新开始accept新连接。其实就是获取互斥锁的最大延迟时间
-
ngx_accept_disabled: 用于控制当前worker进程是否参与获取ngx_accept_mutex
。一般在当前worker进程连接数过多时,就不会参与竞争,这样可以起到负载均衡的目的。
我们当前并不支持NGX_STAT_STUB
,该宏定义需要在启用HTTP_STUB_STATUS
编译选项时才会定义。请参看Module ngx_http_stub_status_module但是这里我们还是简单介绍一下各字段的含义。
-
ngx_stat_accepted: 用于统计当前nginx一共accept多少连接
-
ngx_stat_handled: 用于统计当前所处理的总的连接数。通常情况下,本字段的值与ngx_stat_accepted
字段的值相同,除非达到了一些资源的限制(例如,worker_connections的限制)
-
ngx_stat_requests: 用于统计客户端总的请求数
-
ngx_stat_active: 用于统计当前处于active状态的客户端连接数,这也包括Waiting
状态的连接
-
ngx_stat_reading: 当前Nginx正在读取请求头
(request header)的连接数
-
ngx_stat_writing: 当前nginx正在向客户端回写应答
(write the response back)的连接数
-
ngx_stat_waiting: 当前正处于空闲状态的客户端连接数。
-
ngx_event_timer_alarm: 表示收到了SIGALRM
信号产生的alarm事件,收到信号后一般表示需要更新nginx缓存时间
-
ngx_event_flags: 用于保存当前事件驱动机制
所支持的事件掩码
-
ngx_events_module: events模块。作为event模块的入口,解析events{}中的配置项,同时管理这些事件模块存储配置项的结构体。select、poll、epoll都是属于events模块,每一个事件模块产生的配置结构体指针都会被放在ngx_events_module模块创建的指针数组中。
-
ngx_event_core_module: events核心模块。负责维护事件模块相关核心配置。
注: ngx_events_module与ngx_event_core_module与的关系是一种包含与被包含的关系。参看events模块的配置文件:
events {
accept_mutex on;
multi_accept on;
#use epoll;
worker_connections 1024;
}
- ngx_event_get_conf: 用于获取event模块相关配置项的值
12. 相关函数声明
13. nginx中事件介绍
13.1 IO事件
每一条通过调用函数ngx_get_connection()获取到的连接(connection)都绑定了两个事件:c->read以及c->write,分别用于接收对应socket读、写就绪的通知(notification)。所有这些事件都以边沿触发模式
(Edge-Triggered mode)工作,这意味着这些事件只会在socket状态发生改变时才会触发通知(notification)。举例来说,假如你在一个socket上只读取部分数据(partial read),那么nginx并不会重复投递(deliver)读通知(read notification),直到后续有更多的数据到达socket。注意,即使底层的IO通知机制实际上用的是水平触发
(Level Triggered,如select、poll等),nginx会将这些通知转换成边沿触发
(Edge-Triggered)。
为了使得nginx事件通知在不同平台的所有通知系统(notification systems)上都保持一致,在处理完一个IO socket通知或者调用了socket的任何IO函数之后,我们都需要再函数ngx_handle_read_event(rev, flags)或ngx_handle_write_event(wev,lowat)。通常情况下,这些函数都会在每一个读(read)、写(write)事件的handler末尾处被调用一次。
13.2 定时器事件
一个event可以被设置为在其超时的时候发送一个notification。由该event所使用的定时器从上一次某一个时间点开始以毫秒
为单位开始进行计数。当前的毫秒值可以通过ngx_current_msec
变量来获取到。
函数ngx_add_timer(ev,timer)可以为一个事件设置超时时间,而ngx_del_timer(ev)可以删除一个以前所设置的超时时间。全局的超时红黑树ngx_event_timer_rbtree存储当前所有的超时集。红黑树中节点的key为ngx_msec_t
类型,其存储的就是定时器的到期时间。红黑树的结构使得能够快速的进行插入与删除操作,也能够快捷的访问到当前最近的超时时间,nginx会使用该最近超时时间来等待IO的发生。
13.3 Posted事件
一个event可以被posted
,这就意味着在当前事件循环的后续某个时间点,该event的handler会被调用。posting events在简化代码以及避免栈溢出方面都是很好实践(good practise)。被投递(posted)的事件会被存放在post queue
中。宏定义ngx_post_event(ev,q)会将事件ev
投递到post队列q
中;而ngx_delete_posted_event(ev)宏定义会将事件ev
从其当前所投递的队列中移除。通常情况下,events会被投递到ngx_posted_events队列中,这些事件会在事件循环的后期被处理: 在所有的IO及定时器事件被处理完成之后。函数ngx_event_process_posted()会被调用以处理一个事件队列,其会不断调用event handlers直到整个队列为空。这就意味着一个posted event handler可以在一个事件循环中投递多个事件,然后再被处理。请参看如下示例:
示例中,当connection有可读事件到达时,就会调用ngx_my_read_handler()回调函数来读取数据。
13.4 Event loop
除了nginx的master进程之外,所有的worker进程都会进行IO操作,因此都有一个事件循环(event loop)
。(nginx的master进程把其大部分时间都耗费在sigsuspend()函数调用上,以等待信号signals的到达。)nginx事件循环是在函数ngx_process_events_and_timers()中实现的,其会重复的被调用,直到整个进程退出。
事件循环(event loop)有如下的一些stages(阶段):
-
通过调用ngx_event_find_timer()来查找当前最近的超时时间。该函数会查找红黑树中的最左边的节点,并返回该节点超时的毫秒数
-
根据nginx的配置的特定的事件通知机制,通过调用一个handler来处理IO事件。该handler会等待至少一个IO事件发生,或者等待超时。当一个读(read)、写(write)事件发生时,ready
标志会被设置,然后事件的handler会被调用。对于Linux来说,通常情况下会使用函数ngx_epoll_process_events(),该函数会调用epoll_wait()
来等待IO事件
-
通过调用ngx_event_expire_timers()来处理超时定时器事件。会从timer红黑树的最左侧节点开始遍历直到一个未超时的事件被找到。对于每一个已经超时的节点,event的timeout
标志会被设置,event的timer_set标志会被重置,然后事件的handler会被调用
-
通过调用ngx_event_process_posted()来处理被投递(posted)的事件。该函数会重复的执行: 移除队列中的第一个元素,然后调用该元素的handler()回调,直到整个队列为空。
所有的nginx进程(包括master进程与worker进程)都会处理信号(signals)。信号处理器只会是指一些全局变量
的值,这些变量的值会在调用完ngx_process_events_and_timers()函数之后被检查,以做进一步处理。
[参看]
-
nginx events
-
nginx event 模块解析
-
Linux TCP_DEFER_ACCEPT的作用
-
freebsd高级I/O,kevent的资料很详细
-
Nginx学习笔记(十八):事件处理框架
-
事件和连接
-
Nginx学习笔记(十八):事件处理框架
-
Nginx的accept_mutex配置
-
Nginx配置晋级之路(四)—events模块