nginx(发音为“engine x”)是一个由俄罗斯软件工程师Igor Sysoev编写的免费开源Web服务器。自2004年公开发布以来,nginx专注于高性能,高并发性和低内存使用。Web服务器功能之外的其他功能(如负载平衡,缓存,访问和带宽控制)以及与各种应用程序高效集成的功能,使nginx成为现代网站架构的理想选择。目前nginx是互联网上第二个最受欢迎的开源Web服务器。
14.1。为什么高并发性很重要?
这些天,互联网如此广泛和无处不在,十年前我们很难想象它并不完全在那里,就像我们所知道的那样。它从简单的HTML生成基于NCSA然后在Apache Web服务器上的可点击文本,到全球超过20亿用户始终使用的通信媒体。随着永久连接的PC,移动设备和最近的平板电脑的普及,互联网格局正在迅速变化,整个经济体已经成为数字布线。在线服务已经变得更加精细,明显偏向于即时可用的实时信息和娱乐。运行在线业务的安全方面也发生了重大变化。因此,网站现在比以前复杂得多,
网站设计师面临的最大挑战之一就是并发性。自Web服务开始以来,并发性水平一直在不断增长。一个受欢迎的网站为数十万甚至数百万同时在线的用户提供服务并不罕见。十年前,并发的主要原因是慢速客户端 - 使用ADSL或拨号连接的用户。现在,并发性是由移动客户端和较新的应用程序体系结构组合而成的,这些体系结构通常基于维持持久性连接,该连接允许客户端通过新闻,推文,朋友提要等进行更新。增加并发性的另一个重要因素是现代浏览器的行为改变,它可以同时连接四个到六个网站来提高网页加载速度。
为了说明慢速客户端的问题,设想一个简单的基于Apache的Web服务器,它会产生一个相对较短的100 KB响应 - 一个包含文本或图像的网页。生成或检索此页面可能只需几分之一秒,但需要10秒钟将其传输到带宽为80 kbps(10 KB / s)的客户端。从本质上讲,网络服务器会相对迅速地提取100 KB的内容,然后在释放连接之前,将该内容缓慢发送到客户端10秒钟就会忙碌起来。现在假设您有1,000个同时连接的客户请求了类似的内容。如果每个客户端只分配1 MB的额外内存,则会导致1000 MB(大约1 GB)的额外内存专门用于服务1000个客户端100 KB的内容。事实上,基于Apache的典型Web服务器通常每个连接分配超过1 MB的附加内存,令人遗憾的是几十Kbps通常仍然是移动通信的有效速度。尽管向缓慢客户端发送内容的情况在某种程度上可能会通过增加操作系统内核套接字缓冲区的大小而得到改善,但这并不是解决问题的通用解决方案,并且可能会产生不良副作用。
使用持久连接时,处理并发的问题更加突出,因为为了避免与建立新的HTTP连接相关的延迟,客户端将保持连接,并且对于每个连接的客户端,都有一定量的Web服务器分配的内存。
因此,为了处理与不断增长的受众相关的增加的工作量,并因此需要更高水平的并发性,并且能够持续这样做,网站应该基于一些非常有效的构建块。虽然硬件(CPU,内存,磁盘),网络容量,应用程序和数据存储体系结构等其他部分显然很重要,但在Web服务器软件中客户端连接已被接受和处理。因此,Web服务器应该能够随着每秒同时连接和请求数量的增长而非线性地进行伸缩。
不适合Apache吗?
目前仍主要支配互联网的网络服务器软件Apache,起源于20世纪90年代初。最初,它的架构与当时存在的操作系统和硬件相匹配,但也与互联网的状态相匹配,其中网站通常是运行单个Apache实例的独立物理服务器。到21世纪初,很明显独立的Web服务器模型不容易复制,以满足不断增长的Web服务的需求。虽然Apache为未来的开发提供了坚实的基础,但它的架构是为每个新连接创建一个副本,这不适合网站的非线性可伸缩性。最终,Apache成为一个通用的Web服务器,专注于具有许多不同的功能,各种第三方扩展,以及几乎适用于任何类型的Web应用程序开发。然而,没有价格就没有价值,因为每个连接的CPU和内存使用量增加,在单一软件中拥有如此丰富且通用的工具组合的缺点是可扩展性较差。
因此,当服务器硬件,操作系统和网络资源不再成为网站增长的主要制约因素时,世界各地的网络开发人员开始寻找更有效的网络服务器运行方式。大约十年前,着名软件工程师Daniel Kegel 宣称 “现在是Web服务器同时处理一万个客户端的时候了”,并预测了我们现在称之为Internet云服务的内容。Kegel的C10K清单引发了许多尝试,以解决Web服务器优化问题,以同时处理大量客户端,而nginx则成为最成功的客户端之一。
为了解决10,000个同时连接的C10K问题,nginx的编写思路是不同的架构 - 在同时连接数和每秒请求数都更适合非线性可伸缩性的情况下编写nginx。nginx是基于事件的,所以它不遵循Apache为每个网页请求产生新进程或线程的风格。最终的结果是,即使负载增加,内存和CPU使用率仍然可以管理。现在,nginx可以在具有典型硬件的服务器上提供数以万计的并发连接。
当nginx的第一个版本发布时,它意味着与Apache一起部署,以便像nginx一样处理静态内容,如HTML,CSS,JavaScript和图像,以卸载基于Apache的应用程序服务器的并发和延迟处理。在开发过程中,nginx通过使用FastCGI,uswgi或SCGI协议以及像memcached这样的分布式内存对象缓存系统,增加了与应用程序的集成 。其他有用的功能,如反向代理与负载平衡和缓存也被添加。这些附加功能将nginx整合为一个有效的工具组合,以构建可扩展的Web基础架构。
2012年2月,Apache 2.4.x分支向公众发布。虽然这个最新版本的Apache增加了新的多处理核心模块和旨在增强可伸缩性和性能的新代理模块,但现在还不能确定其性能,并发性和资源利用率现在是否与纯事件相当或更好驱动的Web服务器。但是,看到Apache应用程序服务器在新版本中可以更好地扩展,这将非常好,因为它可以缓解后端的瓶颈问题,这在典型的nginx-plus-Apache web配置中仍然没有解决。
使用nginx有更多优点吗?
以高性能和高效率处理高并发性一直是部署nginx的关键好处。但是,现在有更多有趣的好处。
在过去的几年中,Web架构师已经接受了将应用程序基础结构与Web服务器分离并分离的想法。但是,之前以LAMP(Linux,Apache,MySQL,PHP,Python或Perl)为基础的网站的形式现在可能不仅仅是一个基于LEMP的网站(“E”代表`Engine x')。 ,但越来越多地将Web服务器推向基础设施边缘,并以不同的方式在其周围集成相同或改进的一组应用程序和数据库工具。
nginx非常适合这一点,因为它提供了方便卸载并发性,延迟处理,SSL(安全套接字层),静态内容,压缩和缓存,连接和请求限制以及甚至HTTP应用程序流媒体传输所需的关键功能层到更高效的边缘Web服务器层。它还允许直接与memcached / Redis或其他“NoSQL”解决方案集成,以在为大量并发用户提供服务时提高性能。
随着最近的开发工具包和编程语言广泛使用,越来越多的公司正在改变他们的应用程序开发和部署习惯。nginx已经成为这些不断变化的范例中最重要的组成部分之一,并且它已经帮助很多公司在其预算内快速开发和开发他们的Web服务。
nginx的第一行是在2002年编写的。2004年,它使用两个条款的BSD许可证向公众发布。从那时起,nginx用户的数量一直在增长,提出了一些想法,并提交了对整个社区极为有用和有益的错误报告,建议和观察结果。
nginx代码库是原创的,完全是从头开始用C编程语言编写的。nginx已被移植到许多体系结构和操作系统,包括Linux,FreeBSD,Solaris,Mac OS X,AIX和Microsoft Windows。nginx拥有自己的库,除了zlib,PCRE和OpenSSL之外,它的标准模块除了系统的C库外没有太多用处,如果不需要,或者由于潜在的许可证冲突,它们可以选择从构建中排除。
有关Windows版本nginx的几句话。虽然nginx在Windows环境下工作,但Windows版本的nginx更像是一个概念验证,而不是一个功能齐全的端口。nginx和Windows内核体系结构在目前不能很好地交互。Windows的nginx版本的已知问题包括少得多的并发连接,性能下降,无缓存和无带宽管制。未来版本的nginx for Windows将更贴近主流功能。
14.2。nginx体系结构概述
处理并发连接的传统的基于进程或线程的模型涉及用单独的进程或线程来处理每个连接,并阻止网络或输入/输出操作。根据应用程序的不同,内存和CPU消耗可能非常低下。产生一个单独的进程或线程需要准备一个新的运行时环境,包括分配堆和堆栈内存,以及创建新的执行上下文。额外的CPU时间也用于创建这些项目,由于过度的上下文切换导致线程抖动,最终可能导致性能较差。所有这些并发症都表现在Apache等早期的Web服务器体系结构中。这是提供丰富的通用功能和优化服务器资源使用之间的一种折衷。
从一开始,nginx就意味着成为一个专门的工具,以实现服务器资源的更高性能,更高密度和更经济的使用,同时实现网站的动态增长,因此它采用了不同的模式。实际上,它受到各种操作系统中基于事件的高级机制的持续开发的启发。结果是一个模块化的,事件驱动的异步单线程非阻塞体系结构,它成为nginx代码的基础。
nginx大量使用复用和事件通知,并将特定任务分配给独立的进程。连接在一个称为worker
s 的有限数量的单线程进程中以高效运行循环进行处理。在每个worker
nginx中,每秒可以处理数千个并发连接和请求。
代码结构
nginx worker
代码包含核心和功能模块。nginx的核心是负责维护一个严格的运行循环,并在每个请求处理阶段执行适当的模块代码部分。模块构成了大部分演示和应用层功能。模块读取和写入网络和存储,转换内容,执行出站筛选,应用服务器端包含操作,并在代理激活时将请求传递给上游服务器。
nginx的模块化体系结构通常允许开发人员在不修改nginx内核的情况下扩展一组web服务器功能。nginx模块有着稍微不同的化身,即核心模块,事件模块,阶段处理程序,协议,变量处理程序,过滤器,上游和负载平衡器。目前,nginx不支持动态加载的模块; 即,模块在构建阶段与核心一起编译。但是,计划在未来的主要版本中支持可加载模块和ABI。有关不同模块角色的更多详细信息,请参见 第14.4节。
同时处理各种具有接受,处理和管理网络的连接和内容检索相关联的动作,nginx的使用事件通知机制和许多基于BSD磁盘I /在Linux,Solaris和O性能增强的操作系统,等等kqueue
,epoll
和event ports
。目标是尽可能多地向操作系统提供提示,以便及时获取入站和出站流量的异步反馈,磁盘操作,读取或写入套接字,超时等。针对多路复用和高级I / O操作使用不同的方法,针对每个运行于其上的基于Unix的操作系统进行了大量优化。
图14.1给出了nginx体系结构的高级概述 。
图14.1:nginx架构图
工人模型
如前所述,nginx不会为每个连接生成一个进程或线程。相反,worker
进程接受来自共享“监听”套接字的新请求,并在每个套接字中执行高效的运行循环,worker
以便每个处理数千个连接worker
。worker
在nginx中没有专门的仲裁或连接到s 的连接。这项工作是由OS内核机制完成的。启动时,会创建一组初始侦听套接字。worker
然后在处理HTTP请求和响应时不断接受,读取和写入套接字。
运行循环是nginx worker
代码中最复杂的部分。它包含全面的内部调用,并且非常依赖于异步任务处理的思想。异步操作通过模块化,事件通知,回调函数的广泛使用和微调定时器来实现。总的来说,关键原则是尽可能不阻塞。nginx仍然可以阻塞的唯一情况是当worker
进程没有足够的磁盘存储性能时。
因为nginx不会为每个连接分配进程或线程,所以在绝大多数情况下,内存使用非常保守并且非常高效。nginx也可以节省CPU周期,因为进程或线程没有持续的创建 - 破坏模式。nginx做的是检查网络和存储的状态,初始化新连接,将它们添加到运行循环中,并异步处理直到完成,此时连接被解除分配并从运行循环中删除。结合仔细使用 syscall
s和支持接口(如pool和slab内存分配器)的准确实现,nginx即使在极端工作负载下也能实现中等至较低的CPU使用率。
因为nginx产生了几个worker
s来处理连接,所以它可以在多个内核之间很好地扩展。通常,worker
每个内核单独使用可以充分利用多核架构,并且可以防止线程抖动和锁定。没有资源匮乏,并且资源控制机制在单线程worker
进程中被孤立。此模型还允许跨物理存储设备提供更高的可扩展性,有助于提高磁盘利用率并避免磁盘I / O阻塞。因此,可以更高效地利用服务器资源,并在多个工作人员之间共享工作负载。
对于一些磁盘使用和CPU负载模式,worker
应调整nginx 的数量 。这些规则在这里有点基本,系统管理员应该为他们的工作负载尝试一些配置。一般建议可能如下:如果负载模式是CPU密集型的 - 例如,处理大量TCP / IP,执行SSL或压缩--nginx worker
的数量应该与CPU内核的数量相匹配; 如果负载主要是磁盘I / O限制 - 例如,从存储提供不同的内容集,或者大量代理 - worker
s 的数量可能是核心数量的1.5倍到2倍。一些工程师会worker
根据单个存储单元的数量来选择 s的数量,但这种方法的效率取决于磁盘存储的类型和配置。
nginx的开发人员将在即将到来的版本中解决的一个主要问题是如何避免磁盘I / O上的大部分阻塞。目前,如果没有足够的存储性能来为特定的磁盘操作提供服务worker
,这worker
可能仍然会阻止从磁盘读取/写入。存在许多机制和配置文件指令来缓解这种磁盘I / O阻塞情况。最值得注意的是,诸如sendfile和AIO等选项的组合通常会为磁盘性能产生很大的空间。应该根据数据集,可用于nginx的内存量以及底层存储体系结构计划nginx安装。
现有worker
模型的另一个问题与嵌入式脚本的有限支持有关。首先,使用标准的nginx发行版,只支持嵌入Perl脚本。对此有一个简单的解释:关键问题是嵌入式脚本可能会阻止任何操作或意外退出。这两种行为都会立即导致工人被挂起的情况,一次影响数千个连接。计划进行更多的工作,使nginx的嵌入式脚本更简单,更可靠,适用于更广泛的应用。
nginx进程角色
nginx在内存中运行多个进程; 有一个主进程和几个worker
进程。还有一些特殊用途的进程,特别是缓存加载器和缓存管理器。所有进程在nginx的1.x版本中都是单线程的。所有进程主要使用共享内存机制进行进程间通信。主进程以root
用户身份运行 。缓存加载器,缓存管理器和worker
s作为非特权用户运行。
主进程负责以下任务:
- 读取和验证配置
- 创建,绑定和关闭套接字
- 开始,终止和维护配置的
worker
进程 数量 - 重新配置而不会中断服务
- 控制不间断的二进制升级(如果需要,启动新的二进制和回滚)
- 重新打开日志文件
- 编译嵌入式Perl脚本
这些worker
进程接受,处理和处理来自客户端的连接,提供反向代理和过滤功能,并完成nginx所能做的其他任何事情。在监视nginx实例的行为方面,系统管理员应该关注worker
s,因为它们是反映Web服务器实际日常操作的过程。
缓存加载器进程负责检查磁盘缓存项目并用缓存元数据填充nginx的内存数据库。从本质上讲,缓存加载器准备nginx实例来处理已经存储在磁盘中的文件在一个专门分配的目录结构中。它遍历目录,检查缓存内容元数据,更新共享内存中的相关条目,然后在所有内容都干净并准备好使用时退出。
缓存管理器主要负责缓存过期和失效。它在正常的nginx操作期间保留在内存中,并在故障情况下由主进程重新启动。
nginx缓存简介
nginx中的缓存是以文件系统上的分层数据存储的形式实现的。缓存键是可配置的,并且可以使用不同的请求特定参数来控制进入缓存的内容。缓存键和缓存元数据存储在缓存加载器,缓存管理器和worker
s可以访问的共享内存段中。目前,除了操作系统的虚拟文件系统机制所暗示的优化以外,没有任何文件内存中的缓存。每个缓存的响应都放置在文件系统的不同文件中。层次结构(层次和命名细节)通过nginx配置指令进行控制。将响应写入缓存目录结构时,文件的路径和名称将从代理URL的MD5哈希派生而来。
将内容放入缓存的过程如下所示:当nginx从上游服务器读取响应时,首先将内容写入缓存目录结构之外的临时文件。当nginx完成处理请求时,它会重命名临时文件并将其移至缓存目录。如果代理的临时文件目录位于另一个文件系统上,则该文件将被复制,因此建议将临时目录和高速缓存目录保留在同一个文件系统上。当需要显式清除缓存目录结构中的文件时,也很安全。有nginx的第三方扩展使得可以远程控制缓存的内容,并计划将更多的工作集成到主发行版中。
14.3。nginx配置
nginx的配置系统受到Igor Sysoev与Apache经验的启发。他的主要见解是可扩展的配置系统对于Web服务器而言至关重要。在维护具有大量虚拟服务器,目录,位置和数据集的大型复杂配置时遇到了主要的扩展问题。在相对较大的网页设置中,如果在应用程序级别和系统工程师本人都不能正确完成,那么这可能是一场噩梦。
因此,nginx配置旨在简化日常操作并为进一步扩展Web服务器配置提供简便的方法。
nginx配置保存在通常驻留在/usr/local/etc/nginx
或 中的许多纯文本文件中/etc/nginx
。主配置文件通常被调用nginx.conf
。为了保持整洁,部分配置可以放在单独的文件中,这些文件可以自动包含在主文件中。但是,这里应该注意的是,nginx目前不支持Apache风格的分布式配置(即 .htaccess
文件)。所有与nginx Web服务器行为相关的配置都应驻留在一组集中的配置文件中。
配置文件最初由主进程读取并验证。一个已编译的只读形式的nginx配置可用于worker
进程,因为它们是从主进程派生出来的。配置结构由通常的虚拟内存管理机制自动共享。
nginx的配置有几种不同的情况下进行main
, http
,server
,upstream
,location
(也 mail
用于邮件代理)指令块。上下文不会重叠。例如,不存在将指令块放入 location
块中的main
情况。另外,为了避免不必要的歧义,没有任何东西像“全球网络服务器”配置。nginx配置意味着干净而合乎逻辑,允许用户维护包含数千条指令的复杂配置文件。在一次私人对话中,Sysoev说:“全局服务器配置中的位置,目录和其他块是我在Apache中从未喜欢的功能,所以这就是为什么它们从未在nginx中实现过的原因。”
配置语法,格式和定义遵循所谓的C风格约定。这种制作配置文件的特殊方法已经被各种开源和商业软件应用程序所使用。通过设计,C风格的配置非常适合嵌套的描述,逻辑性强,易于创建,读取和维护,并且受到许多工程师的青睐。nginx的C风格配置也可以轻松实现自动化。
尽管一些nginx指令类似于Apache配置的某些部分,但设置nginx实例则是一种非常不同的体验。例如,nginx支持重写规则,尽管它需要管理员手动调整旧版Apache重写配置以匹配nginx样式。重写引擎的实现也不同。
一般来说,nginx设置还提供了对几种原始机制的支持,这些机制作为精简Web服务器配置的一部分非常有用。简单提一下变量和try_files
指令是有意义的 ,这对nginx来说有点独特。nginx中的变量是为了提供一个额外的甚至更强大的机制来控制Web服务器的运行时配置。变量经过优化以进行快速评估,并在内部预编译为索引。根据需求进行评估; 即,变量的值通常只计算一次,并在特定请求的生命周期内缓存。变量可以与不同的配置指令一起使用,为描述条件请求处理行为提供了额外的灵活性。
该try_files
指令最初意图以if
更适当的方式逐步替换条件配置语句,并且其目的是快速和有效地尝试/匹配不同的URI到内容映射。总的来说,该 try_files
指令运作良好,可以非常有效和有用。建议读者彻底检查 try_files
指令并在适用的时候采用它。
14.4。nginx内部
如前所述,nginx代码库由一个核心和一些模块组成。nginx的核心是负责提供Web服务器,Web和邮件反向代理功能的基础; 它支持使用底层网络协议,建立必要的运行时环境,并确保不同模块之间的无缝交互。但是,大多数协议和应用程序特定的功能都是由nginx模块完成的,而不是核心。
在内部,nginx通过管道或模块链来处理连接。换句话说,每个操作都有一个模块正在做相关的工作; 例如压缩,修改内容,执行服务器端包含,通过FastCGI或uwsgi协议与上游应用程序服务器通信或与memcached通信。
有几个nginx模块位于核心和真正的“功能”模块之间。这些模块 http
和mail
。这两个模块为核心组件和低级组件提供了额外的抽象级别。在这些模块中,实现了与诸如HTTP,SMTP或IMAP的相应应用层协议相关联的事件序列的处理。结合nginx内核,这些高级模块负责维护对各个功能模块的调用的正确顺序。虽然HTTP协议当前是作为http
模块的一部分实现的,但由于需要支持SPDY等其他协议,因此有计划将其分成将来的功能模块(请参阅“SPDY:快速网络的实验协议 “)。
功能模块可以分为事件模块,阶段处理程序,输出过滤器,变量处理程序,协议,上游和负载平衡器。大多数这些模块补充了nginx的HTTP功能,尽管也使用了事件模块和协议mail
。事件模块提供特定的与操作系统相关的事件通知机制,如kqueue
或epoll
。nginx使用的事件模块取决于操作系统功能和构建配置。协议模块允许nginx通过HTTPS,TLS / SSL,SMTP,POP3和IMAP进行通信。
典型的HTTP请求处理周期如下所示。
- 客户端发送HTTP请求。
- nginx核心根据匹配请求的配置位置选择适当的阶段处理程序。
- 如果配置为这样,负载均衡器会选择上游服务器进行代理。
- 阶段处理程序执行其任务并将每个输出缓冲区传递给第一个过滤器。
- 第一个过滤器将输出传递给第二个过滤器。
- 第二个筛选器将输出传递给第三个(依此类推)。
- 最终答复发送给客户。
nginx模块调用是非常可定制的。它通过一系列使用指向可执行函数指针的回调来执行。然而,这样做的缺点是可能会给想要编写自己的模块的程序员带来很大的负担,因为他们必须准确定义模块应该如何以及何时运行。nginx API和开发人员的文档都得到了改进,并使其更容易获得缓解。
一些模块可以附加的例子有:
- 在配置文件被读取和处理之前
- 对于位置和所在服务器的每个配置指令
- 主配置初始化时
- 当服务器(即主机/端口)初始化时
- 当服务器配置与主配置合并时
- 当位置配置初始化或与其父服务器配置合并时
- 主进程启动或退出时
- 新工作进程启动或退出时
- 处理请求时
- 筛选响应头和主体时
- 挑选,启动并重新启动对上游服务器的请求时
- 处理来自上游服务器的响应时
- 完成与上游服务器的交互时
在a中worker
,导致产生响应的运行循环的动作序列如下所示:
- 开始
ngx_worker_process_cycle()
。 - 使用操作系统特定的机制处理事件(如
epoll
或kqueue
)。 - 接受事件并发送相关操作。
- 进程/代理请求标头和正文。
- 生成响应内容(标题,正文)并将其流式传输到客户端。
- 完成请求。
- 重新初始化定时器和事件。
运行循环本身(步骤5和6)确保增量生成响应并将其流式传输到客户端。
处理HTTP请求的更详细视图可能如下所示:
- 初始化请求处理。
- 处理标题。
- 过程体。
- 调用关联的处理程序。
- 贯穿处理阶段。
这给我们带来了阶段。当nginx处理一个HTTP请求时,它会通过一些处理阶段。在每个阶段都有处理者要打电话。通常,阶段处理程序处理请求并生成相关输出。阶段处理程序附加到配置文件中定义的位置。
阶段处理程序通常执行四项任务:获取位置配置,生成适当的响应,发送标题并发送主体。处理程序有一个参数:描述请求的特定结构。请求结构有很多关于客户端请求的有用信息,例如请求方法,URI和头。
当读取HTTP请求标头时,nginx会查看关联的虚拟服务器配置。如果找到虚拟服务器,请求将经历六个阶段:
- 服务器重写阶段
- 位置阶段
- 位置重写阶段(这可以使请求返回到前一阶段)
- 访问控制阶段
- try_files阶段
- 登录阶段
为了响应请求生成必要的内容,nginx将请求传递给合适的内容处理程序。根据确切位置的配置,nginx的可先试用所谓的无条件处理,如perl
, proxy_pass
,flv
,mp4
,等。如果请求不符合上述任何内容处理程序,它由以下处理器之一采摘,在这个确切订购:random index
, index
,autoindex
,gzip_static
,static
。
索引模块的详细信息可以在nginx文档中找到,但是这些是用尾部斜线处理请求的模块。如果一个特定的模块是mp4
或者autoindex
不适合的话,这个内容被认为只是一个磁盘上的文件或目录(即静态的)并且由static
内容处理程序提供服务。对于目录,它会自动重写URI,以便尾随斜线始终存在(然后发出HTTP重定向)。
内容处理程序的内容然后传递给过滤器。过滤器也附加到位置,并且可以为某个位置配置多个过滤器。过滤器负责处理由处理程序生成的输出。过滤器执行的顺序在编译时确定。对于预先定义的即用型过滤器,对于第三方过滤器,可以在构建阶段对其进行配置。在现有的nginx实现中,过滤器只能进行出站更改,目前还没有机制可以编写和附加过滤器来执行输入内容转换。输入过滤将出现在未来版本的nginx中。
过滤器遵循特定的设计模式。过滤器被调用,开始工作,并调用下一个过滤器,直到链中的最终过滤器被调用。之后,nginx完成响应。过滤器不必等待前一个过滤器完成。一旦链中的下一个过滤器可以在来自前一个过滤器的输入可用时开始自己的工作(功能上与Unix管道非常相似)。反过来,在收到来自上游服务器的整个响应之前,正在生成的输出响应可以传递给客户端。
有标题过滤器和身体过滤器; nginx将响应的头部和主体分别提供给关联的过滤器。
标题过滤器由三个基本步骤组成:
- 决定是否在此响应中运作。
- 根据回复进行操作。
- 调用下一个过滤器。
身体过滤器转换生成的内容。身体过滤器的例子包括:
- 服务器端包含
- XSLT过滤
- 图像过滤(例如,即时调整图像大小)
- 字符集修改
-
gzip
压缩 - 分块编码
在过滤器链之后,响应被传递给作者。除了作者之外,还有一些额外的专用滤波器,即copy
滤波器和postpone
滤波器。该copy
过滤器是负责填充内存缓冲区有可能被存储在一个代理临时目录相关的响应内容。该postpone
过滤器用于子请求。
子请求是请求/响应处理的非常重要的机制。子请求也是nginx最强大的方面之一。通过子请求,nginx可以从不同于客户端最初请求的URL返回结果。一些网络框架将此称为内部重定向。但是,nginx更进一步 - 不仅过滤器可以执行多个子请求并将输出组合为单个响应,而且子请求也可以嵌套和分层。子请求可以执行其自己的子子请求,并且子子请求可以启动子子子请求。子请求可以映射到硬盘,其他处理程序或上游服务器上的文件。子请求对于根据原始响应中的数据插入其他内容非常有用。例如,include
指令与指定的URL的内容。或者,它可以是制作过滤器的一个示例,该过滤器将文档的全部内容视为要检索的URL,然后将新文档附加到URL本身。
上游和负载平衡器也值得简要介绍。上游用于实现可识别为反向代理(proxy_pass
处理程序)的内容处理程序。上游模块主要准备将请求发送到上游服务器(或“后端”)并接收来自上游服务器的响应。这里没有呼叫输出过滤器。当上游服务器准备好写入和读取时,上游模块确切地设置回调被调用。实现以下功能的回调函数存在:
- 设计一个请求缓冲区(或它们的链)以发送到上游服务器
- 重新初始化/重置到上游服务器的连接(在再次创建请求之前发生)
- 处理上游响应的第一位并保存从上游服务器接收到的有效载荷的指针
- 中止请求(当客户端过早终止时发生)
- nginx完成从上游服务器读取时完成请求
- 修剪响应主体(例如移除预告片)
负载均衡器模块附加到proxy_pass
处理程序,以便在多个上游服务器符合条件时提供选择上游服务器的能力。负载均衡器注册启用配置文件指令,提供额外的上游初始化函数(解析DNS中的上游名称等),初始化连接结构,决定将请求路由到何处以及更新统计信息。目前,nginx支持两种标准的学科用于向上游服务器进行负载平衡:循环和ip-hash。
上行和负载均衡处理机制包括检测失败的上行服务器和将新请求重新路由到剩余服务器的算法 - 尽管计划增加许多额外工作来增强此功能。通常,计划负载平衡器方面的工作将更多,而在下一版本的nginx中,负载分布在不同上游服务器以及健康状况检查的机制将大大改善。
还有一些其他有趣的模块提供了一组用于配置文件的变量。虽然nginx中的变量是在不同的模块中创建和更新的,但有两个模块完全专用于变量:geo
和map
。该geo
模块用于帮助客户根据IP地址进行跟踪。该模块可以创建取决于客户端IP地址的任意变量。另一个模块map
允许从其他变量创建变量,本质上提供了灵活映射主机名和其他运行时变量的能力。这种模块可能被称为变量处理器。
在单个nginx worker
中实现的内存分配机制在 某种程度上受Apache的启发。对nginx内存管理的高级描述如下:对于每个连接,必要的内存缓冲区都是动态分配,链接的,用于存储和操作请求和响应的头和主体,然后在连接释放后释放。值得注意的是,nginx尽量避免在内存中复制数据,大部分数据都是通过指针值传递的,而不是通过调用memcpy
。
再深入一点,当模块生成响应时,检索到的内容将放入内存缓冲区,然后添加到缓冲链链接。后续处理也适用于此缓冲链链接。缓冲链在nginx中相当复杂,因为根据模块类型的不同,有多种处理方案会有所不同。例如,在实现身体过滤器模块时精确地管理缓冲区可能非常棘手。这种模块可以一次只能在一个缓冲区(链条)操作,它必须决定是否覆盖输入缓冲器,与新分配的缓冲区更换缓冲,或前或有问题的缓冲后插入一个新的缓冲区。使事情复杂化,有时一个模块会接收多个缓冲区,因此它有一个不完整的缓冲区链,它必须运行。然而,
关于上述方法的一个注意事项是,在连接的整个生命周期内都分配了内存缓冲区,因此对于长时间的连接,会保留一些额外的内存。与此同时,在一个空闲的keepalive连接上,nginx只花费550字节的内存。未来版本的nginx可能的优化将是重用和共享内存缓冲区的长期连接。
管理内存分配的任务由nginx池分配器完成。共享内存区域用于接受互斥锁,缓存元数据,SSL会话缓存以及与带宽管制和管理(限制)相关的信息。在nginx中实现了一个slab分配器来管理共享内存分配。为了同时安全地使用共享内存,可以使用多种锁定机制(互斥锁和信号量)。为了组织复杂的数据结构,nginx还提供了一个红黑树实现。红黑树用于将缓存元数据保存在共享内存中,跟踪非正则表达式位置定义以及其他一些任务。
不幸的是,上述所有内容都没有以一致和简单的方式描述,使得为nginx开发第三方扩展的工作相当复杂。虽然存在,但关于nginx的内部一些好的文件例如,那些由埃文·米勒这样的文件制作需要巨大的逆向工程的努力,和Nginx的模块的实现仍是许多黑色的艺术。
尽管与第三方模块开发相关的某些困难,nginx用户社区最近看到了很多有用的第三方模块。例如,有一个用于nginx的嵌入式Lua解释器模块,用于负载均衡的其他模块,完整的WebDAV支持,高级缓存控制以及本章作者鼓励并将在未来支持的其他有趣的第三方工作。
14.5。得到教训
当Igor Sysoev开始编写nginx时,大多数支持互联网的软件已经存在,而且这种软件的架构通常遵循传统服务器和网络硬件,操作系统以及一般旧互联网架构的定义。然而,这并不妨碍伊戈尔认为他可能能够改善网络服务器领域的事情。所以,虽然第一课似乎很明显,但它是这样的:总是有改进的空间。
考虑到更好的网络软件,Igor花了很多时间开发初始代码结构,研究优化各种操作系统代码的不同方法。十年之后,他正在开发一个nginx版本2.0的原型,并考虑到了第一版的积极开发年。显然,新架构的初始原型和初始代码结构对于未来的一个软件产品。
另一点值得一提的是,发展应该集中。Windows版本的nginx可能是一个很好的例子,说明如何避免在开发人员的核心竞争力和目标应用程序方面对开发工作进行稀释。它同样适用于多次尝试增强nginx以提供更多功能以便与现有传统设置向后兼容的重写引擎。
最后但并非最不重要的一点,值得一提的是,尽管nginx开发者社区不是很大,但第三方模块和nginx的扩展一直是它受欢迎的非常重要的部分。Evan Miller,Piotr Sikora,Valery Kholodkov,Zhang Yichun(agentzh)和其他有才华的软件工程师所做的工作得到了nginx用户社区及其原始开发人员的高度赞赏。