处理并发连接的传统的基于进程或线程的模型涉及使用单独的进程或线程处理每个连接,并阻止网络或输入/输出操作。 根据应用,在内存和CPU消耗方面可能非常低效。 产生一个单独的进程或线程需要准备一个新的运行时环境,包括分配堆和堆栈内存,以及创建新的执行上下文。 额外的CPU时间也用于创建这些项目,这可能会导致由于线程在过多的上下文切换上的转机而导致性能下降。 所有这些并发症都表现在较老的Web服务器架构(如Apache)中。 这是提供丰富的一般应用功能和优化的服务器资源使用之间的一个折衷。
从一开始nginx就是一个专门的工具,可以实现更高性能,更密集和经济地使用服务器资源,同时实现网站的动态发展,所以它采用了不同的模式。 它实际上受到各种操作系统中高级事件机制的不断发展的启发。发展结果变成是一个模块化的,事件驱动的,异步的,单线程的非阻塞架构的nginx代码基础。
nginx大量使用复用和事件通知,并专门用于分离进程的特定任务。 连接在有限数量的单线程进程称为工作(worker
)的高效运行循环中处理。 在每个工作(worker
)中,nginx可以处理每秒数千个并发连接和请求。
nginx工作(worker
)码包括核心和功能模块。 nginx的核心是负责维护严格的运行循环,并在请求处理的每个阶段执行模块代码的适当部分。 模块构成了大部分的演示和应用层功能。 模块读取和写入网络和存储,转换内容,执行出站过滤,应用服务器端包含操作,并在代理启动时将请求传递给上游服务器。
nginx的模块化架构通常允许开发人员扩展一组Web服务器功能,而无需修改nginx内核。 nginx模块略有不同,即核心模块,事件模块,阶段处理程序,协议,可变处理程序,过滤器,上游和负载平衡器。nginx不支持动态加载的模块; 即在构建阶段将模块与核心一起编译。
在处理与接受,处理和管理网络连接和内容检索相关的各种操作时,nginx在基于Linux,Solaris和BSD的操作系统中使用事件通知机制和一些磁盘I/O性能增强,如:kqueue,epoll, 和事件端口。 目标是为操作系统提供尽可能多的提示,以便及时获取入站和出站流量,磁盘操作,读取或写入套接字,超时等异步反馈。 对于每个基于Unix的nginx运行的操作系统,大量优化了复用和高级I/O操作的不同方法的使用。
nginx架构的高级概述如下图所示 -
如前所述,nginx不会为每个连接生成一个进程或线程。 相反,工作(worker
)进程接受来自共享“listen”套接字的新请求,并在每个工作(worker
)内执行高效的运行循环,以处理每个工作(worker
)中的数千个连接。 没有专门的仲裁或分配与nginx工作(worker
)的联系; 这个工作(worker
)是由操作系统内核机制完成的。 启动后,将创建一组初始侦听套接字。 然后,工作(worker
)在处理HTTP请求和响应时不断接受,读取和写入套接字。
运行循环是nginx工作(worker
)代码中最复杂的部分。 它包括全面的内部调用,并且在很大程度上依赖异步任务处理的想法。 异步操作通过模块化,事件通知,广泛使用回调函数和微调定时器来实现。 总体而言,关键原则是尽可能不阻塞。 nginx仍然可以阻塞的唯一情况是工作(worker
)进程没有足够的磁盘存储。
由于nginx不会连接一个进程或线程,所以在绝大多数情况下,内存使用非常保守,非常有效。 nginx也节省CPU周期,因为进程或线程没有持续的创建 - 销毁模式。 nginx的作用是检查网络和存储的状态,初始化新连接,将其添加到运行循环中,并异步处理直到完成,此时连接被重新分配并从运行循环中删除。 结合仔细使用系统调用(syscall
)和精确实现支持接口(如pool
和slab
内存分配器),nginx通常可以在极端工作负载下实现中到低的CPU使用。
在一些磁盘使用和CPU负载模式,应调整nginx工作(worker
)的数量。 在这里说一点基础规则:系统管理员应该为其工作负载尝试几个配置。 一般建议可能如下:如果负载模式是CPU密集型的,例如,处理大量TCP/IP,执行SSL或压缩,则nginx工作(worker
)的数量应与CPU内核数量相匹配; 如果负载大多是磁盘I/O绑定,例如,从存储或重代理服务不同的内容集合 - 工作(worker
)的数量可能是核心数量的一到两倍。有些工程师会根据个人存储单元的数量选择工作(worker
)的数量,但这种方法的效率取决于磁盘存储的类型和配置。
nginx的开发人员将在即将推出的版本中解决的一个主要问题是如何避免磁盘I/O上的大多数阻塞。 目前,如果没有足够的存储性能来提供特定工作(worker
)生成的磁盘操作,该工作(worker
)可能仍然阻止从磁盘读取/写入。 存在许多机制和配置文件指令来减轻此类磁盘I/O阻塞情况。要注意的是,诸如:sendfile和AIO之类的选项的组合通常会为磁盘性能带来很大的余量。 应该根据数据集,可用于nginx的内存量和底层存储架构来规划安装一个nginx服务器。
现有工作(worker
)模式的另一个问题是与嵌入式脚本的有限支持有关。 一个使用标准的nginx分发,只支持嵌入Perl脚本。一个简单的解释:关键问题是嵌入式脚本阻止任何操作或意外退出的可能性。 这两种类型的行为将立即导致工作(worker
)挂起的情况,同时影响到数千个连接。 更多的工作(worker
)计划是使nginx的嵌入式脚本更简单,更可靠,适用于更广泛的应用。
nginx在内存中运行多个进程; 有一个主进程和几个工作(worker
)进程。 还有一些特殊用途的过程,特别是缓存加载器和缓存管理器。 所有进程都是单线程版本为1.x
的nginx。 所有进程主要使用共享内存机制进行进程间通信。主进程作为root
用户运行。 缓存加载器,缓存管理器和工作(worker
)则以无权限用户运行。
主程序负责以下任务:
worker
)进程数工作(worker
)进程接受,处理和处理来自客户端的连接,提供反向代理和过滤功能,并执行几乎所有其他的nginx能力。 关于监视nginx实例的行为,系统管理员应该关注工作(worker
)进程,因为它们是反映Web服务器实际日常操作的过程。
缓存加载器进程负责检查磁盘缓存项目,并使用缓存元数据填充nginx的内存数据库。 本质上,缓存加载器准备nginx实例来处理已经存储在磁盘上的特定分配的目录结构中的文件。 它遍历目录,检查缓存内容元数据,更新共享内存中的相关条目,然后在所有内容清洁并准备使用时退出。
缓存管理器主要负责缓存到期和无效。 在正常的nginx操作期间它保持在内存中,并且在失败的情况下由主进程重新启动。
在nginx中的缓存以文件系统上的分层数据存储的形式实现。 缓存密钥是可配置的,并且可以使用不同的请求特定参数来控制进入缓存的内容。 缓存密钥和缓存元数据存储在共享存储器段中,高速缓存加载器,缓存管理器和工作(worker
)可以访问它们。 目前,除了操作系统的虚拟文件系统机制暗示的优化之外,没有任何内存中的文件缓存。 每个缓存的响应都放在文件系统上的不同文件中。 层次结构(级别和命名细节)通过nginx配置指令进行控制。 当响应写入缓存目录结构时,文件的路径和名称将从代理URL的MD5哈希导出。
将内容放置在缓存中的过程如下:当nginx从上游服务器读取响应时,内容首先写入缓存目录结构之外的临时文件。 当nginx完成处理请求时,它重命名临时文件并将其移动到缓存目录。 如果用于代理的临时文件目录位于另一个文件系统上,则该文件将被复制,因此建议将临时文件目录和缓存目录保存在同一文件系统上。 当需要显式清除缓存目录结构时,从文件中删除文件也是非常安全的。 nginx有第三方扩展,可以远程控制缓存的内容,还有更多的工作计划将此功能集成到主分发中。