1. 为什么高并发重要?
这些天来,互联网是如此的普遍,无处不在,这和我们十年前知道的完全不一样的。它极大地演变,从简单的HTML产生点击文字,基于NCSA然后是Apache Web服务器,到一个由全球超过2亿用户使用的一直在线的通信介质。永久连接的个人电脑,移动设备和最近的平板,因特网景观迅速的变化和整个经济体已成为数字化。在线服务有明显的偏向立即可用的实时信息和娱乐。在线业务安全运行方面也有显着的改变。因此,网站现在比以前要复杂得多,为了健壮性和可扩展行通常需要更多的工程努力。
并发性一直是一个网站的架构师面临的最大挑战之一。Web服务创立以来,并发的水平一直在不断增长。一个受欢迎的网站,服务几十万,甚至上百万的同时在线用户的情况并不少见。十年前,造成并发的主要原因是缓慢的ADSL或拨号连接的客户端用用户。如今,并发是由移动客户端和新的应用程序架构的组合,这是都基于保持一个持久的连接,可以让客户端进行更新新闻,tweets,朋友feeds等。促进提高并发性的另一个重要因素是特性改变的现代浏览器,打开4到6个同时连接到一个网站,以提高网页加载速度。
为了说明关于慢速的客户问题,想象一个简单的基于Apache的Web服务器产生一个相对短的100 KB响应的web页面中的文本或图像。生成或检索这个页面只需要几分之一秒,但将它发送到带宽为80 kbps(10 KB/秒)客户端需要10秒。从本质上讲,Web服务器会相对迅速地获取100 KB的内容,然后在释放这个连接前, 将是繁忙的10秒钟,慢慢将此内容发送到客户端。现在,假设你有1000个同时连接的客户端的请求类似的内容。如果每个客户端只分配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和内存使用,它的缺点就是弱扩展性。
因此,当服务器硬件,操作系统和网络资源不再是网站增长的主要制约因素,世界各地的Web开发人员开始到处寻找一个更有效的方式运行Web服务器。大约在10年前,著名的软件工程师,Daniel Kegel宣布,“Web服务器是时候同时处理10000客户啦”,并预测也就是我们现在所说的互联网云服务。 Kegel的C10K明显的刺激了一些尝试者去解决Web服务器问题,去优化在同一时间处理大量的客户。nginx被证明是最成功的之一。
为了解决C10K问题的10,000个并发连接,nginx从心地以不同体系结构编写,这更适合于非线性可扩展性的并发连接数和每秒请求数。 nginx是基于事件的,所以它没有延续apache的风格,不会为每个网页请求产生新的进程或线程。最终的结果是,即使在负载增加时,内存和CPU使用率依然是可控的。一个典型的硬件服务器上,nginx的现在可以提供数十万的并发连接。
当nginx的第一个版本发布之时,它倾向于部署在Apache侧旁,静态内容,如HTML,CSS,JavaScript和图像 由nginx处理以分担apache为基础的应用服务的并发性和低延迟。在其发展过程中,nginx的增加了集成的应用程序,通过使用uswgi的FastCGI,SCGI协议,像Memcached的分布式的内存对象缓存系统。其他有用的功能,也增加了如反向代理负载均衡和缓存。这些额外的功能把nginx塑造成一个有效的工具组合,来建立一个可扩展的网络基础架构。
在2012年2月,Apache的2.4.x分支向公众发布。虽然这个最新版本的Apache已经加入新的多处理核心模块和新的代理模块旨在提高可扩展性和性能,断定它的性能,并发和资源利用是否看齐,或好与纯事件驱动的Web服务器还太早。很乐于看到新版本Apache应用程序服务器更好的处理伸缩性,不过,因为它可能会缓解后端的瓶颈问题,这在典型的nginx的加的Apache Web配置经常仍然没有得到解决。
使用nginx有更多的优势吗?
高性能,高效率的处理高并发是部署nginx的一直是关键的好处。然而,现在有更有趣的好处。
在过去的几年里,网络架构师们接受他们的应用程序从Web服务器基础设施的去耦和分离的想法。不过,以前以LAMP(Linux操作系统,Apache,MySQL和PHP,Python或Perl)的形式为基础的网站,现在可能成为不仅仅基于LEMP('E'引擎X'的地位)但越来越多往往是一个例子以不同的方式推动Web服务器到基础设施边缘和整合相同或改组后的一套应用程序和数据库工具。
Nginx是非常适合的,因为它提供了必要的关键功能,方便分担的并发性,低延迟的处理,SSL(安全套接字层),静态的内容,压缩和缓存,连接和请求限制,甚至HTTP的流媒体的应用层,以一种更有效的边缘Web服务器层。它也允许直接集成memcached/Redis的或其他的“NoSQL”的解决方案,当服务大量并发用户时以提高性能。
随着近年来目前开发工具包和编程语言风格广泛使用的,越来越多的企业正在改变他们的应用开发和部署的习惯。 nginx的已成为这些策略变革的最重要组成部分之一,它已经帮助许多公司开始开发Web服务,并在他们的预算中。
在2002年的nginx的的第一行代码写成。 2004年在BSD许可证下向公众发布。 从那时nginx的用户的数量一直增长,贡献的想法,并提交bug报告,建议和意见,对社区有极大的帮助和益处。
nginx的代码库是原创的,是完全从头开始用C编程语言编写的。 nginx的已经被移植到许多体系结构和操作系统,包括Linux,FreeBSD,Solaris,Mac OS X中,AIX和Microsoft Windows。 nginx的有其自己的库,其标准模块不会使用远远超出系统C库的东西,除了zlib的,PCRE和OpenSSL,如果没有必要,可以选择排除,因为的构建中潜在的许可冲突,。
说下Windows版本nginx。虽然nginx的工作在Windows环境中,Windows版本nginx的更像是概念的证明,而不是一个全功能的移植。 nginx和Windows内核架构,在这个时候,交互不是很好,有一定的限制。 nginx的版本的Windows的已知问题包括低得多的并发连接数,性能下降,没有缓存,没有带宽策略。 nginx的Windows的未来版本将和主流版本功能更为贴近。
2. nginx的体系结构概述。
传统的进程或线程模型处理并发连接是用一个单独的进程或线程处理每个连接使,并阻塞在网络或者输入/输出操作。取决于应用,内存和CPU的使用可能是非常低效的。产生一个单独的进程或线程都需要一个新的运行环境准备,包括堆和栈内存分配,并创建一个新的执行上下文。创建这些项目也花费额外的CPU时间,这最终会导致很差的性能,由于线程过多的上下文切换的颠簸。所有这些并发症的在较旧的Web服务器架构都有表现,比如Apache。这个在一个提供丰富特性的通用应用程序和优化服务资源的一个权衡。
从一开始,nginx就倾向于成为一个专门的工具来实现更高的性能,经济的使用服务器资源,同时允许一个网站的动态增长,所以它采取了不同的模式。它实际上是基于在各种操作系统的先进的事件的机制不断发展的启发。结果成就一个模块化的,事件驱动的,异步,单线程的,非阻断架构,成为nginx代码的基础。
nginx大量使用复用和事件通知,以及单独的进程致力于特定任务。连接的处理在有限数目称为worker的单线程进程的一个高效率的运行循环中。在每个worker 中nginx可以处理每秒成千上万的并发连接请求。
代码结构
nginx的worker代码包括核心和功能模块。 nginx的核心是负责维持严格的运行循环和在每个阶段的请求处理中执行适当模块的部分代码。模块构成大部分演示和应用层的功能。模块读取和写入到网络和存储,内容转换,出站过滤,应用服务器端包含行动,及当代理服务器激活时,并向上游传送请求。
nginx的模块化结构,通常允许开发者来扩展Web服务器功能集合,而无需修改nginx的核心。 nginx的模块稍微不同的化身,即核心模块,事件模块,阶段处理程序,协议,变量处理程序,过滤器,上游和负载平衡器。目前,nginx的不支持动态加载模块,在构建阶段模块和核心一起编译。然而,计划在将来主版本支持可加载模块和ABI。不同的模块的角色的更详细信息,可以参看第14.4节。
在处理与接受,处理和管理网络连接和内容检索的各种响应,nginx的使用事件通知机制和Linux的性能大量的增强磁盘I / O,Solaris和BSD为基础的操作系统,喜欢的kqueue,epoll,event ports事件端口。我们的目标是给操作系统提供尽可能多的提示,关于入站和出站流量及时的异步反馈,磁盘操作,读取或写入socket,超时等。复用和高级I / O操作在nginx的运行的基于Unix的操作系统上已经使用各种方法做了最大程度的优化。
nginx的高度概述架构如图14.1示意。
![](https://i-blog.csdnimg.cn/blog_migrate/82415ff07045fa932b1c98817815c7fe.png)
Worker模型
正如前面所提到的,,nginx并不为每个连接产生一个进程或线程。相反,工作进程从一个共享的“listen”socket接受新的请求,每个worker内运行循环高效执行。每个worker处理数以千计的连接。在nginx中有没有专门仲裁或分发连接到worker,这项工作是由操作系统内核机制完成。在启动时,一组初始的监听套接字被创建。worker不断接受,读取和写入到socket,当处理HTTP请求和响应时
nginx的worker代码的运行循环是最复杂的部分。它包括全部的内部调用,并在很大程度上依赖于异步处理任务的想法。异步操作通过模块化,事件通知,广泛使用回调函数和微调定时器实现。总体而言,关键的原则是尽可能的非阻塞。 唯一可以让nginx阻塞的情形是,没有足够的磁盘存储供worker操作。
由于nginx的并不会为每个连接产生的进程或线程,内存使用在绝大多数情况下是非常保守的,效率非常高。 nginx的节省CPU周期,因为没有持续的进程或线程的创建消毁模式。nginx要做的是检查网络和存储的状态,初始化新的连接,将它们添加到运行循环,处理直到完成,此时连接被释放,从运行循环中移除。谨慎使用系统调用和准确实现支持接口,如poll和slab内存分配器相结合,nginx的通常达到中度至低CPU占用率,即使在极端恶劣的工作负载。
由于nginx的产生数个worker进程处理连接,在多核上扩展性很好。一般情况下,每核一个单独的worker可以充分利用多核架构,并防止线程的颠簸和锁定。隔离的单线程worker进程没有资源匮乏和资源控制机制。该模型还允许在物理存储设备的可扩展性,有利于更好的磁盘利用率,避免了磁盘I / O阻塞。所以,一些资源在几个worker之间共享可以更有效使用
一些磁盘和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挂起,进而影响成千上万的连接。更多的工作计划将会实施, 将使nginx的嵌入式脚本更简单,更可靠,适用于更广泛的应用。
Nginx进程的作用
nginx在内存中运行多个进程。有一个主进程和多个worker进程。此外,还有一些特殊用途的进程,特别是高速缓存器加载和高速缓存管理器。在nginx的1.x版本中所有的进程都是单线程。所有进程主要使用的共享内存的进程间通信机制。主进程以root用户运行。缓存加载器,缓存管理和worker作为一个非特权用户运行。
主进程负责执行以下任务:
- 读取并验证配置
- 创建,绑定和关闭套接字
- 启动,终止和维护配置的工作进程数
- 不中断服务的情况下重新配置
- 控制不停止服务的二进制升级(如有必要,启动新的二进制程序和回滚)
- 重新打开日志文件
- 编译嵌入式的Perl脚本
worker进程接受,处置和处理来自客户端的连接,提供反向代理和过滤功能,做nginx能力范围之内的所有事情。关于监测nginx的实例的动态,系统管理员应该留意worker,因为他们是反映了Web服务器的实际的日常操作的进程。
缓存加载器进程负责检查磁盘上的缓存项和使用高速缓存元数据填充Nginx的在内存数据库。从本质上讲,缓存加载器把帮助nginx和文件协同工作,一个已经专门分配存储在磁盘上的目录结构。它遍历目录,检查缓存内容,元数据,更新共享内存中的相关条目,当一切都干净并准备投入使用时,然后退出,。
高速缓存管理器主要是负责缓存过期和失效。在正常nginx的操作过程中它驻留在内存中,在失败的情况下,它被主进程重新启动。
Nginx的缓存的简要概述
Nginx的缓存在以分层数据存储的形式在一个文件系统中实现的。缓存键是可配置的,不同请求为目的参数可以用来控制那些参数进入高速缓存中。高速缓存键和缓存元数据存储在共享内存段,从而缓存加载器,缓存管理器和worker可以访问。目前没有任何文件缓存在内存中,只有操作系统的虚拟文件系统机制方面的优化。每个高速缓存的响应被放置在文件系统上的不同文件。通过nginx的配置指令控制的层次结构(层次和命名的详细信息)。当一个响应被写入到高速缓存的目录结构,路径和文件的文件名是来自代理URL的MD5哈希。
放置在高速缓存中的内容的过程,如下所示:当nginx从上游服务器读取响应,内容首先被写入到高速缓存的目录结构以外的一个临时文件。当nginx的完成处理请求时,临时文件重命名,并被移动到缓存目录中。如果临时文件目录代理是在另一个文件系统上,该文件将被复制,因此建议临时文件和缓存目录保持在同一个文件系统。需要显式地清除,从缓存目录结构删除文件的是比较安全。有nginx第三方扩展,这可以远程控制缓存的内容,在主版本中有更多的工作计划将集成这一功能。
3. Nginx的配置
Nginx的配置系统灵感来自Igor Sysoev使用Apache的经验。他的主要见解是:一个可扩展的Web服务器配置系统是必不可少的。维护有很多的virtual servers,directories,locations和datasets的的大型复杂配置遇到了主要问题是可伸缩性。一个比较大的网络设置,无论是在应用程序级别和系统工程师自己,如果做得不好,那将是个噩梦。
因此,nginx配置的目的是要简化日常运作,并为Web服务器将来扩展的配置提供一个简单的方法。
Nginx的配置以纯文本文件保存,且通常驻留在/usr/local/etc/nginx或/etc/nginx。主配置文件通常被称为nginx.conf。为了保持它的整洁,可以把部分的配置放在单独的文件中,它可以在主文件中自动包含。然而,应该指出的是nginx的目前不支持Apache风格的分布式配置(例如,。htaccess文件)。所有和nginx web server的相关配置应驻留在一组集中的配置文件中。
配置文件由master进程最先读取和验证。一个编译过的nginx的配置是只读的形式提供给worker进程,worker是主进程派生的。配置结构通常由虚拟内存管理机制自动共享。
Nginx的配置main, http, server, upstream, location (mail)有几种不同上下文的指令块。上下文永远不会重叠。例如,没有把location block放在main block的指令块里的事情。此外,为避免不必要的歧义,没有任何像“全球网络服务器”配置的东西。 nginx的配置,倾向于干净和逻辑,允许用户能够保持复杂的配置文件,包括数以千计的指令。在一次私人谈话中,Sysoev说,Apache全局服务器配置中的“Locations,directories和其它块特性我从来都不喜欢的,所以因为这个原因,它们的功能从来没有在nginx中实现。”
配置语法,格式和定义遵循一个所谓的C-风格的惯例。这种特殊的配置文件的方法已经被用于各种开源和商业软件应用。在设计上,C-风格的配置适用于嵌套的描述,逻辑和容易创建,阅读和维护,和许多工程师都喜欢的。 nginx的C-风格的配置,也容易实现自动化。
虽然一些nginx的指令类似Apache配置中的某些部分,设立一个nginx的实例是完全不同的经历。例如,重写由nginx的支持的规则,虽然这将需要管理员从适应传统的Apache重写配置到匹配nginx的风格。重写引擎的实现也不同。
在一般情况下,nginx的设置也提供了支持几个原来的机制,这就是非常有用的瘦Web服务器配置的一部分。这是有道理的,简单提一下的variables和try_files指令,让nginx是有些独特的。在nginx的Variables,以提供额外的,更强大的机制来控制运行时配置的Web服务器。变量为快速评估进行了优化,并在内部预编译成指数。评估的需求;即一个变量的值,通常只计算一次,并缓存的生命周期的一个特定的请求。变量可以和不同的配置指令一起使用,为描述条件请求处理的行为提供额外的灵活性。
try_files指令最初是为了一个更恰当的方式逐步取代条件配置语句,它的目的是快速,高效地尝试不同的URI到内容的映射/匹配。总体而言,try_files的指令运作良好,并可以非常有效和有用的。建议读者彻底检查的try_files,接受它的适用。
4. Nginx的内部
正如之前提到的,nginx的代码库包含一个核心和大量的模块。 nginx的核心是负责提供Web服务器基础服务,Web和邮件的反向代理功能,它允许使用相关的网络协议,建立必要的运行时环境,并确保不同模块之间的无缝互动。然而,大多数的协议和特定应用程序的功能是由nginx的模块完成的,而不是核心。
在内部,nginx的通过模块管道或链的处理连接。换句话说,每一个操作都有一个模块向对应,例如,压缩,修改的内容,执行服务器端的包含通过FastCGI或uwsgi协议和上游应用程序服务器通信,或memcached交互。
有一对nginx的模块位于核心和真正的“功能性”模块之间。这些模块是http和mail。这两个模块在核心和较低级别的组件提供了更高级别的抽象。在这些模块中,处理一系列的事件对应相应的应用层协议,如HTTP,SMTP,IMAP已经实现。结合nginx的核心,这些上层模块负责维持各功能模块正确顺序的调用。虽然目前HTTP协议是HTTP模块的一部分的,由于需要支持其他协议,如SPDY,在未来有计划把它分割成一个功能模块,(见“SPDY协议的实验速度更快的网络“)。
这些功能模块可分为事件模块,阶段处理,输出滤波器,变量处理,协议,上游和负载平衡器。nginx大多数这些模块的补充HTTP的功能,虽然事件模块和协议也被用于邮件。事件模块提供了一个特别依赖于操作系统的kqueue或epoll的事件通知机制,如。 nginx的使用的事件模块依赖于操作系统的功能和构建配置。协议模块允许nginx通过TLS / SSL,HTTPS,SMTP,POP3和IMAP沟通。
一个典型的HTTP请求处理周期看起来像下面这样。
- 客户端发送HTTP请求。
- nginx的核心阶段根据所配置的location选择合适匹配请求的处理程序。
- 如果配置要求这样做,负载平衡器为代理挑选一个上游服务器。
- 阶段处理程序执行它的工作,并传递每个输出缓冲器到所述第一滤波器。
- 第一滤波器的输出传递到所述第二过滤器。
- 第二滤波器的输出到第三个(等等)。
- 最终的响应被发送到客户端。
nginx的模块调用是非常定制的。它是通过使用指针指向可执行函数执行一系列回调。但是,这种方法的缺点是,它给愿意写自己的模块的程序员一个很大包袱,因为他们必须准确定义模块何时以及如何运行。 nginx的API和开发者文档中不断完善,更可用以减轻这个负担。
一些一个模块可以附加的例子:
- 在配置文件中读取和处理之前
- 对于每个配置指令的location和服务器出现的地方
- 主配置初始化完成后
- 当服务器(即主机/端口)的初始化后
- 当服务器配置合并进主要配置
- 当location的配置初始化或和它的父服务器配置合并后
- 当主进程启动或退出
- 当一个新的worker进程启动或退出
- 在处理一个请求
- 当过滤的响应报头和身体
- 当选择,启动和重新启动请求到上游服务器
- 当处理从上游服务器来的响应
- 当完成一个与上游服务器交互
在worker的内部,导致运行循环生成响应行动的顺序看起来像下面这样:
- 开始ngx_worker_process_cycle()。
- 处理与OS相关的特定事件机制(,如epoll的或kqueue的)。
- 接受事件和调度相关的行动。
- 处理/代理请求报头和主体。
- 生成响应内容(标题,正文),并传输到客户端。
- 完成请求
- 重新初始化定时器和事件。
运行循环本身(步骤5和6),确保产生一个增量的响应和流给客户端。
一个更详细的处理一个HTTP请求视图可能看起来像这样:
- 初始化请求处理。
- 处理报头
- 处理报文主体
- 调用相关的处理程序
- 执行通过处理阶段。
这给我们带来的阶段。 当nginx的处理HTTP请求,通过一些处理阶段。在每个阶段中,有调用相应的处理程序。在一般情况下,阶段程序处理请求,并产生相应的输出。阶段处理程序附加在配置文件中定义的location。
第一阶段处理程序通常做四件事:获取location配置,产生适当的响应,发送标题,发送的消息体。一个处理程序有一个参数:一个特定的结构来描述的要求。请求结构有很多有用的信息,客户端的请求,如请求方法,URI,和头。
当读取HTTP请求头,nginx的执行查找相关联的虚拟服务器配置。如果虚拟服务器找到,该请求通过六个阶段:
- 服务器重写阶段
- location 阶段
- location重写阶段(可带回到上一阶段的要求)
- 访问控制阶段
- try_files阶段
- 登录阶段
nginx为了产生必要的响应于该请求的内容,将请求传递到一个合适的内容处理程序。根据的确切location配置,nginx的可以尝试所谓的无条件处理程序,如PERL,proxy_pass,FLV,MP4,等,如果请求不符合任何对以上内容的处理程序,它挑选的下列处理程序在这个正确的顺序:随机指标,指数,自动索引,gzip_static,静态的。
索引模块的详细信息,可以在nginx的文档找到,但它们是处理带斜线请求的模块。如果一个专门的模块,如MP4或自动索引不合适, 内容被认为是只是在磁盘上一个文件或目录(也就是静态)被静态内容处理程序处理。对于一个目录,它会自动重写的URI,这样结尾的斜线是永远存在的(然后发出一个HTTP重定向)。
内容处理程序的内容,然后递送给过滤器。过滤器附加在locations里,且一个locations可以配置数个过滤器。过滤器做任务的是操作由一个处理程序生成的输出。过滤器执行的顺序是在编译时确定。对于出过滤器是已经预定义,第三方的过滤器,它可以在构建阶段配置。在现有的nginx的实现,过滤器只能做出的变化,目前还没有写和附加过滤器去做输入内容转换的机制。输入滤波将在nginx的未来版本中出现。
过滤器按照特定的设计模式。过滤器被调用,开始工作,调用下一个过滤器,直到最后的过滤器链中的。在那之后,nginx的定型的反应。过滤器不必等待前面的过滤器来完成。在一个链中的下一个过滤器可以从它自己的工作尽快从以前的1的输入是可用的(功能很像Unix的管道)。反过来,所产生的输出响应可以在上游服务器接收到的全部响应之前传递给客户端,。
有消息头过滤器和消息体过滤器; nginx分别馈送消息头和消息体的和过滤器相关联的响应。
一个头过滤器包括三个基本步骤:
- 决定是否在操作此响应。
- 操作此响应
- 调用下个过滤器.
体过滤器转换生成的内容。体过滤器的例子包括:
- 服务器端包含
- XSLT 过滤器
- 图像过滤器 (for instance, resizing images on the fly)
- 字符集修改
- gzip 压缩
- 分块编码
过滤器链后,响应被传递到写操作。伴随写操作有一对其他特殊用途的过滤器,即复制过滤器和的推迟过滤器。复制过滤器是负责,用于填充相关响应内容的内存缓冲区, 这些内容可能存贮在代理的一个临时目录中。推迟过滤器用于子请求。
子请求是请求/响应处理的一个非常重要的机制。子请求nginx的最强大的方面之一。通过子请求nginx可以从一个不同于客户端最初请求的URL返回结果。一些web框架调用这个内部重定向。然而,nginx的更进一步,不仅可以过滤执行多个子请求的输出组合成一个单独的响应,但是子请求也可以是嵌套和层次。一个子请求可以执行其自己的子子请求,一个子子请求启动子子子请求。子请求映射到硬盘上的文件,其他处理,或上游服务器。子请求最有用的是在原来的响应基础上插入额外的内容。例如,SSI(服务器端包含)模块使用过滤器来解析返回的文档的内容,然后指定的URL的内容替换include指令。或者,它可以是使一个过滤器,将一个文件的全部内容作为要检索一个URL的一个例子,然后追加新的文件到URL本身。
上游和负载平衡器也值得描述简要。Upstreams被标记为内容处理,也就是一个反向代理(proxy_pass处理程序)。Upstreams模块主要是把请求发送到上游服务器(或“后端”),并从上游服务器收到的响应。这里没有输出过滤出。的上游模块具体要做是设置回调函数,当上游服务器已准备好要写入和读出被调用。存在回调实现以下功能:
- 制作一个发送到上游服务器的缓冲区请求被(或它们的链)
- 重新初始化和重置连接到上游服务器(which happens right before creating the request again)
- 处理的的上游响应的第一个位和保存到从上游服务器接收有效载荷的指针。
- 中止请求(这发生在客户端提前终止)
- 当nginx从上游服务器完成读取时,完成请求。
- 修剪的响应体 (e.g. removing a trailer)
负载均衡模块连接到的proxy_pass处理程序,当一个以上的上游服务器符合要求,提供选择上游服务器能力。负载平衡器注册使能的配置文件中指令,提供了额外的上游初始化函数(解析上游在DNS名称等),初始化的连接结构,决定路由请求到哪里,并更新统计信息。目前,nginx的负载平衡支持到上游服务器两个标准的方式:循环和IP哈希。
上游和负载平衡处理机制,包括检测上游服务器失败算法和路由的新请求到其它,虽然很多额外的工作计划以增强此功能。在一般情况下,负载均衡方面更多工作的计划,并在nginx的下一版本中在不同的上游服务器分配负载以及健康检查的机制将大大改善。
也有一些其他有趣的模块,提供了一个额外varibales集合在配置文件中使用。虽然在nginx的variables在不同的模块中创建和更新,有两个模块的是完全致力于variables: geo和map。geo模块方便根据客户端的IP地址跟踪客户端。该模块可以依赖于客户端的IP地址创建任意变量。其他模块,map,允许从其他变量创建变量,基本上能够做到灵活的映射主机名和其他运行时变量。这种模块可以被称为变量的处理程序。
内存分配机制实现在一个单一的nginx的worker里,在一定程度上,受Apache启发。在一个高层次上描述nginx的内存管理应该如下:对于每个连接,动态分配必要的内存缓冲区,链接,用于存储和处理的请求和响应的头和身体,然后释放连接释放后。非常重要是要注意nginx试图尽可能地避免在存储器中的复制数据和大部分的数据传递由指针的值传递,而不是调用memcpy。
更深一点,一个影响由模块所产生时,所检索的内容放置在内存存储器中,然后添加到缓冲区链中。后续处理工作也和这个缓冲区链相关。缓冲区链在nginx是相当复杂的,因为有几个处理场景,取决模块类型不同而有所不同。例如,实现消息体过滤器模块时精确地管理缓冲区是相当棘手的。这样的模块只能在同一时间运行在一个缓冲区(链环),它必须决定是否要改写输入缓冲器,用一个新分配的缓冲区更换缓冲区,或插入一个新的缓冲区之前或之后的缓冲区。为了使事情变得复杂,有时一个模块会收到几个缓冲区,因此,它有一个不完整的必须运行在缓冲区链。然而,此时的nginx只提供了一个低级别的API操纵缓冲链,因此在做任何实际实现第三方模块之前,开发人员应该对nginx的这部分神秘代码相当熟悉。
对上述方案的说明是总在连接整个生命周期都有内存分配,从而为长期连接保持一些额外的内存。在同一时间,在一个空闲的保持活动的连接,nginx花费的内存只有550个字节。一个可能的优化将是在nginx的未来版本的为长连接重用和共享内存缓冲区。
管理内存分配的任务是通过nginx的池分配器。共享内存区域用于接受互斥,缓存元数据,SSL会话缓存,相关的信息带宽策略和管理(限制)。nginx实现了一个slab分配器实施管理共享内存分配。同时为了共享内存的同步安全使用,一些锁定机制可用(互斥锁和信号量)。为了组织复杂的数据结构,Nginx还提供了一个红黑树的实现。红 - 黑树是用来保持缓存元数据在共享内存中,追踪用于一对其他任务的非正则表达式的location定义。
不幸的是,上述所有从未一致的和简单的方式描述,使得开发nginx的第三方扩展的工作相当复杂。虽然存在一些很好的nginx的内部文件 - 例如,埃文·米勒所写的,这样的文件需要一个巨大的逆向工程努力,实现nginx的模块对许多人来说仍然如魔术一般。
尽管第三方模块的开发有一定的困难,nginx的用户社区最近看到了很多有用的第三方模块。例如,有一个nginx的嵌入式的Lua语言解释器模块,其他模块,负载均衡,全面支持WebDAV,先进的高速缓存控制和其他有趣的第三方本章作者的鼓励未来被支持。
5. 经验教训
当 Igor Sysoev开始写nginx时,大部分软件使互联网已经存在,那样的软件的体系结构通常遵循的传统的服务器和网络硬件,操作系统,和老的Internet体系结构的通常定义。然而,这并没有阻挡Igor认为他可以在web服务器上做些事情。所以,第一个的经验似乎是显而易见的,它是这样的:总是有改进的余地。
有着使网络软件更好的想法,Igor花了很多时间开发初始代码的结构和学习在各种操作系统优化代码的不同方式。十年后,考虑到第1版多年的积极发展, 他正在开发nginx2.0版本原型。很显然,对未来的软件产品初始原型的新架构,最初的代码结构,是非常重要的。
还有一点值得一提的是,开发要有重点。 Windows版本的nginx在避免在既不是开发商的核心竞争力或目标应用上分散开发努力,可能是一个很好的例子。它同样适用重写引擎过程中出现多次尝试增加更多的nginx功能与现有的传统设置的向后兼容性。
最后但并非最不重要的一点是,值得一提的是,尽管事实上nginx的开发人员社区不是非常大,nginx的第三方模块和扩展一直是一个nginx受欢迎的非常重要的部分。Evan Miller, Piotr Sikora, Valery Kholodkov, Zhang Yichun (agentzh) 和其他优秀的软件工程师所做的工作备受nginx的用户社区和它的最初的开发人员赞赏。