3.nginx配置
nginx配置系统受益于Igor Sysoev(nginx创始人)在Apache(阿帕奇软件基金会)的经历。Igor Sysoev洞察出可扩展的配置系统对于一个web服务器来说是必不可少的。当维持众多虚拟服务器、目录库、地址信息和数据集的大规模结构复杂的配置时,扩展中的主要问题便不期而遇。如果在应用端和系统工程师设计方面处理的不恰当,对于配置一个相对大的web服务器来说将会是一个噩梦。
因此,nginx配置旨在简化日常的操作和为进一步扩展web服务器配置提供一个简单的方法。
nginx配置信息存放在一些位于/usr/local/etc/nginx或者/etc/nginx等目录下的纯文本文件里。主要的配置文件通常被称为nginx.conf。为了保持配置系统结构清晰,部分的配置信息保存在单独的文件里,这些文件可以自动被nginx.conf文件调用。然而,这里应该指出目前nginx并不支持Apache风格的分布式配置(即.htaccess文件)。所有和nginx web服务器操作有关的配置信息都应该集中存放在一组配置文件里。
主进程会对这些配置文件执行初始化读取和验证操作。由于是主进程的分支,被编译成只读形式的nginx配置对worker进程也是可见的。配置结构自动分享给虚拟的内存管理机制。
nginx配置系统为main, http, server, upstream, location(还有提供邮件代理功能的mail)块的指令集设置了不同的上下文环境。这些上下文环境从不重叠。例如,不会有像location块和main块的指令集混在一起这样的事情发生。而且,为避免不必要的歧义,nginx不存在一个全局的web服务器配置。nginx配置是结构清晰、有逻辑的,而且同时允许用户维护包含成千上万指令的复杂配置文件。在和Sysoev私下谈话中,他曾说:“在Apache中全局服务器配置中的地址、目录和其它配置块是我所不喜欢的功能,因此这也是我没有把它们在nginx中实现的原因”。
nginx配置中的语法、格式和定义采用了C语言风格的规则。这些生成配置文件的方式已经在各种各样的开源的和商业的软件应用中使用。通过设计,C语言风格的配置很适合嵌套和逻辑描述,而且容易创建、阅读、和维护,同时被许多工程师所喜爱。nginx中C语言风格的配置也方便自动部署。
虽然nginx的一些指令和Apache设置的部分指令相似,但设置一个nginx实例有相当大的不同之处。例如,nginx支持重写规则,但却需要管理员动手适应从apache风格的rewrite配置文件到nginx风格的转变。重写引擎的实现也有所不同。
一般情况下,nginx设置也对几种原始机制提供了支持,这些机制可以非常有用的作为一个精巧的web服务器配置的一部分。有必要简短地介绍一下nginx独特的变量和try_files命令。为了控制web服务器运行时的配置,nginx中生成的变量提供了一种附加的甚至更强大的机制。为了快速计算,nginx优化了变量和做了内部预编译处理。计算按需完成;也就是说,变量的值通常只被计算一次并且保存在缓存里在特定的请求生命周期内。变量被用于不同的配置指令,为描述条件请求处理行为提供额外的灵活性。
try_files命令最初的目的是以一种恰当的方式逐渐取代if条件配置语句,而且快速高效地匹配不同的uri到内容的映射。总的来说,try_files命令非常高效有用。建议读者全面核对try_files命令,尽可能的使用其用法。
4、nginx内部构件
正如之前提到的,nginx代码库由一个内核和多个模块组成。nginx内核负责提供web服务器的基础功能以及web和邮件的反向代理功能;它允许使用底层的网络协议,建立必要的运行时环境,以及确保不同模块之间的无缝交互。然而,大多数协议和应用程序特定的功能都是由nginx模块完成的,而非nginx内核。
nginx在内部通过管道或链模块处理连接。换句话说,对于每个操作都有一个模块在做相关的工作;例如,压缩,修改内容,执行服务器端的包含文件,通过FastCGI或 uwsgi协议和上行应用服务器通信,或者和缓存通话。
nginx有两个位于内核和真正的“功能”模块之间的模块,它们就是http和mail模块。这两个模块提供位于内核和下层组件之间的额外的抽象。在这些模块中,实现了与像http,smtp,imap这些应用层协议相关的事件序列的处理。结合nginx内核,这些上层模块负责维护正确调用各功能模块的顺序。尽管http协议目前作为http模块的一部分,由于需要支持像SPDY(请参考:"SPDY: An experimental protocol for a faster web")的其它协议,计划将来把它拆分为一个功能模块。
功能模块可以拆分为事件模块、状态处理程序、输出过滤器、变量处理程序、协议、上行流和负载平衡器。虽然事件模块和协议也用于mail,这些模块中的大多数补充了nginx的HTTP功能。事件模块提供了一个特别依赖于操作系统的像kqueue、epoll的事件通知机制。 nginx使用的模块依赖操作系统的性能和构建配置。协议模块允许通过HTTPS, TLS/SSL, SMTP, POP3和 IMAP协议和 nginx进行通信。
一个典型的HTTP请求处理周期如下所示:
1、客户端发送HTTP请求
2、nginx内核选择合适的基于配置地址和匹配请求的状态处理程序
3、如果进行了此项配置,一个负载均衡器选择一个上游的代理服务器
4、状态处理程序把每一个输出缓存区传递到第一个过滤器
5、第一个过滤器把输出结果传递到第二个过滤器
6、第二个过滤器把输出结果传递到第三个过滤器(以此类推)
7、最终的处理结果被发送到客户端
nginx模块调用是非常可定制的。这是通过一系列的回调方法实现的。然而,这样做有一个缺点:对于那些喜欢自己编写模块的程序员来说,由于他们必须要准确地定义这些模块应该怎样运行和什么时候运行,所以他们也许会面临一个巨大的负担。为了解决这些问题,nginx API 和开发者文档正在尽可能的改进。
下面是一些模块可附加地址的例子:
●在配置文件被读取和处理前
●遍历地址和服务器的每一个配置指令
●在服务器配置和主要配置合并的时候
●在地址配置初始化或与其父服务器配置合并的时候
●在主进程启动或退出时
●在一个新的worker进程启动或退出时
●在处理一个请求时
●在过滤响应的头部和主体数据时
●当向上游服务器执行选择、启动、重新启动一个请求的操作时
●在处理来自上游服务器的响应信息时
●当完成与上游服务器交互时
在worker模块内部,在生成响应的地方,导致运行循环的操作顺序如下所示:
1、开启ngx_worker_process_cycle()方法
2、用操作系统特定的机制(例如:kqueue、epoll)处理事件
3、接受事件并调度相关的行动
4、处理或代理请求的头部和主体信息
5、生成响应的内容(头部、主体)并以数据流的形式发送到客户端
6、完成请求
7、重新初始化计时器和事件
运行循环(步骤5和6)确保增量生成一批响应并且把它们以数据流的形式发送到客户端。
处理一个HTTP请求的更详细的视图如下所示:
1、初始化请求处理
2、处理请求头部信息
3、处理请求主体信息
4、调用关联的处理程序
5、运行处理阶段
下面我们谈谈具体的过程。当nginx处理一个HTTP请求时,它将通过一系列的处理阶段。在每个阶段都有可调用的处理程序。一般来说,阶段处理程序处理一个请求并且生成相应的输出信息。阶段处理程序附加到配置文件中定义的地址。
阶段处理程序通常做四件事情:获取地址的配置信息、生成恰当的响应信息、发送响应的头信息、发送响应的主体信息。处理程序有一个这样的参数:描述请求的特定结构。请求结构有许多关于客户端请求的有用的信息,比如请求的方法和URI以及头信息。
当读取一个HTTP请求头时,nginx查找相关的虚拟服务器的配置。如果找到了虚拟服务器,请求将会经过6个阶段:
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 pipeline)。 相应地, 在接收来自上游服务器的整个响应之前,生成的输出响应可以被传送到客户端。
nginx有响应头过滤器和响应主体过滤器;nginx提供响应的头和主体分别给关联的过滤器。
响应头过滤器的操作由三个步骤组成:
1、决定是否对此响应进行操作
2、在响应中做操作
3、调用下一个过滤器
响应主体过滤器转换生成的内容。例子如下:
1、服务器端包含
2、XSLT过滤
3、图形过滤(例如,动态调整图像)
4、字符集更改
5、gzip压缩
6、分块编码
在过滤器链之后,响应被传送到写入器。有两个额外的专用过滤器伴随着写入器,也就是copy过滤器 和postpone过滤器。copy过滤器负责使用可能存储在代理临时目录中相关的响应内容来填充内存缓冲区。postpone过滤器被用于子请求。
对处理请求/响应来说,子请求是一个非常重要的机制。子请求也是nginx最强大
的方面之一。使用子请求,nginx可以从一个不同的URL返回结果而不仅仅是客户的原始请求URL。一些web框架把这种方式叫做内部重定向。然而,nginx更进一步----
不仅过滤器可以执行多个子请求并把输出合并到一个响应,而且子请求也可以层层嵌套,例如,一个 subrequest可以执行它本身的sub-subrequest,并且一个sub-subrequest触发 sub-sub-subrequest 。(子请求可以为自己创建二级子请求,而且二级子请求可以进一步创建三级子请求)
子请求可以映射到硬盘上的文件,其它处理程序,或上游服务器。子请求最大的用处在于插入基于原始响应数据的新增内容。例如,SSI(服务器端包含)模块使用过滤器来解析返回文档的内容, 然后使用指定 url的内容来替换include指令。 或者,它可以成为把整个文档内容当做一个url的过滤器被检索的一个例子,然后把新文档添加到url本身。
upstream和负载平衡器也值得简要描述。Upstream用于实现什么可以被确认是一个反向代理的内容处理程序(proxy_pass处理程序)。upstream模块大多预备发送到上行服务器或后台的请求,并且接收来自上行服务器的响应信息。这里没有调用输出过滤器。当上行服
务器准备写入和读取时,上行模块负责设置要调用的回调方法。回调方法实现以下功能:
1、制作一个发送到上行服务器的请求缓冲区或者请求链
2、重新初始化/重置到上行服务器的连接(再一次创建请求之前)
3、处理上行响应第一个比特位(bit)的数据并且保存从上行服务器接收的有效负载指针
4、退出请求处理(当客户端过早终止请求时)
5、当 nginx完成从上游服务器的读操作时,结束请求处理
6、修整响应主体(例如,移除尾部)
当有多个上游服务器可供选择时,负载均衡器模块附加到proxy_pass处理程序以提供选择一个上游服务器的功能。一个负载均衡器注册一个可启用的配置文件指令,提供附加的上行初始化函数(以解析DNS中上行名称等等),初始化连接结构,决定在什么地方路由请求,和更新统计信息。目前nginx对上游服务器负载均衡支持两种标准的准则:轮询 和IP哈希 。
upstream和负载均衡处理机制包含检测连接失败的服务器,并将新的请求重新路由至仍然正常的服务器的算法,但是计划要做很多额外的工作来加强这方面的功能。一般来说,关于负载均衡有更多的工作计划去做,而且在下一个版本的nginx中,把负载分布到不同的上行服务器以及健康检查的机制将会大大改善。
还有其它几个有趣的模块,这些模块在配置文件中提供附加的变量以供使用。尽管nginx的变量在不同的模块中被创建和修改,有两个模块是完全专用于变量:geo和map。geo模块是用于基于ip地址跟踪客户端。该模块基于客户端ip地址可以创建任意的变量。另外一个模块map允许从其他变量创建新变量,提供灵活映射主机名和其他运行时变量的能力。这种类型的模块可以称为变量处理程序。
在某种程度上受到了Apache的启发,单个nginxworker中实现了内存分配机制。nginx内存管理进一步的描述如下:对每一个连接,内存缓冲区动态分配、链接、用于存储和操作请求和响应的头以及主体、然后释放连接。注意到这一点是非常重要的:nginx试图尽可能避免在内存中复制数据,大多数数据传递使用指针而非调用memcpy。
再深入一些,当一个模块生成响应时,检索的内容放入一个内存缓冲区,然后此内存缓冲区被添加到一个缓冲区链。此缓冲区链进行后续的处理工作。nginx的缓冲区链之所以十分复杂,是因为有几种处理情景因模块类型而异。比如,在实现一个主体过滤模块时,精确的管理缓冲区是相当棘手的一件事情。这样的模块一次只能操作一个缓冲区(链),它必须要决定是否要重写输入缓冲区、用新分配的缓冲区替换当前
的缓冲区、或者在缓冲区之前或之后插入一个新的缓冲区。更复杂一些的情况,有时候一个模块将接收多个缓冲数据导致该模块在一些必要处理操作上会持有不完整的缓冲区链。然而,目前nginx只提供了一个低级的API用于操作缓冲区链,因此在实现第三方模块之前开发人员应该熟悉nginx这部分特性。
以上这些需要注意的是,在连接的整个生命周期中,都为其分配了内存缓冲区;因此要为长期连接保留一些额外的内存。然而,保留一个空闲的持久连接,nginx仅仅使用了550字节的内存。在未来版本的nginx中,一些可能的优化:可复用、长
期连接共享内存缓冲区。
nginx分配器池负责管理内存分配。共享内存被用于:接受互斥锁、缓存元数据、SSL会话缓存、和带宽管理(限制)有关的信息。nginx实现了slab分配器用于管理共享内存分配。为了能够同时安全使用共享内存,nginx提供了许多锁定机制(互斥锁和信号量)。为了组织复杂的数据结构,nginx还提供了红黑树的实现。 红黑树用来保存共享内存中的缓存元数据,跟踪非正则表达式地址定义和一些其他任务。
遗憾的是,从未使用过一个一致且简单的方式描述过上面所说的内容,为nginx进行第三方扩展开发是一项相当复杂的工作。虽然nginx内部有一些很好的文档-----例如,Evan Miller编写的文档-----这些文档是经过巨大的逆向工程努力产生的,而且nginx模块的实现仍然有黑色艺术性。
不管与第三方模块开发相关的困难,nginx用户社区最近出现很多有用的第三方模块。例如,nginx嵌入Lua解释器模块, 负载均衡的附加模块, 完全支持WebDAV,高级高速缓存控制以及本章作者鼓励和将来支持的其他有趣的第三方工作。
(未完,待续。。。)
1. 本文由mathew翻译,程序员学架构校审
2. 本文译自The Architecture of Open Source Applications
3. 转载请务必注明本文出自:程序员学架构(微信号:archleaner )
4. 更多文章请扫码: