HTTP(29):NGINX

108 阅读5分钟

进程池

Nginx 是个“轻量级”的 Web 服务器,那么这个所谓的“轻量级”是什么意思呢?

“重量级”指服务器进程很“重”,占用很多资源,当处理 HTTP 请求时会消耗大量的 CPU 和内存,受到这些资源的限制很难提高性能。

而 Nginx 作为“轻量级”的服务器,它的 CPU、内存占用都非常少,同样的资源配置下就能够为更多的用户提供服务,其奥秘在于它独特的工作模式。

image.png

Nginx 之前,Web 服务器的工作模式大多是Per-ProcessPer-Thread,对每一个请求使用单独的进程或线程处理。这就存在创建进程或线程的成本,还会有“上下文切换”的额外开销。如果请求数量很多,CPU 就会在多个进程、线程之间切换时“疲于奔命”,平白地浪费了计算时间。

Nginx 则完全不同,“一反惯例”地没有使用多线程,而是使用了进程池 + 单线程的工作模式。

Nginx 在启动的时候会预先创建好固定数量的 worker 进程,在之后的运行过程中不会再 fork 出新进程,这就是进程池,而且可以自动把进程“绑定”到独立的 CPU 上,这样就完全消除了进程创建和切换的成本,能够充分利用多核 CPU 的计算能力。

在进程池之上,还有一个“master”进程,专门用来管理进程池。用来监控进程,自动恢复发生异常的 worker,保持进程池的稳定和服务能力。

I/O 多路复用

使用多线程能够很容易实现并发处理,也有一些缺点,除了刚才说到的“上下文切换”成本,还有编程模型复杂、数据竞争、同步等问题,写出正确、快速的多线程程序并不是一件容易的事情。

所以 Nginx 选择了单线程的方式,带来的好处就是开发简单,没有互斥锁的成本,减少系统消耗。

那么,疑问也就产生了:为什么单线程的 Nginx,处理能力却能够超越其他多线程的服务器呢?

这要归功于 Nginx 利用了 Linux 内核里的I/O 多路复用接口epoll

Web 服务器从根本上来说是“I/O 密集型”而不是“CPU 密集型”,处理能力的关键在于网络收发而不是 CPU 计算(这里暂不考虑 HTTPS 的加解密),而网络 I/O 会因为各式各样的原因不得不等待,比如数据还没到达、对端没有响应、缓冲区满发不出去等等。

这种情形就有点像 HTTP 的“队头阻塞”。单线程 CPU 就会“停下来”,造成浪费。而多线程的解决思路有点类似“并发连接”,虽然有的线程可能阻塞,但由于多个线程并行,总体上看阻塞的情况就不会太严重了。

Nginx 里使用的 epoll,就好像是 HTTP/2 里的“多路复用”技术,它把多个 HTTP 请求处理打散成碎片,都“复用”到一个单线程里,不按照先来后到的顺序处理,而是只当连接上真正可读、可写的时候才处理,如果可能发生阻塞就立刻切换出去,处理其他的请求。

通过这种方式,Nginx 就完全消除了 I/O 阻塞,把 CPU 利用得“满满当当”,又因为网络收发并不会消耗太多 CPU 计算能力,也不需要切换进程、线程,所以整体的 CPU 负载是相当低的。

Nginx“I/O 多路复用”的示意图,你可以看到,它的形式与 HTTP/2 的流非常相似,每个请求处理单独来看是分散、阻塞的,但因为都复用到了一个线程里,所以资源的利用率非常高。

image.png

epoll 还有一个特点,大量的连接管理工作都是在操作系统内核里做的,这就减轻了应用程序的负担,所以 Nginx 可以为每个连接只分配很小的内存维护状态,即使有几万、几十万的并发连接也只会消耗几百 M 内存,而其他的 Web 服务器这个时候早就“Memory not enough”了。

多阶段处理

有了“进程池”和“I/O 多路复用”,Nginx 是如何处理 HTTP 请求的呢?

Nginx 在内部也采用的是“化整为零”的思路,把整个 Web 服务器分解成了多个“功能模块”,可以在配置文件里任意拼接搭建,从而实现了高度的灵活性和扩展性。

Nginx 的 HTTP 处理有四大类模块:

  1. handler 模块:直接处理 HTTP 请求;
  2. filter 模块:不直接处理请求,而是加工过滤响应报文;
  3. upstream 模块:实现反向代理功能,转发请求到其他服务器;
  4. balance 模块:实现反向代理时的负载均衡算法。

因为 upstream 模块和 balance 模块实现的是代理功能,Nginx 作为“中间人”,运行机制比较复杂,所以我今天只讲 handler 模块和 filter 模块。

不知道你有没有了解过“设计模式”这方面的知识,其中有一个非常有用的模式叫做“职责链”。它就好像是工厂里的流水线,原料从一头流入,线上有许多工人会进行各种加工处理,最后从另一头出来的就是完整的产品。

Nginx 里的 handler 模块和 filter 模块就是按照“职责链”模式设计和组织的,HTTP 请求报文就是“原材料”,各种模块就是工厂里的工人,走完模块构成的“流水线”,出来的就是处理完成的响应报文。

下面的这张图显示了 Nginx 的“流水线”,在 Nginx 里的术语叫“阶段式处理”,一共有 11 个阶段,每个阶段里又有许多各司其职的模块。

image.png

小结

  1. Nginx 是一个高性能的 Web 服务器,它非常的轻量级,消耗的 CPU、内存很少;
  2. Nginx 采用“master/workers”进程池架构,不使用多线程,消除了进程、线程切换的成本;
  3. Nginx 基于 epoll 实现了“I/O 多路复用”,不会阻塞,所以性能很高;
  4. Nginx 使用了“职责链”模式,多个模块分工合作,自由组合,以流水线的方式处理 HTTP 请求。