Py学习  »  NGINX

《开源软件架构》--nginx配置与内部(三)

并发编程网 • 5 年前 • 177 次点击  

14.3. nginx配置

nginx的配置系统灵感来自Igor Sysoev使用Apache的经验。他的主要观点是,可伸缩的配置系统对于web服务器非常必要。在维护包含大量虚拟服务器、目录、位置和数据集的大型复杂配置时,遇到了主要的扩展性问题。在一个相对较大的web设置中,如果在应用程序级别和系统工程师本人都不能正确地进行设置,那么这将是一场噩梦。

因此,nginx配置旨在简化日常操作,并为进一步扩展web服务器配置提供一种简单的方法。

nginx配置保存在许多纯文本文件中,这些文件通常位于 /usr/local/etc/nginx 或 /etc/nginx 中。主配置文件通常称为 nginx.conf。为了保持其整洁,可以将部分配置放在单独的文件中,这些文件可以自动被包含到主文件中。然而,值得注意的是,nginx目前不支持Apache风格的分布式配置(即,.htaccess文件)。所有与nginx web服务器行为相关的配置都应该位于一组集中的配置文件中。

配置文件最初由主进程读取和验证。当从主进程派生worker进程时,worker进程可以使用已编译的只读形式的nginx配置。配置结构由常用的虚拟内存管理机制自动共享。

nginx配置有几个不同的上下文,分别用于main、http、server、upstream 、location  (以及用于邮件代理的mail)块的指令。上下文不重叠。例如,不存在在main指令块中location块这样的事情。此外,为了避免不必要的歧义,没有任何类似于“全局web服务器”配置的东西。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:用于更快的web的实验性协议”),未来计划将其分离为功能模块。

功能模块可以分为事件模块、阶段处理程序、输出过滤器、变量处理程序、协议、上行流和负载均衡器。大多数这些模块补充了nginx的HTTP功能,尽管事件模块和协议也用于 mail 模块。事件模块提供特定的基于os的事件通知机制,如kqueue或epoll。nginx使用的事件模块取决于操作系统功能和构建配置。协议模块允许nginx通过HTTPS、TLS/SSL、SMTP、POP3和IMAP进行通信。

典型的HTTP请求处理周期如下所示。

  1. 客户端发送HTTP请求。

  2. nginx 核心根据与请求匹配的location配置选择适当的阶段处理程序。

  3. 如果配置了,负载均衡器将选择上游服务器进行代理。

  4. 阶段处理器完成它的工作,并将每个输出缓冲区传递给第一个过滤器。

  5. 第一个过滤器将输出传递给第二个过滤器。

  6. 第二个过滤器将输出传递给第三个(以此类推)。

  7. 最终的响应被发送到客户端。

nginx模块调用是完全可定制的。它是通过使用指向可执行函数的指针的一系列回调来执行的。然而,这样做的缺点是,它可能会给想要编写自己的模块的程序员带来很大的负担,因为他们必须准确地定义模块应该如何以及何时运行。nginx API和开发人员的文档都得到了改进,并提供了更多的可用性来缓解这一问题。

可以附加模块的一些例子是:

  • 在读取和处理配置文件之前

  • location和server出现的每个配置指令

  • 初始化主配置时

  • 初始化服务器(即,主机/端口)时

  • 合并服务器配置和主配置时

  • 初始化location配置或与其父server配置合并时

  • 主进程启动或退出时

  • 当新的worker进程启动或退出时

  • 处理请求时

  • 过滤响应头和响应体时

  • 挑选,初始化并重新初始化对上游服务器的请求时

  • 处理来自上游服务器的响应时

  • 完成与上游服务器的交互时

  • When finishing an interaction with an upstream server

在一个worker进程中,生成响应的运行循环的操作序列如下所示:

  1. 开始ngx_worker_process_cycle()。

  2. 使用特定的OS机制(如epoll 或 kqueue)来处理事件。

  3. 接受事件并分派相关操作。

  4. 处理/代理请求头和请求体。

  5. 生成响应内容(响应头、响应体)并以流的形式发送到客户端。

  6. 完成请求。

  7. 重新初始化计时器和事件。


运行循环本身(步骤5和6)确保增量生成响应并将其流式传输到客户端。

处理HTTP请求的更详细视图可能如下所示:

  1. 初始化请求处理。

  2. 处理请求头。

  3. 处理请求体。

  4. 调用关联的处理程序。

  5. 运行贯穿处理阶段。


这就把我们带到了各个处理阶段。当nginx处理HTTP请求时,它会将其传递到多个处理阶段。在每个阶段都有要调用的处理程序。通常,阶段处理程序处理一个请求并生成相关的输出。阶段处理程序附加到配置文件中定义的位置。

阶段处理程序通常做四件事:获取位置配置、生成适当的响应、发送响应头和响应体。处理程序有一个参数:描述请求的特定结构。请求结构包含许多关于客户端请求的有用信息,例如请求方法、URI和请求头。

当读取HTTP请求头时,nginx会查找相关的虚拟服务器配置。如果找到虚拟服务器,请求将经过六个阶段: .

  1. 服务器重写阶段

  2. 定位阶段

  3. 定位重写阶段(可以将请求恢复到上一阶段)

  4. 访问控制阶段

  5. try_files 阶段

  6. 日志记录阶段

为了生成响应请求所需的内容,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分别将响应头和响应体提供给关联的过滤器。

头过滤器包括三个基本步骤:

  1. 决定是否对此响应进行操作。

  2. 操作响应。

  3. 调用下一个过滤器.

体过滤器转换生成的内容。体过滤器的例子包括:

  • 服务器端包含

  • XSLT 过滤

  • 图像过滤(例如,动态调整图像大小)

  • 修改字符集

  • gzip 压缩

  • 分块编码

在过滤器链之后,响应被传递给写入器。除了写入器之外,还有两个额外的特殊用途过滤器,即 copy 过滤器和 postpone 过滤器。copy 过滤器负责用可能存储在代理临时目录中的相关响应内容填充内存缓冲区。postpone 过滤器用于子请求。

子请求是请求/响应处理的一种非常重要的机制。子请求也是nginx最强大的方面之一。通过子请求,nginx可以从与客户端最初请求的URL不同的URL返回结果。一些web框架称之为内部重定向。然而,nginx更进一步——过滤器不仅可以执行多个子请求并将输出组合成单个响应,而且子请求也可以嵌套和分层。子请求可以执行自己的子请求,孙请求也可以发起它的子请求。子请求可以映射到硬盘、其他处理程序或上游服务器上的文件。子请求对于在原始响应的数据插入额外内容最有用。例如,SSI(服务器端包含)模块使用过滤器解析返回文档的内容,然后用指定URL的内容替换 include 指令。或者,它可以是一个过滤器的示例,该过滤器将文档的整个内容视为要检索的URL,然后将新文档追加到URL上。

upstream 和负载均衡器也值得简要描述。上游用于实现可以定义为反向代理的内容处理程序(proxy_pass处理程序)。upstream 模块主要准备发送到上游服务器(或“后端”)的请求,并从上游服务器接收响应。这里没有对输出过滤器的调用。
upstream 模块所做的事情是设置回调等待上游服务器准备好写入和读取时调用。存在实现以下功能的回调:

  • 制作要发送到上游服务器的请求缓冲区(或缓冲区链)

  • 重新初始化/重置与上游服务器的连接(在再次创建请求之前发生)

  • 处理上游响应的第一位并保存指向从上游服务器接收的有效负载的指针

  • 中止请求(在客户端过早终止时发生)

  • 当nginx从上游服务器读取完成时结束请求

  • 整理响应体(例如移除trailer)

负载均衡模块附加到proxy_pass处理程器,当有多个上游服务器符合条件时,提供选择上游服务器的能力。负载均衡器注册一个启用的配置文件指令,提供额外的上游初始化函数(解析DNS中的上游服务器名称,等等),初始化连接结构,决定请求的路由,并更新统计信息。目前nginx支持两种标准的规则来平衡上游服务器的负载:轮询和ip-hash。

upstream 和负载均衡处理机制包括检测失效的上游服务器和将新请求重新路由到其他服务器的算法——尽管计划进行大量额外工作来增强此功能。一般来说,计划对负载均衡器进行更多的开发,在nginx的下一个版本中,用于在不同上游服务器之间分配负载以及健康检查的机制将得到极大的改进。

还有一些其他有趣的模块,它们提供了一组额外的变量供配置文件中使用。虽然nginx中的变量是跨不同模块创建和更新的,但是有两个模块完全专用于变量:geo 和 map。geo模块用于根据客户端的 IP 地址进行跟踪。这个模块可以创建依赖于客户端 IP 地址的任意变量。另一个模块 map 允许从其他变量创建变量,本质上提供了灵活映射主机名和其他运行时变量的能力。这种模块可以称为变量处理程序。

进程nginx中单个worker中实现的内存分配机制在某种程度上受到Apache的启发。nginx内存管理的高级描述如下:对于每个连接,必要的内存缓冲区是动态分配,关联,用来存储和操作请求和响应的头和体的,然后在连接释放时释放。值得注意的是,nginx尽量避免在内存中复制数据,并且大多数数据是通过指针值传递的,而不是通过调用memcpy。

再深入一点,当模块生成响应时,检索到的内容被放入内存缓冲区,然后将内存缓冲区添加到缓冲区链路中。后续处理也适用于这个缓冲链。在nginx中,缓冲链非常复杂,因为根据模块类型的不同,有几种不同的处理场景。例如,在实现体过滤模块时,精确地管理缓冲区可能非常棘手。这样的模块一次只能操作一个缓冲区(链路),它必须决定是覆盖输入缓冲区、用新分配的缓冲区替换缓冲区,还是在相关缓冲区之前或之后插入新的缓冲区。更复杂的是,有时一个模块会接收多个缓冲区,因此它必须对一个不完整的缓冲区链进行操作。然而,此时nginx只提供了一个用于操作缓冲链的低级API,因此在进行任何实际实现之前,第三方模块开发人员应该非常熟悉nginx的这个神秘部分。

关于上述方法的一个注意事项是,存在为连接的整个生命周期分配的内存缓冲区,因此对于长生命周期的连接,会保留一些额外的内存。同时,在空闲的keepalive连接上,nginx只花费550字节的内存。nginx未来版本的一个可能的优化是对长生命周期的连接重用和共享内存缓冲区。

管理内存分配的任务由nginx池分配器完成。共享内存区域用于接受互斥锁、缓存元数据、SSL会话缓存以及与带宽策略和管理(限制)相关的信息。nginx中实现了一个slab分配器来管理共享内存分配。为了同时安全使用共享内存,可以使用许多锁定机制(互斥锁和信号量)。为了组织复杂的数据结构,nginx还提供了一个红黑树实现。红黑树用于将缓存元数据保存在共享内存中,跟踪非非正则表达式位置定义和其他一些任务。

不幸的是,上述所有内容从未以一致和简单的方式进行描述,这使得为nginx开发第三方扩展的工作变得非常复杂。尽管存在一些关于nginx内部构件的优秀文档(例如由Evan miller贡献的那些文档),但是这类文档需要大量的逆向工程工作,并且nginx模块的实现对许多人来说仍然是一门神秘的艺术。

尽管第三方模块开发存在一定的困难,但nginx用户社区最近看到了许多有用的第三方模块。例如,有一个用于nginx的嵌入式Lua解释器模块、用于负载平衡的额外模块、完整的WebDAV支持、高级缓存控制和其他有趣的第三方工作,本章的作者鼓励并将在将来支持这些工作。

14.5. 经验总结

当Igor Sysoev开始编写nginx时,大多数支持Internet的软件都已经存在,而此类软件的体系结构通常遵循遗留服务器和网络硬件、操作系统和旧Internet体系结构的定义。然而,这并没有阻止Igor认为他可以改进web服务器领域的东西。因此,第一个经验显而易见,它是:总有改进的空间。

怀着开发更好的web软件的想法,Igor花了大量时间开发初始代码结构,并研究了针对各种操作系统优化代码的不同方法。十年后,考虑到在版本1上多年的积极开发,他正在开发nginx 2.0版本的原型。很明显,新体系结构的初始原型和初始代码结构对软件产品的未来至关重要。

另一点值得一提的是,开发应该专注。nginx的Windows版本可能是一个很好的例子,说明了避免开发人员的核心能力或目标应用程序之外的工作耗费太多精力是值得的。它同样适用于重写引擎,出现多次尝试使用更多特性增强nginx,以便与现有遗留设置向后兼容。

最后但同样重要的,值得一提的是,尽管nginx开发人员社区不是很大,但第三方模块和扩展一直是nginx广受欢迎的重要原因。Evan Miller、Piotr Sikora、Valery Kholodkov、Zhang Yichun (agentzh)等优秀软件工程师的工作得到了nginx用户社区及其原始开发人员的高度赞赏。

(全文完)

点击下方
阅读原文



Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/31802
 
177 次点击