Nginx 介绍(译文II–Nginx架构综述)

本章目录

Nginx架构综述

使用单独的线程或进程处理每个连接,是基于线程或进程处理并发连接的传统做法。

  • 该做法往往会阻塞于网络和I/O操作。
  • 对于不同的应用,这种做法可能会使内存和CPU消耗变得非常低效。
  • 创建一个新的进程或线程需要准备新的运行环境,包括在内存中分配新的堆栈、新的运行上下文。
    在创建这些过程中,必然会产生额外的CPU时间,而这种运行上下文切换引起的线程抖动最终将造成性能低下。

以上的这些并发症几乎存在于所有在如Apache这样的老旧网站服务架构中。因此,我们往往需要在丰富的通用特性集和优化服务器资源之间权衡利弊。

早期的nginx为了做一个专注于为动态扩展网站提供

  • 更高性能
  • 高密度
  • 更有效

的使用服务器资源的专业工具,因此使用了不一样的模型。

实际上,随着各操作系统上的高级事件驱动开发的发展,使得一个

  • 模块化
  • 事件驱动
  • 异步
  • 单线程
  • 无阻塞架构

成为了nginx源码的基础。

Nginx主要使用了多路复用事件通知方式,将各个任务分发给各个特定进程。所有的请求连接由数量有限的进程(被称为worker)处理,各个worker使用一个高效的单线程循环处理,每个worer进程每秒可处理上千个并发和请求。

代码结构

Nginx worker的代码包含核心功能模块

  • 核心负责维护一个严谨的处理循环,并且在请求处理的每个阶段执行对应的代码模块。
  • 模块负责大部分展示和应用层功能,包括从网络和存储设备读取、写入,内容转换、执行输出过滤,执行服务端嵌入(SSI)操作、以及在启用代理时转发请求给后端服务器。

得益于nginx模块化的架构,开发者可以在无需修改核心的基础上,对服务器的功能进行扩展。Nginx的典型模块并不多,分别是:

  • 核心模块
  • 事件模块
  • 阶段处理器
  • 协议、变量处理器
  • 过滤器
  • 上游和负载均衡器

目前,nginx并不支持动态加载模块,即模块代码是在编译核心代码时一起编译的。然而,动态加载模块和ABI已列入未来主要发布版本计划中。各个模块角色的详细信息可查看后面的nginx Internals。

Nginx通过使用事件通知机制和一系列提高IO的工具(在Linux、Solaris和基于BSD的操作系统上使用kqueue、epoll和event ports等技术),来处理大量操作如接受、处理和管理连接、检索内容。其目的在于尽可能的提示操作系统,对进出流量,磁盘操作,套接字读写操作,超时等事件及时异步地获取反馈。其中,多路复用与高级I/O操作的各种方式,对每个基于Unix操作系统运行的nginx都做了高度的优化。

图14.1展示了nginx架构的整体设计。
NAT

nginx-architecture.png-nginx架构图

Worker模型

如前面提到的,nginx不会为每个连接创建新的进程或线程。而是由worker进程各自通过共享“监听”socket来接收新请求,同时每个worker内部会使用一个高效的处理循环来处理上千个连接。在Nginx中,不存在特定的仲裁器或分发器用以分发连接给各个worker,这个工作由操作系统内核机制完成。服务启动时,一组监听socket被创建并完成初始化,workers进程不断地从这些套接字接受、读取HTTP请求和输出响应。

事件处理循环是nginx worker代码中最复杂的部分,它包含了全面的内部调用,并且高度依赖异步任务处理的思想。异步操作通过

  • 模块化
  • 事件通知
  • 大量使用回调函数
  • 高度调优的定时器

等实现。

总之,关键原则就是尽可能做到非阻塞。目前唯一还会引起Nginx阻塞的条件是woker的磁盘性能不足。

  • 由于nginx不为每个连接创建新进程或线程,故内存使用在大多场景中是传统并高效的。
  • 同时由于不用频繁创建-销毁进程或线程,nginx也很节省CPU时间。
  • Nginx所做的就是检查网络和存储的状态,初始化新连接、将其添加到主循环,异步处理直到完成,而后才从主循环中释放并删除。
  • 结合精心设计的系统调用、优雅实现诸如内存池等支持接口,nginx基本能够做到中低CPU使用率,即使在极端负载的情况下也不例外。

因为nginx创建了多个worker进程来处理连接,所以能够很好的利用多核CPU。通常一核一worker便能完全利用多核体系架构,并能够优雅的避免线程抖动与锁。这是因为在一个单线程?的worker进程内部不存在资源匮乏,并且资源控制机制也能很好的隔离。此外这个模型也允许

  • 在物理存储设备之间进行扩展
  • 提高磁盘利用率
  • 避免磁盘I/O导致的阻塞

总之,这种将工作负载分布到多个worker进程上的方式,最终能使服务器资源被更高效的利用。

nginx worker进程数应根据具体的磁盘使用和CPU负载的模式进行调整。具体实施方法比较简单,系统管理员往往是根据具体负载情况进行多次配置项的尝试。一般会推荐如下做法:

  • 如果负载模式是CPU密集型--如处理大量的TCP/IP协议,使用SSL,或者压缩数据--那么nginx worker进程数与CPU核数相当;
  • 如果是磁盘密集型--如从存储磁盘中读取多样化内容给客户端的服务,或包含大量的代理服务--此时worker的进程数应该是CPU核数的1.5~2倍。

一些工程师会基于独立存储单元的个数来选择worker进程数,然而该方法的有效性会受到磁盘存储类型和配置的影响。

解决如何避免由磁盘I/O引起的大部分阻塞,是Nginx开发者在未来版本中需要解决的主要问题之一。目前,针对某个woker的磁盘操作,如果没有足够的存储性能为其服务,该进程就可能会一直阻塞在磁盘读写操作上。当然,存在多种机制和配置指令文件用于缓解这类磁盘I/O阻塞的场景,典型的做法如结合一些像sendfile和异步IO指令,便可以为磁盘操作节省大量的空间。同时,在nginx的安装过程中,也应将数据集,可用内存数,以及底层存储架构来规划在内。

对嵌入脚本的支持有限,是当前的worker模型存在的另一个问题。例如,标准的nginx发布版本只支持Perl作为嵌入脚本语言。原因很简单:关键是嵌入脚本很可能会在任何操作上阻塞或者异常退出,而这两个行为都会直接导致worker进程挂住而同时影响数千个连接。与此相关的更多工作已列入Nginx开发计划,即将脚本更简单,更可靠地嵌入nginx并且更适合广泛应用。

Nginx进程角色

Nginx在内存中运行多个进程:

  • 一个单独master进程
  • 多个worker进程
  • 一些特殊用途的进程,如缓存加载进程、缓存管理进程

在nginx 1.x版本,所有进程都是单线程的,且主要使用共享内存机制作为进程间通信机制。除Master进程使用root用户权限运行外,缓存加载进程、缓存管理进程以及所有worker进程都使用非特权用户权限运行。

master进程负责下列工作:

  • 读取和验证配置文件
  • 创建、绑定、关闭sockets
  • 按照配置数量启动、终止、维护worker进程
  • 不中断服务的形式生效新配置文件
  • 不中断服务的形式升级程序(启动新版本且需要时回滚)
  • 重新打开日志文件
  • 编译嵌入Perl脚本

Worker进程负责:

  • 接受、处理来自客户端的连接
  • 提供反向代理和过滤功能
  • 提供其他nginx所具有的所有功能。

由于worker进程是web服务器每日操作的实际执行者,关于监控nginx实例,系统管理员实际还需要时刻关注worker进程。

缓存加载进程的主要职责:

  • 负责检查磁盘上的缓存数据
  • 维护缓存元数据的内存数据库

缓存加载进程负责管理缓存相关数据的载入和更新。Nginx的文件存于一个分配好的特定目录结构下,缓存加载进程负责管理这些文件,为nginx实例做准备,主要为:

  1. 会遍历目录
  2. 检查缓存内容的元数据
  3. 更新共享内存中的相关条目
  4. 当数据已整理干净并做好准备时,退出服务。

缓存管理进程主要负责缓存过期和失效。它在nginx正常操作时存在于内存中,在出现故障时由master进程重启。

Nginx缓存概述

Nginx的缓存基于文件系统使用分层数据存储实现,特点如下:

  • 缓存主键(key)可配置
  • 可使用不同特定请求参数来控制缓存内容
  • 缓存主键(key)和缓存元数据存储在共享内存段中,这样做的好处是缓存加载进程、缓存管理进程和worker进程都能访问到。
  • 目前不支持在内存中缓存文件,但可以用操作系统的虚拟文件系统机制进行优化。
  • 每个缓存的响应体存于文件系统的一个特定文件中,其中该存储的层次结构(层数及命名规则)由Nginx配置指令控制。
  • Nginx根据代理url的Md5值实现哈希算法,来实现获取某个已被缓存的响应体在该缓存目录结构中的文件名及路径。

其中,Nginx将数据内容放入缓存的过程如下:

  1. 在nginx从后端服务器读取响应时,该数据此时被写入到一个临时目录(非缓存文件目录)下的临时文件中。
  2. 当nginx完成该请求处理后,该临时文件被重命名并移动到缓存文件目录下。注意如果以上临时目录与缓存目录不属于同一个文件系统,则临时文件会被拷贝一次。所以建议将临时目录和缓存目录放在同一个文件系统中。

此外,管理缓存文件目录可使用如下方法:

  • 如果需要显示地清理缓存目录,从缓存目录结构下删除文件的行为是绝对安全的。
  • 可以使用一些nginx的第三方扩展远程控制缓存内容
  • 更多以上管理(远程or本地)缓存目录相关的工作,已被列入到主发布版的计划中。