Nginx如何处理Web请求

Web 服务器处理请求的方式

Web 服务器和客户端是一对多的关系,Web 服务器必须有能力同时为多个客户端提供服务,一般来说完成并行处理请求工作有三种方式可供选择:多进程方式、多线程方式和异步方式。

多进程方式

多进程方式是指服务器每当接收到一个客户端请求时,就由服务器主进程生成一个子进程出来和该客户端建立连接进行交互,直到连接断开,该子进程就结束了。

多进程方式的优点在于,设计和实现相对简单,各个子进程之间相互独立,处理客户端请求的过程互相不干扰,并且当一个子进程产生问题时,不容易将影响漫延到其它进程中,保证了服务的稳定性。当子进程退出时,其占用资源会被操作系统回收,不会留下任何垃圾。而其缺点在于,进程是操作系统的有限资源,每次生成一个子进程在资源和时间上的开销比较大,当 Web 服务器接收大量并发请求时,会对系统资源造成压力,导致系统性能下降。

初期的 Apache 服务器就是采用这种方式,为了应付大量并发请求,apache 采用了 预生成进程 机制对多进程方式进行了改进,所谓预生成进程机制就是类似于进程池,在客户端请求到达之前预先生成好子进程,当请求到来时,主进程分配一个子进程与客户端进行交互,节省了实时创建子进程的消耗,交互完成后,该进程不结束,而是被主进程管理起来,等待下一个请求,改进的多进程方式一定程度上缓解了服务器对系统资源造成的压力,但并不能从根本上解决问题。

多线程方式

多线程方式是指服务器每当接收到一个客户端请求时,就由服务器主进程派生出一个线程出来和该客户端建立连接进行交互。看上去跟多进程方式差不多,但是操作系统产生一个线程的开销远远小于产生一个进程的开销,所以多线程方式在很大程度上减轻了 Web 服务器对系统资源的要求。而缺点在于,多个线程位于同一个进程内,可以访问同样的内存空间,彼此之间相互影响,同时,开发过程中不可避免地要由开发者自己地内存进行管理,增加了出错的风险,服务器需要长时间不停地运转,错误的积累可能最终对整个服务器产生重大影响。

IIS 服务器采用这种方式,它的稳定性相对来说还是不错的。

异步方式

说异步方式之前,先熟悉几个概念:同步和异步、阻塞和非阻塞。

在网络通信中的同步机制和异步机制是描述通信模式的概念:

  • 同步机制:指发送方发送请求后,需要等待直到接收到接收方发回的响应,才能接着发送下一个请求。
  • 异步机制:指发送方发送请求后,不等待接收方的响应,就继续发送下个请求。

阻塞和非阻塞是用来描述进程处理调用的方式,在网络通信中,主要指网络套接字 socket 的阻塞和非阻塞方式,而 socket 的实质就是 IO 操作。

  • 阻塞调用:调用结果返回前,当前线程从运行状态被挂起,一直等到调用结果返回之后,才进入就绪状态,获取 CPU 后继续执行
  • 非阻塞调用:如果调用结果不能马上返回,当前线程也不会被挂起,而是立即返回并执行下一个调用

经常可以看到人们将同步和阻塞等同、异步和非阻塞等同,事实上,这两对概念有一定的区别,两两组合,就产生了四个概念:同步阻塞、同步非阻塞、异步阻塞、异步非阻塞

同步阻塞

这种方式实现简单,但是效率不高。
发送方向接收方发送请求后,一直等待响应,直到接收到接收方的响应,才能进行下一次请求。
接收方处理请求时,阻塞式等待处理结果,期间不能进行其它工作,直到返回结果,响应发送方,才能处理下一个请求。

同步非阻塞

发送方向接收方发送请求后,一直等待响应,直到接收到接收方的响应,才能进行下一次请求。
接收方处理请求时,如果不能马上得到结果,接收方不会阻塞式地等待处理结果,而会去做其它事情,由于没有得到处理结果,所以也不响应发送方,一直到最后 IO 操作完成,返回结果,响应发送方。
实际中不使用这种方式。

异步阻塞

发送方向接收方发送请求后,不用等待响应,接着进行其它工作。
接收方处理请求时,阻塞式等待处理结果,期间不能进行其它工作,直到返回结果,响应发送方,才能处理下一个请求。
实际中不使用这种方式。

异步非阻塞

发送方向接收方发送请求后,不用等待响应,接着进行其它工作。
接收方处理请求时,如果不能马上得到结果,也不等待,而会去做其它事情,当 IO 操作完,将完成状态和结果通知接收方,接收方再响应发送方。
在这四种方式中,这种方式是发送方和接收方通信效率最高的一种。

Nginx 服务器处理请求的方式

Nginx 服务器能够同时处理大量并发请求,它使用的是多进程机制和异步机制结合的方式提供服务。

Nginx 服务器启动后,可以产生一个主进程(master process)和多个工作进程(worker process),其中,工作进程数量可以在配置文件中指定。Nginx 服务器的所有工作进程都用于接收和处理客户端的请求。这类似于 Apache 的多进程机制,预生成多个工作进程,等待处理客户端请求。

每个工作进程使用了异步非阻塞方式,可以同时处理多个客户端请求。当某个工作进程接收到客户端请求后,调用 IO 进行处理,如果不能立即得到结果,就会去处理其它请求,而客户端也无需等待,可以去处理其它事情,当 IO 调用返回结果时,就会通知此工作进程,该进程得到通知,暂时挂起当前处理的事务,去响应客户端请求。

Nginx 服务器使用多进程机制,能够保证不增长对系统资源的压力,使用异步非阻塞方式,则减少了工作进程在 IO 调用上的阻塞延迟,保证并发处理能力。

事件驱动模型

工作进程调用 IO 后,就去进行其它工作了,当 IO 返回后会通知工作进程,这里有一个问题,IO 调用是如何把自己的状态通知给工作进程的?

一般解决这个问题的方案有两种:

  • 工作进程在进行其它工作的过程中,间隔一段时间就去检查一下 IO 的运行状态,如果完成,就去影响客户端,如果未完成,就继续正在进行的工作。但是不断地检查在时间和资源上会导致不少的开销
  • IO 调用在完成后主动通知工作进程。

具体来说,select/poll/epoll/kqueue 等这样的系统调用就是用来支持第二种解决方案的。这些系统调用,也常被称为事件驱动模型,它们提供了种机制,让进程可以同时处理多个并发请求,不用关心 IO 调用的具体状态。IO 调用完全由事件驱动模型来管理,事件准备好之后就通知工作进程事件已就绪。

服务器基于事件驱动模型进行响应和处理 Web 请求,服务器的事件处理机制有以下几种实现方式:

  • 服务器接收到 Web 请求,创建一个新的进程,调用事件处理器来处理该请求
  • 服务器接收到 Web 请求,创建一个新的线程,调用事件处理器来处理该请求
  • 服务器接收到 Web 请求,将其放入一个待处理事件的列表,使用非阻塞 IO 方式调用事件处理器来处理该请求

上面三种方式,第一种方式,由于创建新的进程开销比较大,会导致服务哭喊性能比较差,但其实现相对来说比较简单。第二种方式,由于要涉及到线程的同步,可能会面临死锁、同步等一系列问题,编码比较复杂。第三种方式,逻辑比前面两种都复杂,大多数网络服务器采用了第三种方式,逐渐形成了所谓的 事件驱动处理库

事件驱动处理库又称为多路 IO 复用方法,常见的包括:select模型、poll模型、epoll模型。

select库

select 库是各个版本 Linux 和 Windows 平台都支持的基本事件驱动模型库。

select 库的工作方式:

  1. 先创建一个关注事件的描述符集合,对于一个描述符,可以关注其上面的读事件,写事件以及异常发生事件,所以要创建三类事件描述符集合,分别用来收集读事件的描述符、写事件的描述符和异常事件的描述符。
  2. 调用底层的 select() 函数,等待事件发生。
  3. 轮询所有事件描述符集合中的每一个事件描述符,检查是否有相应的事件发生,如果有,就进行处理。
poll库

poll 库是 Linux 平台的基本事件驱动模型,Windows 平台不支持。

poll 库的工作方式跟 select 库基本相同:先创建描述符集合,然后等待这些事件发生,然后再轮询描述符集合,检查有没有事件发生,如果有,就进行处理。poll 库与 select 库的区别在于,select 库为读事件、写事件和异常事件分别创建了一个描述符集合,因此在最后轮询的时候,需要分别轮询这三个集合,而 poll 库只创建一个集合,在每个描述符的结构上分别设置了读事件、写事件和异常事件,轮询的时候同时检查这三种事件是否发生。可以说,poll 库是 select 库的优化实现。

epoll库

epoll 库是 Nginx 支持的高性能事件驱动库之一,是公认的非常优秀的事件驱动模型。

epoll 库属于 poll 库的一个变种,但跟 select 库和 poll 库有很大不同。select 库和 poll 库的处理方式都是创建一个待处理事件列表,然后把这个列表发给内核,返回的时候,再去轮询检查这个列表,以判断事件是否发生。这样在描述符比较多的应用中,效率就会显得低下。一种比较好的做法是,把描述符列表的管理交由内核负责,一旦有某种事件发生,内核把发生事件的描述符列表通知给进程 ,这样就避免了轮询整个描述符列表。

epoll 库的工作方式:

  1. epoll 库通过相关调用通知内核创建一个有 N 个描述符的事件列表,然后给这些描述符设置所关注的事件,并把它添加到内核的事件列表中去。
  2. epoll 库等待内核事件,当某一事件发生后,内核将发生事件的描述符列表上报给 epoll 库,epoll 库就可以进行事件处理了。

epoll 库在 Linux 平台上是高效的,它支持一个进程打开大数目的事件描述符,上限是系统可以打开文件的最大数目,同时,epoll 库的 IO 效率不随描述符数量增加而线性下降,因为它只会对内核上报的活跃的描述符进行操作。

发布了153 篇原创文章 · 获赞 22 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/gongm24/article/details/103592767