关于Nginx

这应该是一篇迟到好几年的文章了,之前面试就被问过关于nginx的东西。回忆一下回答的并不好,大多数时候其实都只是简单的应用而已。很少去关心为什么用?所以想想也对,一直逃避的问题其实是逃不掉,所以最终还是要坦然面对。

简介

nginx(pronounced “engine x”),开源web服务器。nginx has focused on high performance, high concurrency and low memory usage.

Architechture

img

nginx在启动后,会有一个master进程和多个worker进程。master进程主要用来管理worker进程,包含:接收来自外界的信号,向各worker进程发送信号,监控worker进程的运行状态,当worker进程退出后,会自动重新启动新的worker进程。而基本的网络事件,则是放在worker进程中来处理了。worker进程的个数是可以设置的,一般我们会设置与机器cpu核数一致。

master来管理worker进程,所以我们只需要与master进程通信就行了,master进程会接收来自外界发来的信号,再根据信号做不同的事情。所以我们要控制nginx,只需要通过kill向master进程发送信号就行了。

worker进程之间是平等的,每个进程,处理请求的机会也是一样的。当我们提供80端口的http服务时,一个连接请求过来,每个进程都有可能处理这个连接,怎么做到的呢?所有worker进程都是从master进程fork过来,在master进程里面,先建立好需要listen的socket(listenfd)之后,然后再fork出多个worker进程。所有worker进程的listenfd会在新连接到来时变得可读,为保证只有一个进程处理该连接,所有worker进程在注册listenfd读事件前抢accept_mutex,抢到互斥锁的那个进程注册listenfd读事件,在读事件里调用accept接受该连接,这样一个完整的请求就是这样的了。

nginx采用这种进程模型的好处

  • 首先,对于每个worker进程来说,独立的进程,不需加锁,所以省掉了锁带来的开销。
  • 其次,采用独立的进程,可以让互相之间不会影响,一个进程退出后,其他进程还在工作,服务不会中断,master进程则很快启动新的worker进程。

IO多路复用(事件驱动)。

Connections are processed in a highly efficient run-loop in a limited number of single-threaded processes called workers. Within each worker nginx can handle many thousands of concurrent connections and requests per second.

nginx uses event notification mechanisms and a number of disk I/O performance enhancements in Linux, Solaris and BSD-based operating systems. like kqueue, poll, and event ports.

nginx采用了异步非阻塞的方式来处理请求。

apache的工作方式?每个请求会独立占一个工作线程,当并发数上到几千时,就同时有几千的线程在处理请求。这对操作系统来说,是个不小的挑战:

  • 线程带来的内存占用非常大
  • 线程的上下文切换带来的cpu开销很大

在nginx里面,最忌讳阻塞的系统调用了。不要阻塞,那就非阻塞。非阻塞就是事件没有准备好,马上返回EAGAIN,告诉你,事件还没有准备好呢,你慌什么,过会再来吧。(select/poll/epoll/kqueue)

拿epoll为例,当事件没有准备好时,放到epoll里面,事件准备好了,我就去读写,当读写返回EAGAIN时,我们将它再次加入到epoll里面。这样只要事件准备好了,我们就去处理它,只有当所有事件都没准备好时,才在epoll里面等着。

推荐设置worker的个数为cpu的核数,在这里就很容易理解了,更多的worker数,只会导致进程来竞争cpu资源了,从而带来不必要的上下文切换。

对于一个基本的web服务器来说,事件通常有三种类型,网络事件、信号、定时器

其中网络事件通过异步非阻塞可以很好的解决掉。

信号的处理,对nginx来说,有一些特定的信号,代表着特定的意义。信号会中断掉程序当前的运行,在改变状态后,继续执行。如果是系统调用,则可能会导致系统调用的失败,需要重入。对于nginx来说,如果nginx正在等待事件(epoll_wait),如果程序收到信号,在信号处理函数处理完后,epoll_wait会返回错误,然后程序可再次进入epoll_wait调用。

定时器。由于epoll_wait等函数在调用的时候是可以设置一个超时时间的,所以nginx借助这个超时时间来实现定时器。nginx里面的定时器事件是放在一颗维护定时器的红黑树里面,每次在进入epoll_wait前,先从红黑树里面拿到所有定时器事件的最小时间,在计算出epoll_wait的超时时间后进入epoll_wait。所以,当没有事件产生,也没有中断信号时,epoll_wait会超时,也就是说,定时器事件到了。这时,nginx会检查所有的超时事件,将他们的状态设置为超时,然后再去处理网络事件。

Workers Model

Worker processes accept new requests from a shared “listen” socket and execute a highly efficient run-loop inside each worker to process thousands of connections per worker.

nginx runs several processes in memory; there is a single master process and several worker processes. There are also a couple of special purpose processes, specifically a cache loader and cache manager. All processes are single-threaded in version 1.x of nginx. All processes primarily use shared-memory mechanisms for inter-process communication.

The master process is responsible for the following tasks:

  • 读取以及验证配置
  • 创建、绑定以及关闭sockets
  • 启动、关闭以及维护配置好的worker进程
  • 重新加载配置
  • controlling non-stop binary upgrades
  • re-opening log files
  • compeiling embedded Perl scripts

高性能

  • 采用多进程异步非阻塞事件处理机制(epoll)。
  • master/worker结构。

采用单线程来异步非阻塞处理请求,不会为每个请求分配cpu和内存资源,节省了大量资源,同时也减少了大量的CPU的上下文切换。所以才使得Nginx支持更高的并发。

Nginx能够实现高性能和可扩展性的关键取决于两个基本的设计选型:

  • 尽可能限制工作进程的数量,从而减少上下文切换带来的开销。默认和推荐配置是让每个CPU内核对应一个工作进程,从而高效利用硬件资源。
  • 工作进程采用单线程,并以非阻塞的方式处理多个并发连接。

Nginx的每个工作进程通过状态机处理多个连接请求,这个状态机被实现为非阻塞的工作方式:

  • 每个工作进程需要处理若干套接字,包括监听套接字或者连接套接字。
  • 当监听套接字收到新的请求时,会打开一个新的连接套接字来处理与客户端的通信。
  • 当一个事件到达连接套接字时,工作进程迅速完成响应,并转而处理其他任何套接字新收到的事件。

Web服务器

一般Web服务器可通过以下三种方式提供服务:

  • 多进程方式(如Apache prefork模块)
  • 多线程方式(如Apache worker模块)
  • 异步方式(如Nginx worker模块)

其中效率最高的是异步方式,最稳定的是多进程方式,占用资源较少的是多线程方式。

Q

nginx如何复用端口号?

如何实现从容重启?(nginx -s reload)

Reference

http://tengine.taobao.org/book/index.html

http://www.aosabook.org/en/nginx.html

https://www.zhihu.com/question/28594409

http://www.infoq.com/cn/news/2015/06/nginx-design-performance-scale-/

More than your eyes can see