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