最初的Web服务器如Apache,采用的是fork and run的模式,对每个到来的连接,fork一个进程去处理,处理完成后进程退出。优点是编程实现简单,缺点是并发处理能力不足。为应对高并发的处理,以Nginx为代表的异步处理方式应运而生。
Nginx以事件驱动工作,事件的来源有两个,网络IO和定时器。
对于高并发的网络IO处理,不同的操作系统提供了不同的解决方案,如linux的epoll, freebsd的kqueue。这里以epoll为例。将需要监听的socket加入到epoll中后,便可以通过epoll_wait获取已发生的事件,避免对众多的socket进行轮寻。
Nginx的定时器采用红黑数实现,每个定时器事件以超时时间为key插入到红黑树中,每次取红黑树中key最小的结点与当前的系统时间比较即可知道是否超时。
Nginx工作进程处理任务的核心在ngx_process_events_and_timers函数中。
1 |
|
ngx_process_events_and_timers主要逻辑如下所示。
1 |
|
函数中先调用ngx_process_events处理epoll_wait得到的网络IO事件,再调用ngx_event_expire_timers处理所有的超时事件。
1 |
|
利用epoll模式时ngx_process_events即为ngx_epoll_process_events,上面的代码中省略了无关的部分,当revents & EPOLLIN为true时socket有数据可以读取,revents & EPOLLOUT为true时socket可写,然后调用相应的handler回调函数进行处理。
ngx_event_expire_timers处理定时器事件逻辑更简单一些,主要就是调用ngx_rbtree_min获取超时时间最近的事件,如果已超时即调用ev->handler进行处理。
1 |
|
对于网络IO主要操作就是读和写,现实网络环境非常复杂,连接会因为各种原因中断,无法传输数据。同时服务器端的每个网络连接都要消耗服务器资源,为了避免无效的连接一直占用系用的资源,需要对读写操作设置超时机制,为此Nginx做了专门的处理。
对每个到来的链接,Nginx分配一个ngx_connection_t结构体存储相应的信息,每个ngx_connection_t结构体都有两个成员read和write,对应两个ngx_event_t事件。当需要从socket读取数据时,将socket加入到epoll监听事件中,同时将对应的read事件加入到定时器中,如果定时器超时后仍然没有数据可读,便认为读取数据超时。
事件超时时,Nginx会将ev->timedout置为1,再调用ev->handler。相应的Nginx中对每个事件的handler回调函数大多会有如下的逻辑
1 |
|
handler回调函数的开头判断事件是否已超时,如果ev->timedout不为0便认为已超时。可能会关闭连接,也可能会执行其他操作。