上篇介绍了一些基础概念,应该对Nginx有了一个大概轮廓,原计划直接讲配置,想了想还是继续介绍下模块和处理流程方面的东西,虽说平时涉及Nginx模块开发方面不多,但这些东西掌握了,配置自然也就记住了,或者是一看就明白其含义了,本文计划三个方面来讲:模块、请求流程(不是上篇工作流程)、配置。
模块
上篇讲了Nginx模块分类,本篇在此基础上细化一下,无可厚非,一个模块就是为了实现一个特定功能,除了core外,http和mail比较特殊,是在core的基础上抽象而来,实现了关于http和email相关协议的事件,其余的模块根据类型可以细化一下(图来自阿里团队Nginx开发从入门到精通):
event module | 搭建了独立于操作系统的事件处理机制的框架,及提供了各具体事件的处理。包括ngx_events_module, ngx_event_core_module和ngx_epoll_module等。nginx具体使用何种事件处理模块,这依赖于具体的操作系统和编译选项。 |
phase handler | 此类型的模块也被直接称为handler模块。主要负责处理客户端请求并产生待响应内容,比如ngx_http_static_module模块,负责客户端的静态页面请求处理并将对应的磁盘文件准备为响应内容输出。 |
output filter | 也称为filter模块,主要是负责对输出的内容进行处理,可以对输出进行修改。例如,可以实现对输出的所有html页面增加预定义的footbar一类的工作,或者对输出的图片的URL进行替换之类的工作。 |
upstream | upstream模块实现反向代理的功能,将真正的请求转发到后端服务器上,并从后端服务器上读取响应,发回客户端。upstream模块是一种特殊的handler,只不过响应内容不是真正由自己产生的,而是从后端服务器上读取的。 |
load-balancer | 负载均衡模块,实现特定的算法,在众多的后端服务器中,选择一个服务器出来作为某个请求的转发服务器。 |
一般需要第三方开发的模块是上表中粗体的handler、filter和balancer三个模块,平时在项目中使用最多的配置则是upstream模块,因为Nginx是作为反向代理服务器出现的,event模块是必选的模块,该模块选择取决于操作系统,handler和filter更多的是完成单机工作,而upstream完成了数据的接受、处理和转发,他是通过请求后端服务器得到内容,所以称为upstream(上游),理论上来讲upstream也属于handler,整个请求和响应整个过程已经封装到Nginx内部,所以upstream模块只需要开发若干回调函数,完成构造请求和解析响应等具体工作。filter模块,顾名思义是过滤响应头和内容的模块,可以对回复的头和内容进行处理。它的处理时间在获取回复内容之后,向用户发送响应之前。它的处理过程分为两个阶段,过滤HTTP回复的头部和主体,在这两个阶段可以分别对头部和主体进行修改。
负载均衡模块用于从”upstream”指令定义的后端主机列表中选取一台主机,nginx先使用负载均衡模块找到一台主机,再使用upstream模块实现与这台主机的交互。负载均衡模块的配置区集中在upstream{}块中,一般使用后端keepalive连接时,连接在使用完以后并不关闭,而是存放在一个队列中,新的请求只需要从队列中取出连接,当得到的连接地址连接不上或者请求对应的服务器得到异常响应,会尝试调用别的连接,这也是负载均衡的作用。
请求流程
一般Nginx所有配置都在conf目录下的nginx.conf下,在启动nginx时,先解析配置,初始化模块,根据配置文件初始化文件句柄、内存,然后是监听端口,创建master、worker子进程等,待以上步骤结束以后,nginx各个子进程开始各司其职,比如worker进程开始accept请求并按最新配置处理请求,cache-manager进程开始管理cache文件目录等等。
实际业务都是在worker进程中完成,worker进程中有ngx_worker_process_cycle()函数,执行无限循环,不断处理收到的来自客户端的请求,并进行处理,直到整个nginx服务被停止,这个函数中,一个请求的简单处理流程如下:
1.操作系统提供的机制(例如epoll, kqueue等)产生相关的事件。
2.接收和处理这些事件,如是接受到数据,则产生更高层的request对象。
3.处理request的header和body。
4.产生响应,并发送回客户端。
5.完成request的处理。
6.重新初始化定时器及其他事件。
以HTTP Request为例,一个HTTP Request的处理过程涉及到以下几个阶段。
1.初始化HTTP Request(读取来自客户端的数据,生成HTTP Request对象,该对象含有该请求所有的信息)。
2.处理请求头。
3.处理请求体。
4.如果有的话,调用与此请求(URL或者Location)关联的handler。
5.依次调用各phase handler进行处理。
当nginx读取到一个HTTP Request的header的时候,nginx首先查找与这个请求关联的虚拟主机的配置。如果找到了这个虚拟主机的配置,那么通常情况下,这个HTTP Request将会经过以下几个阶段的处理(phase handlers):
NGX_HTTP_POST_READ_PHASE:
读取请求内容阶段
NGX_HTTP_SERVER_REWRITE_PHASE:
Server请求地址重写阶段
NGX_HTTP_FIND_CONFIG_PHASE:
配置查找阶段:
NGX_HTTP_REWRITE_PHASE:
Location请求地址重写阶段
NGX_HTTP_POST_REWRITE_PHASE:
请求地址重写提交阶段
NGX_HTTP_PREACCESS_PHASE:
访问权限检查准备阶段
NGX_HTTP_ACCESS_PHASE:
访问权限检查阶段
NGX_HTTP_POST_ACCESS_PHASE:
访问权限检查提交阶段
NGX_HTTP_TRY_FILES_PHASE:
配置项try_files处理阶段
NGX_HTTP_CONTENT_PHASE:
内容产生阶段
NGX_HTTP_LOG_PHASE:
日志模块处理阶段
在内容产生阶段,为了给一个request产生正确的响应,nginx必须把这个request交给一个合适的content handler去处理。如果这个request对应的location在配置文件中被明确指定了一个content handler,那么nginx就可以通过对location的匹配,直接找到这个对应的handler,并把这个request交给这个content handler去处理。这样的配置指令包括像,perl,flv,proxy_pass,mp4等,一般情况下,我们自定义的模块,大多数是挂载在NGX_HTTP_CONTENT_PHASE阶段的
如果一个request对应的location并没有直接有配置的content handler,那么nginx依次尝试:
如果一个location里面有配置 random_index on,那么随机选择一个文件,发送给客户端。
如果一个location里面有配置 index指令,那么发送index指令指明的文件,给客户端。
如果一个location里面有配置 autoindex on,那么就发送请求地址对应的服务端路径下的文件列表给客户端。
如果这个request对应的location上有设置gzip_static on,那么就查找是否有对应的.gz文件存在,有的话,就发送这个给客户端(客户端支持gzip的情况下)。
请求的URI如果对应一个静态文件,static module就发送静态文件的内容到客户端。
内容产生阶段完成以后,生成的输出会被传递到filter模块去进行处理。filter模块也是与location相关的。所有的fiter模块都被组织成一条链。输出会依次穿越所有的filter,直到有一个filter模块的返回值表明已经处理完成。
当一个请求进来以后,nginx从NGX_HTTP_POST_READ_PHASE阶段开始依次执行每个阶段中所有handler。执行到 NGX_HTTP_CONTENT_PHASE阶段的时候,如果这个location有一个对应的content handler模块,那么就去执行这个content handler模块真正的处理函数。否则继续依次执行NGX_HTTP_CONTENT_PHASE阶段中所有content phase handlers,直到某个函数处理返回NGX_OK或者NGX_ERROR。
配置
nginx的配置系统由一个主配置文件和其他一些辅助的配置文件构成。这些配置文件均是纯文本文件,全部位于nginx安装目录下的conf目录下。
配置文件中以#开始的行,或者是前面有若干空格或者TAB,然后再跟#的行,都被认为是注释。
由于除主配置文件nginx.conf以外的文件都是在某些情况下才使用的,而只有主配置文件是在任何情况下都被使用的。所以在这里我们就以主配置文件为例,来解释nginx的配置系统。
在nginx.conf中,包含若干配置项。每个配置项由配置指令和指令参数2个部分构成。指令参数也就是配置指令对应的配置值,配置指令是一个字符串,可以用单引号或者双引号括起来,也可以不括。但是如果配置指令包含空格,一定要引起来,指令的参数使用一个或者多个空格或者TAB字符与指令分开。指令的参数有一个或者多个TOKEN串组成。TOKEN串之间由空格或者TAB键分隔。TOKEN串分为简单字符串或者是复合配置块。复合配置块即是由大括号括起来的一堆内容。一个复合配置块中可能包含若干其他的配置指令。
如果一个配置指令的参数全部由简单字符串构成,也就是不包含复合配置块,那么我们就说这个配置指令是一个简单配置项,否则称之为复杂配置项。例如下面这个是一个简单配置项:
error_page 500 502 503504 /50x.html;
nginx.conf中的配置信息,根据其逻辑上的意义,对它们进行了分类,也就是分成了多个作用域,或者称之为配置指令上下文。不同的作用域含有一个或者多个配置项。
main | nginx在运行时与具体业务功能(比如http服务或者email服务代理)无关的一些参数,比如工作进程数,运行的身份等。 |
http | 与提供http服务相关的一些配置参数。例如:是否使用keepalive啊,是否使用gzip进行压缩等。 |
server | http服务上支持若干虚拟主机。每个虚拟主机一个对应的server配置项,配置项里面包含该虚拟主机相关的配置。在提供mail服务的代理时,也可以建立若干server.每个server通过监听的地址来区分。 |
location | http服务中,某些特定的URL对应的一系列配置项。 |
| 实现email相关的SMTP/IMAP/POP3代理时,共享的一些配置项(因为可能实现多个代理,工作在多个监听地址上)。 |
指令上下文,可能有包含的情况出现。例如:通常http上下文和mail上下文一定是出现在main上下文里的。在一个上下文里,可能包含另外一种类型的上下文多次。例如:如果http服务,支持了多个虚拟主机,那么在http上下文里,就会出现多个server上下文。
我们来看一个示例配置:
user nobody;
worker_processes 1;
error_log logs/error.log info;
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name www.linuxidc.com;
access_log logs/linuxidc.access.log main;
location / {
index index.html;
root /var/www/linuxidc.com/htdocs;
}
}
server {
listen 80;
server_name www.Androidj.com;
access_log logs/androidj.access.log main;
location / {
index index.html;
root /var/www/androidj.com/htdocs;
}
}
}
mail {
auth_http 127.0.0.1:80/auth.php;
pop3_capabilities "TOP" "USER";
imap_capabilities "IMAP4rev1" "UIDPLUS";
server {
listen 110;
protocol pop3;
proxy on;
}
server {
listen 25;
protocol smtp;
proxy on;
smtp_auth login plain;
xclient off;
}
}
在这个配置中,上面提到个五种配置指令上下文都存在。
存在于main上下文中的配置指令如:user/worker_processes/error_log/events/http/mail;
存在于http上下文中的指令如:server;
存在于mail上下文中的指令如:Server/auth_http/imap_capabilities;
存在于server上下文中的配置指令如:Listen/server_name/access_log/location/protocol/proxy/smtp_auth/xclient;
存在于location上下文中的指令如:Index/root;
当然,这里只是一些示例。具体有哪些配置指令,以及这些配置指令可以出现在什么样的上下文中,需要参考nginx的使用文档。
该篇文章更多整理自阿里团队Nginx开发从入门到精通,大家可自行前往学习,内容更加翔实。