Nginx

Nginx简介
Nginx 是俄罗斯人编写的十分轻量级的 HTTP 服务器,Nginx,它的发音为“engine X”,是一个高性能的HTTP和反向代理服务器,同时也是一个 IMAP/POP3/SMTP 代理服务器。Nginx 是由俄罗斯人 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,它已经在该站点运行超过两年半了。Igor Sysoev 在建立的项目时,使用基于 BSD 许可。
Nginx 以事件驱动的方式编写,所以有非常好的性能,同时也是一个非常高效的反向代理、负载平衡。其拥有匹配 Lighttpd 的性能,同时还没有 Lighttpd 的内存泄漏问题,而且 Lighttpd 的 mod_proxy 也有一些问题并且很久没有更新。
现在,Igor 将源代码以类 BSD 许可证的形式发布。Nginx 因为它的稳定性、丰富的模块库、灵活的配置和低系统资源的消耗而闻名.业界一致认为它是 Apache2.2+mod_proxy_balancer 的轻量级代替者,不仅是因为响应静态页面的速度非常快,而且它的模块数量达到 Apache 的近 2/3。对 proxy 和 rewrite 模块的支持很彻底,还支持 mod_fcgi、ssl、vhosts ,适合用来做 mongrel clusters 的前端 HTTP 响应。

Nginx基本特性
1、Nginx 做为 HTTP 服务器,有以下几项基本特性:
2、处理静态文件,索引文件以及自动索引;打开文件描述符缓冲.
3、无缓存的反向代理加速,简单的负载均衡和容错.
4、FastCGI,简单的负载均衡和容错.
5、模块化的结构。包括 gzipping, byte ranges, chunked responses,以及 SSI-filter 等 filter。如果由 FastCGI 或其它代理服务器处理单页中存在的多个 SSI,则这项处理可以并行运行,而不需要相互等待。
6、支持 SSL 和 TLSSNI.
Nginx 具有很高的稳定性。其它 HTTP 服务器,当遇到访问的峰值,或者有人恶意发起慢速连接时,也很可能会导致服务器物理内存耗尽频繁交换,失去响应,只能重启服务器。例如当前 apache 一旦上到 200 个以上进程,web响应速度就明显非常缓慢了。而 Nginx 采取了分阶段资源分配技术,使得它的 CPU 与内存占用率非常低。Nginx 官方表示保持 10,000 个没有活动的连接,它只占 2.5M 内存,所以类似 DOS 这样的攻击对 Nginx 来说基本上是毫无用处的。就稳定性而言,Nginx 比 lighthttpd 更胜一筹。
Nginx 支持热部署。它的启动特别容易, 并且几乎可以做到 7*24 不间断运行,即使运行数个月也不需要重新启动。你还能够在不间断服务的情况下,对软件版本进行进行升级。

Nginx架构
Nginx 在启动后,在 unix 系统中会以 daemon 的方式在后台运行,后台进程包含一个 master 进程和多个 worker 进程。
Nginx 是以多进程的方式来工作的,Nginx 也支持多线程的方式,只是我们主流的方式还是多进程的方式,也是 Nginx 的默认方式。
Nginx 在启动后,会有一个 master 进程和多个 worker 进程。master 进程主要用来管理 worker 进程,包含:接收来自外界的信号,向各 worker 进程发送信号,监控 worker 进程的运行状态,当 worker 进程退出后(异常情况下),会自动重新启动新的 worker 进程。而基本的网络事件,则是放在 worker 进程中来处理了。多个 worker 进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求,只可能在一个 worker 进程中处理,一个 worker 进程,不可能处理其它进程的请求。worker 进程的个数是可以设置的,一般我们会设置与机器cpu核数一致,这里面的原因与 Nginx 的进程模型以及事件处理模型是分不开的。Nginx 的进程模型,可以由下图来表示:
在这里插入图片描述
在 Nginx 启动后,如果我们要操作 Nginx,只需要与 master 进程通信就行了。master 进程会接收来自外界发来的信号,再根据信号做不同的事情。
要控制 Nginx,只需要通过 kill 向 master 进程发送信号就行了。比如kill -HUP pid,则是告诉 Nginx,从容地重启 Nginx,我们一般用这个信号来重启 Nginx,或重新加载配置,因为是从容地重启,因此服务是不中断的。

master 进程在接收到 HUP 信号后的操作
1、首先 master 进程在接到信号后,会先重新加载配置文件
2、然后再启动新的 worker 进程
3、并向所有老的 worker 进程发送信号,告诉他们可以光荣退休了。
4、新的 worker 在启动后,就开始接收新的请求
5、老的 worker 在收到来自 master 的信号后,就不再接收新的请求,并且在当前进程中的所有未处理完的请求处理完成后,再退出。

Nginx 在 0.8 版本之后,引入了一系列命令行参数,来方便我们管理。
./nginx -s reload,就是来重启 Nginx
./nginx -s stop,就是来停止 Nginx 的运行。

worker 进程如何处理请求
1、worker 进程之间是平等的,每个进程,处理请求的机会也是一样的。
2、首先,每个 worker 进程都是从 master 进程 fork 过来,在 master 进程里面,先建立好需要 listen 的 socket(listenfd)之后,然后再 fork 出多个 worker 进程。
3、所有 worker 进程的 listenfd 会在新连接到来时变得可读
4、为保证只有一个进程处理该连接,所有 worker 进程在注册 listenfd 读事件前抢 accept_mutex
5、抢到互斥锁的那个进程注册 listenfd 读事件,在读事件里调用 accept 接受该连接。
6、当一个 worker 进程在 accept 这个连接之后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开连接,这样一个完整的请求就是这样的了。
7、一个请求,完全由 worker 进程来处理,而且只在一个 worker 进程中处理。

Nginx基本概念
connection
在 Nginx 中 connection 就是对 tcp 连接的封装,其中包括连接的 socket,读事件,写事件。利用 Nginx 封装的 connection,我们可以很方便的使用 Nginx 来处理与连接相关的事情。
Nginx 中的 http 请求的处理就是建立在 connection之上的,所以 Nginx 不仅可以作为一个web服务器,也可以作为邮件服务器。当然,利用 Nginx 提供的 connection,我们可以与任何后端服务打交道。

结合一个 tcp 连接的生命周期,我们看看 Nginx 是如何处理一个连接的
1、首先,Nginx 在启动时,会解析配置文件,得到需要监听的端口与 ip 地址
2、然后在 Nginx 的 master 进程里面,先初始化好这个监控的 socket(创建 socket,设置 addrreuse 等选项,绑定到指定的 ip 地址端口,再 listen)
3、然后再 fork 出多个子进程出来
4、然后子进程会竞争 accept 新的连接
5、此时,客户端就可以向 Nginx 发起连接了
6、当客户端与服务端通过三次握手建立好一个连接后,Nginx 的某一个子进程会 accept 成功,得到这个建立好的连接的 socket,然后创建 Nginx 对连接的封装,即 ngx_connection_t 结构体
7、接着,设置读写事件处理函数并添加读写事件来与客户端进行数据的交换
8、最后,Nginx 或客户端来主动关掉连接,到此,一个连接就寿终正寝了

Nginx 也是可以作为客户端来请求其它 server 的数据的(如 upstream 模块)
1、此时,与其它 server 创建的连接,也封装在 ngx_connection_t 中
2、作为客户端,Nginx 先获取一个 ngx_connection_t 结构体,然后创建 socket,并设置 socket 的属性( 比如非阻塞)
3、然后再通过添加读写事件,调用 connect/read/write 来调用连接
4、最后关掉连接,并释放 ngx_connection_t

在 Nginx 中,每个进程会有一个连接数的最大上限,这个上限与系统对 fd 的限制不一样。在操作系统中,通过 ulimit -n,我们可以得到一个进程所能够打开的 fd 的最大数,即 nofile,因为每个 socket 连接会占用掉一个 fd,所以这也会限制我们进程的最大连接数,当然也会直接影响到我们程序所能支持的最大并发数,当 fd 用完后,再创建 socket 时,就会失败。

Nginx 通过设置 worker_connections 来设置每个进程支持的最大连接数
1、如果该值大于 nofile,那么实际的最大连接数是 nofile,Nginx 会有警告
2、Nginx 在实现时,是通过一个连接池来管理的
3、每个 worker 进程都有一个独立的连接池,连接池的大小是 worker_connections
4、这里的连接池里面保存的其实不是真实的连接,它只是一个 worker_connections 大小的一个 ngx_connection_t 结构的数组
5、并且,Nginx 会通过一个链表 free_connections 来保存所有的空闲 ngx_connection_t
6、每次获取一个连接时,就从空闲连接链表中获取一个,用完后,再放回空闲连接链表里面
注:
在这里,很多人会误解 worker_connections 这个参数的意思,认为这个值就是 Nginx 所能建立连接的最大值。
其实不然,这个值是表示每个 worker 进程所能建立连接的最大值,所以,一个 Nginx 能建立的最大连接数,应该是worker_connections * worker_processes。
当然,这里说的是最大连接数,对于 HTTP 请求本地资源来说,能够支持的最大并发数量是worker_connections * worker_processes
而如果是 HTTP 作为反向代理来说,最大并发数量应该是worker_connections * worker_processes/2。因为作为反向代理服务器,每个并发会建立与客户端的连接和与后端服务的连接,会占用两个连接。

request
request,在 Nginx 中我们指的是 http 请求,具体到 Nginx 中的数据结构是ngx_http_request_t。
ngx_http_request_t 是对一个 http 请求的封装。
一个 http 请求,包含请求行、请求头、请求体、响应行、响应头、响应体。

http请求处理
http 请求是典型的请求-响应类型的的网络协议。http 是文本协议,所以我们在分析请求行与请求头,以及输出响应行与响应头,往往是一行一行的进行处理。
如果我们自己来写一个 http 服务器,最简单的 webserver 的处理方式:
1、通常在一个连接建立好后,客户端会发送请求过来
2、然后我们读取一行数据,分析出请求行中包含的 method、uri、http_version 信息
3、然后再一行一行处理请求头,并根据请求 method 与请求头的信息来决定是否有请求体以及请求体的长度,然后再去读取请求体
4、得到请求后,我们处理请求产生需要输出的数据,然后再生成响应行,响应头以及响应体
5、在将响应发送给客户端之后,一个完整的请求就处理完了

Nginx 是如何处理一个完整的请求的:
1、对于 Nginx 来说,一个请求是从ngx_http_init_request 开始的,在这个函数中,会设置读事件为 ngx_http_process_request_line,也就是说,接下来的网络事件,会由 ngx_http_process_request_line 来执行(从ngx_http_process_request_line 的函数名,我们可以看到,这就是来处理请求行的,正好与之前讲的,处理请求的第一件事就是处理请求行是一致的)
2、通过 ngx_http_read_request_header 来读取请求数据
3、然后调用 ngx_http_parse_request_line 函数来解析请求行(Nginx 为提高效率,采用状态机来解析请求行,而且在进行 method 的比较时,没有直接使用字符串比较,而是将四个字符转换成一个整型,然后一次比较以减少 cpu 的指令数,这个前面有说过。很多人可能很清楚一个请求行包含请求的方法,uri,版本,却不知道其实在请求行中,也是可以包含有 host 的。比如一个请求 GET http://www.taobao.com/uri HTTP/1.0 这样一个请求行也是合法的,而且 host 是 www.taobao.com,这个时候,Nginx 会忽略请求头中的 host 域,而以请求行中的这个为准来查找虚拟主机。另外,对于对于 http0.9 版来说,是不支持请求头的,所以这里也是要特别的处理。所以,在后面解析请求头时,协议版本都是 1.0 或 1.1。)
4、整个请求行解析到的参数,会保存到 ngx_http_request_t 结构当中
5、在解析完请求行后,Nginx 会设置读事件的 handler 为 ngx_http_process_request_headers
6、然后后续的请求就在 ngx_http_process_request_headers 中进行读取与解析
7、ngx_http_process_request_headers 函数用来读取请求头,跟请求行一样,还是调用 ngx_http_read_request_header 来读取请求头,调用 ngx_http_parse_header_line 来解析一行请求头,解析到的请求头会保存到 ngx_http_request_t 的域 headers_in 中(headers_in 是一个链表结构,保存所有的请求头。而 HTTP 中有些请求是需要特别处理的,这些请求头与请求处理函数存放在一个映射表里面,即 ngx_http_headers_in,在初始化时,会生成一个 hash 表,当每解析到一个请求头后,就会先在这个 hash 表中查找,如果有找到,则调用相应的处理函数来处理这个请求头。比如:Host 头的处理函数是 ngx_http_process_host。)
8、当 Nginx 解析到两个回车换行符时,就表示请求头的结束,此时就会调用 ngx_http_process_request 来处理请求了
9、ngx_http_process_request 会设置当前的连接的读写事件处理函数为 ngx_http_request_handler,然后再调用 ngx_http_handler 来真正开始处理一个完整的http请求(这里可能比较奇怪,读写事件处理函数都是ngx_http_request_handler,其实在这个函数中,会根据当前事件是读事件还是写事件,分别调用 ngx_http_request_t 中的 read_event_handler 或者是 write_event_handler。)
10、此时,我们的请求头已经读取完成了
11、这里面我们设置 read_event_handler 为 ngx_http_block_reading,即不读取数据了(之前有说过,Nginx 的做法是先不读取请求 body)
12、真正开始处理数据,是在 ngx_http_handler 这个函数里面,这个函数会设置 write_event_handler 为 ngx_http_core_run_phases,并执行 ngx_http_core_run_phases 函数(ngx_http_core_run_phases 这个函数将执行多阶段请求处理,Nginx 将一个 http 请求的处理分为多个阶段,那么这个函数就是执行这些阶段来产生数据。-》因为 ngx_http_core_run_phases 最后会产生数据,所以我们就很容易理解,为什么设置写事件的处理函数为 ngx_http_core_run_phases 了。-》函数的调用逻辑,需要明白最终是调用 ngx_http_core_run_phases 来处理请求,产生的响应头会放在 ngx_http_request_t 的 headers_out 中。)
12、Nginx 的各种阶段会对请求进行处理,最后会调用 filter 来过滤数据,对数据进行加工,如 truncked 传输、gzip 压缩等。(这里的 filter 包括 header filter 与 body filter,即对响应头或响应体进行处理。filter 是一个链表结构,分别有 header filter 与 body filter,先执行 header filter 中的所有 filter,然后再执行 body filter 中的所有 filter。在 header filter 中的最后一个 filter,即 ngx_http_header_filter,这个 filter 将会遍历所有的响应头,最后需要输出的响应头在一个连续的内存,然后调用 ngx_http_write_filter 进行输出。ngx_http_write_filter 是 body filter 中的最后一个,所以 Nginx 首先的 body 信息,在经过一系列的 body filter 之后,最后也会调用 ngx_http_write_filter 来进行输出。)
在这里插入图片描述
keepalive
在 Nginx 中,对于 http1.0 与 http1.1 也是支持长连接的。
http 请求是基于 TCP 协议之上的,那么,当客户端在发起请求前,需要先与服务端建立 TCP 连接,而每一次的 TCP 连接是需要三次握手来确定的,如果客户端与服务端之间网络差一点,这三次交互消费的时间会比较多,而且三次交互也会带来网络流量。当然,当连接断开后,也会有四次的交互,当然对用户体验来说就不重要了。而 http 请求是请求应答式的,如果我们能知道每个请求头与响应体的长度,那么我们是可以在一个连接上面执行多个请求的,这就是所谓的长连接,但前提条件是我们先得确定请求头与响应体的长度。对于请求来说,如果当前请求需要有body,如 POST 请求,那么 Nginx 就需要客户端在请求头中指定 content-length 来表明 body 的大小,否则返回 400 错误。也就是说,请求体的长度是确定的。

pipe
pipeline 其实就是流水线作业,它可以看作为 keepalive 的一种升华,因为 pipeline 也是基于长连接的,目的就是利用一个连接做多次请求。

lingering_close
lingering_close,字面意思就是延迟关闭,也就是说,当 Nginx 要关闭连接时,并非立即关闭连接,而是先关闭 tcp 连接的写,再等待一段时间后再关掉连接的读。
注意:
1、Nginx 会将整个请求头都放在一个 buffer 里面,这个 buffer 的大小通过配置项 client_header_buffer_size 来设置
2、如果用户的请求头太大,这个 buffer 装不下,那 Nginx 就会重新分配一个新的更大的 buffer 来装请求头
3、这个大 buffer 可以通过 large_client_header_buffers 来设置,这个 large_buffer 这一组 buffer,比如配置 48k,就是表示有四个 8k 大小的 buffer 可以用。
4、注意,为了保存请求行或请求头的完整性,一个完整的请求行或请求头,需要放在一个连续的内存里面,所以,一个完整的请求行或请求头,只会保存在一个 buffer 里面。
5、这样,如果请求行大于一个 buffer 的大小,就会返回 414 错误,如果一个请求头大小大于一个 buffer 大小,就会返回 400 错误。

Nginx 的配置系统
Nginx 的配置系统由一个主配置文件和其他一些辅助的配置文件构成。这些配置文件均是纯文本文件,全部位于Nginx 安装目录下的 conf 目录下。
配置文件中以#开始的行,或者是前面有若干空格或者 TAB,然后再跟#的行,都被认为是注释。
由于除主配置文件 nginx.conf 以外的文件都是在某些情况下才使用的,而只有主配置文件是在任何情况下都被使用的。
在 nginx.conf 中,包含若干配置项。每个配置项由配置指令和指令参数 2 个部分构成。指令参数也就是配置指令对应的配置值。

指令概述
配置指令是一个字符串,可以用单引号或者双引号括起来,也可以不括。但是如果配置指令包含空格,一定要引起来。

指令参数
指令的参数使用一个或者多个空格或者 TAB 字符与指令分开。指令的参数有一个或者多个 TOKEN 串组成。TOKEN 串之间由空格或者 TAB 键分隔。
TOKEN 串分为简单字符串或者是复合配置块。复合配置块即是由大括号括起来的一堆内容。一个复合配置块中可能包含若干其他的配置指令。(如果一个配置指令的参数全部由简单字符串构成,也就是不包含复合配置块,那么我们就说这个配置指令是一个简单配置项,否则称之为复杂配置项。)
对于简单配置,配置项的结尾使用分号结束。对于复杂配置项,包含多个 TOKEN 串的,一般都是简单 TOKEN 串放在前面,复合配置块一般位于最后,而且其结尾,并不需要再添加分号。

error_page   500 502 503 504  /50x.html;
location / {
    root   /home/jizhao/nginx-book/build/html;
    index  index.html index.htm;
}

指令上下文
nginx.conf 中的配置信息,根据其逻辑上的意义,对它们进行了分类,也就是分成了多个作用域,或者称之为配置指令上下文。不同的作用域含有一个或者多个配置项。
当前 Nginx 支持的几个指令上下文:
1、main: Nginx 在运行时与具体业务功能(比如http服务或者email服务代理)无关的一些参数,比如工作进程数,运行的身份等。
2、http: 与提供 http 服务相关的一些配置参数。例如:是否使用 keepalive 啊,是否使用gzip进行压缩等。
3、server: http 服务上支持若干虚拟主机。每个虚拟主机一个对应的 server 配置项,配置项里面包含该虚拟主机相关的配置。在提供 mail 服务的代理时,也可以建立若干 server,每个 server 通过监听的地址来区分。
4、location: http 服务中,某些特定的URL对应的一系列配置项。
mail: 实现 email 相关的 SMTP/IMAP/POP3 代理时,共享的一些配置项(因为可能实现多个代理,工作在多个监听地址上)。
注意:
1】指令上下文,可能有包含的情况出现。例如:通常 http 上下文和 mail 上下文一定是出现在 main 上下文里的。
2】在一个上下文里,可能包含另外一种类型的上下文多次。例如:如果 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;
    }
}

Nginx 的模块化体系结构
Nginx 提供了 Web 服务器的基础功能,同时提供了 Web 服务反向代理,Email 服务反向代理功能。
Nginx core实现了底层的通讯协议,为其他模块和 Nginx 进程构建了基本的运行时环境,并且构建了其他各模块的协作基础。
除此之外,或者说大部分与协议相关的,或者应用相关的功能都是在这些模块中所实现的。
本节使用 Nginx core 来称呼 Nginx 的核心功能部分。

模块概述
Nginx 将各功能模块组织成一条链,当有请求到达的时候,请求依次经过这条链上的部分或者全部模块,进行处理。每个模块实现特定的功能。例如,实现对请求解压缩的模块,实现 SSI 的模块,实现与上游服务器进行通讯的模块,实现与 FastCGI 服务进行通讯的模块。

http 模块和 mail 模块
有两个模块比较特殊,他们居于 Nginx core 和各功能模块的中间。这两个模块就是 http 模块和 mail 模块。这 2 个模块在 Nginx core 之上实现了另外一层抽象,处理与 HTTP 协议和 Email 相关协议(SMTP/POP3/IMAP)有关的事件,并且确保这些事件能被以正确的顺序调用其他的一些功能模块。

模块的分类
Nginx 的模块根据其功能基本上可以分为以下几种类型:
1、event module: 搭建了独立于操作系统的事件处理机制的框架,及提供了各具体事件的处理。包括 ngx_events_module, ngx_event_core_module和ngx_epoll_module 等。Nginx 具体使用何种事件处理模块,这依赖于具体的操作系统和编译选项。
2、phase handler: 此类型的模块也被直接称为 handler 模块。主要负责处理客户端请求并产生待响应内容,比如 ngx_http_static_module 模块,负责客户端的静态页面请求处理并将对应的磁盘文件准备为响应内容输出。
3、output filter: 也称为 filter 模块,主要是负责对输出的内容进行处理,可以对输出进行修改。例如,可以实现对输出的所有 html 页面增加预定义的 footbar 一类的工作,或者对输出的图片的 URL 进行替换之类的工作。
4、upstream: upstream 模块实现反向代理的功能,将真正的请求转发到后端服务器上,并从后端服务器上读取响应,发回客户端。upstream 模块是一种特殊的 handler,只不过响应内容不是真正由自己产生的,而是从后端服务器上读取的。
5、load-balancer: 负载均衡模块,实现特定的算法,在众多的后端服务器中,选择一个服务器出来作为某个请求的转发服务器。

Nginx 的请求处理
Nginx 使用一个多进程模型来对外提供服务,其中一个 master 进程,多个 worker 进程。master 进程负责管理 Nginx 本身和其他 worker 进程。
所有实际上的业务处理逻辑都在 worker 进程。worker 进程中有一个函数,执行无限循环,不断处理收到的来自客户端的请求,并进行处理,直到整个 Nginx 服务被停止。
worker 进程中,ngx_worker_process_cycle()函数就是这个无限循环的处理函数。在这个函数中,一个请求的简单处理流程如下:
1、操作系统提供的机制(例如 epoll, kqueue 等)产生相关的事件。
2、接收和处理这些事件,如是接受到数据,则产生更高层的 request 对象。
3、处理 request 的 header 和 body。
4、产生响应,并发送回客户端。
5、完成 request 的处理。
6、重新初始化定时器及其他事件。

请求的处理流程
从 Nginx 的内部来看,一个 HTTP Request 的处理过程涉及到以下几个阶段。
1)初始化 HTTP Request(读取来自客户端的数据,生成 HTTP Request 对象,该对象含有该请求所有的信息)。
2)处理请求头。
3)处理请求体。
4)如果有的话,调用与此请求(URL 或者 Location)关联的 handler。
5)依次调用各 phase handler 进行处理。

phase handler 概念:
1、phase 字面的意思,就是阶段。所以 phase handlers 也就好理解了,就是包含若干个处理阶段的一些 handler。
2、在每一个阶段,包含有若干个 handler,在处理到某个阶段的时候,依次调用该阶段的 handler 对 HTTP Request 进行处理。
3、通常情况下,一个 phase handler 对这个 request 进行处理,并产生一些输出。通常 phase handler 是与定义在配置文件中的某个 location 相关联的。
4、一个 phase handler 通常执行以下几项任务:
1)获取 location 配置。
2)产生适当的响应。
3)发送 response header。
4)发送 response body。
5、当 Nginx 读取到一个 HTTP Request 的 header 的时候,Nginx 首先查找与这个请求关联的虚拟主机的配置。如果找到了这个虚拟主机的配置,那么通常情况下,这个 HTTP Request 将会经过以下几个阶段的处理(phase handlers):
1)NGX_HTTP_POST_READ_PHASE: 读取请求内容阶段
2)NGX_HTTP_SERVER_REWRITE_PHASE: Server 请求地址重写阶段
3)NGX_HTTP_FIND_CONFIG_PHASE: 配置查找阶段:
4)NGX_HTTP_REWRITE_PHASE: Location请求地址重写阶段
5)NGX_HTTP_POST_REWRITE_PHASE: 请求地址重写提交阶段
6)NGX_HTTP_PREACCESS_PHASE: 访问权限检查准备阶段
7)NGX_HTTP_ACCESS_PHASE: 访问权限检查阶段
8)NGX_HTTP_POST_ACCESS_PHASE: 访问权限检查提交阶段
9)NGX_HTTP_TRY_FILES_PHASE: 配置项 try_files 处理阶段
10)NGX_HTTP_CONTENT_PHASE: 内容产生阶段
11)NGX_HTTP_LOG_PHASE: 日志模块处理阶段
在内容产生阶段,为了给一个 request 产生正确的响应,Nginx 必须把这个 request 交给一个合适的 content handler 去处理。
如果这个 request 对应的 location 在配置文件中被明确指定了一个 content handler,那么Nginx 就可以通过对 location 的匹配,直接找到这个对应的 handler,并把这个 request 交给这个 content handler 去处理。这样的配置指令包括像,perl,flv,proxy_pass,mp4等。
如果一个 request 对应的 location 并没有直接有配置的 content handler,那么 Nginx 依次尝试:
1)如果一个 location 里面有配置 random_index on,那么随机选择一个文件,发送给客户端。
2)如果一个 location 里面有配置 index 指令,那么发送 index 指令指明的文件,给客户端。
3)如果一个 location 里面有配置 autoindex on,那么就发送请求地址对应的服务端路径下的文件列表给客户端。
4)如果这个 request 对应的 location 上有设置 gzip_static on,那么就查找是否有对应的.gz文件存在,有的话,就发送这个给客户端(客户端支持 gzip 的情况下)。
5)请求的 URI 如果对应一个静态文件,static module 就发送静态文件的内容到客户端。
内容产生阶段完成以后,生成的输出会被传递到 filter 模块去进行处理。filter 模块也是与 location 相关的。所有的 fiter 模块都被组织成一条链。输出会依次穿越所有的 filter,直到有一个 filter 模块的返回值表明已经处理完成。

几个常见的 filter 模块,例如:
1)server-side includes。
2)XSLT filtering。
3)图像缩放之类的。
4)gzip 压缩。

在所有的 filter 中,需要关注的几个 filter 模块。按照调用的顺序依次说明如下:
1)write: 写输出到客户端,实际上是写到连接对应的 socket 上。
2)postpone: 这个 filter 是负责 subrequest 的,也就是子请求的。
3)copy: 将一些需要复制的 buf(文件或者内存)重新复制一份然后交给剩余的 body filter 处理。

handler 模块简介
在Nginx 中,基本上作为第三方开发者最可能开发的就是三种类型的模块,即 handler,filter 和 load-balancer。Handler 模块就是接受来自客户端的请求并产生输出的模块。有些地方说 upstream 模块实际上也是一种 handler 模块,只不过它产生的内容来自于从后端服务器获取的,而非在本机产生的。
配置文件中使用 location 指令可以配置 content handler 模块,当 Nginx 系统启动的时候,每个 handler 模块都有一次机会把自己关联到对应的 location上。如果有多个 handler 模块都关联了同一个 location,那么实际上只有一个 handler 模块真正会起作用。当然大多数情况下,模块开发人员都会避免出现这种情况。
handler 模块处理的结果通常有三种情况: 处理成功,处理失败(处理的时候发生了错误)或者是拒绝去处理。在拒绝处理的情况下,这个 location 的处理就会由默认的 handler 模块来进行处理。-》例如,当请求一个静态文件的时候,如果关联到这个 location 上的一个 handler 模块拒绝处理,就会由默认的 ngx_http_static_module 模块进行处理,该模块是一个典型的 handler 模块。
handler 模块必须提供一个真正的处理函数,这个函数负责对来自客户端请求的真正处理。这个函数的处理,既可以选择自己直接生成内容,也可以选择拒绝处理,由后续的 handler 去进行处理,或者是选择丢给后续的 filter 进行处理。来看一下这个函数的原型申明。

typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);

r 是 http 请求。该函数处理成功返回 NGX_OK,处理发生错误返回 NGX_ERROR,拒绝处理(留给后续的 handler 进行处理)返回 NGX_DECLINE。 返回 NGX_OK 也就代表给客户端的响应已经生成好了,否则返回 NGX_ERROR 就发生错误了。

handler 模块的挂载
handler 模块真正的处理函数通过两种方式挂载到处理过程中,一种方式就是按处理阶段挂载;另外一种挂载方式就是按需挂载。

按处理阶段挂载
为了更精细地控制对于客户端请求的处理过程,Nginx 把这个处理过程划分成了 11 个阶段。从前到后,依次列举如下:
1)NGX_HTTP_POST_READ_PHASE: 读取请求内容阶段
2)NGX_HTTP_SERVER_REWRITE_PHASE: Server 请求地址重写阶段
3)NGX_HTTP_FIND_CONFIG_PHASE: 配置查找阶段:
4)NGX_HTTP_REWRITE_PHASE: Location 请求地址重写阶段
5)NGX_HTTP_POST_REWRITE_PHASE: 请求地址重写提交阶段
6)NGX_HTTP_PREACCESS_PHASE: 访问权限检查准备阶段
7)NGX_HTTP_ACCESS_PHASE: 访问权限检查阶段
8)NGX_HTTP_POST_ACCESS_PHASE: 访问权限检查提交阶段
9)NGX_HTTP_TRY_FILES_PHASE: 配置项 try_files 处理阶段
10)NGX_HTTP_CONTENT_PHASE: 内容产生阶段
11)NGX_HTTP_LOG_PHASE: 日志模块处理阶段
一般情况下,自定义的模块,大多数是挂载在 NGX_HTTP_CONTENT_PHASE 阶段的。挂载的动作一般是在模块上下文调用的 postconfiguration 函数中。
注意:有几个阶段是特例,它不调用挂载地任何的handler,也就是你就不用挂载到这几个阶段了:
NGX_HTTP_FIND_CONFIG_PHASE
NGX_HTTP_POST_ACCESS_PHASE
NGX_HTTP_POST_REWRITE_PHASE
NGX_HTTP_TRY_FILES_PHASE
所以其实真正是有 7 个 phase 可以去挂载 handler。
挂载的代码如下(摘自 hello module):

static ngx_int_t
ngx_http_hello_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;
 
    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
 
    h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }
 
    *h = ngx_http_hello_handler;
 
    return NGX_OK;
}

使用这种方式挂载的 handler 也被称为 content phase handlers。

按需挂载
以这种方式挂载的 handler 也被称为 content handler。
当一个请求进来以后,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。
换句话说,当某个 location 处理到 NGX_HTTP_CONTENT_PHASE 阶段时,如果有 content handler 模块,那么 NGX_HTTP_CONTENT_PHASE 挂载的所有 content phase handlers 都不会被执行了。
但是使用这个方法挂载上去的 handler 有一个特点是必须在 NGX_HTTP_CONTENT_PHASE 阶段才能执行到。如果你想自己的 handler 在更早的阶段执行,那就不要使用这种挂载方式。
在什么情况会使用这种方式来挂载呢? -》 一般情况下,某个模块对某个 location 进行了处理以后,发现符合自己处理的逻辑,而且也没有必要再调用 NGX_HTTP_CONTENT_PHASE 阶段的其它 handler 进行处理的时候,就动态挂载上这个 handler。
使用这种挂载方式的具体例子(摘自 Emiller’s Guide To Nginx Module Development)。

static char *
ngx_http_circle_gif(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;
 
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_circle_gif_handler;
 
    return NGX_CONF_OK;
}

handler 的编写步骤
实现一个 handler 的步骤:
1、编写模块基本结构。包括模块的定义,模块上下文结构,模块的配置结构等。
2、实现 handler 的挂载函数。根据模块的需求选择正确的挂载方式。
3、编写 handler 处理函数。模块的功能主要通过这个函数来完成。

过滤模块
过滤(filter)模块是过滤响应头和内容的模块,可以对回复的头和内容进行处理。它的处理时间在获取回复内容之后,向用户发送响应之前。它的处理过程分为两个阶段,过滤 HTTP 回复的头部和主体,在这两个阶段可以分别对头部和主体进行修改。

upstream 模块
handler、filter,利用这两类模块,可以使 Nginx 轻松完成任何单机工作。
upstream 模块,将使 Nginx 跨越单机的限制,完成网络数据的接收、处理和转发。
upstream 模块使用的是 handler 模块的接入方式。
数据转发功能,为 Nginx 提供了跨越单机的横向处理能力,使 Nginx 摆脱只能为终端节点提供单一功能的限制,而使它具备了网路应用级别的拆分、封装和整合的战略功能。作为附带,Nginx 的配置系统提供的层次化和松耦合使得系统的扩展性也达到比较高的程度。

upstream 模块接口
从本质上说,upstream 属于 handler,只是他不产生自己的内容,而是通过请求后端服务器得到内容,所以才称为 upstream(上游)。
请求并取得响应内容的整个过程已经被封装到 Nginx 内部,所以 upstream 模块只需要开发若干回调函数,完成构造请求和解析响应等具体的工作。
回调函数如下表所示:

SN描述
create_request生成发送到后端服务器的请求缓冲(缓冲链),在初始化 upstream 时使用。
reinit_request在某台后端服务器出错的情况,Nginx会尝试另一台后端服务器。Nginx 选定新的服务器以后,会先调用此函数,以重新初始化 upstream 模块的工作状态,然后再次进行 upstream 连接。
process_header处理后端服务器返回的信息头部。所谓头部是与 upstreamserver 通信的协议规定的,比如 HTTP 协议的 header 部分,或者 memcached 协议的响应状态部分。
abort_request在客户端放弃请求时被调用。不需要在函数中实现关闭后端服务器连接的功能,系统会自动完成关闭连接的步骤,所以一般此函数不会进行任何具体工作。
finalize_request正常完成与后端服务器的请求后调用该函数,与 abort_request 相同,一般也不会进行任何具体工作。
input_filter处理后端服务器返回的响应正文。Nginx 默认的 input_filter 会将收到的内容封装成为缓冲区链 ngx_chain。该链由 upstream 的 out_bufs 指针域定位,所以开发人员可以在模块以外通过该指针 得到后端服务器返回的正文数据。memcached 模块实现了自己的 input_filter,在后面会具体分析这个模块。
input_filter_init初始化 input filter 的上下文。Nginx 默认的 input_filter_init 直接返回。

memcached 模块分析
memcache 是一款高性能的分布式 cache 系统,得到了非常广泛的应用。memcache 定义了一套私有通信协议,使得不能通过 HTTP 请求来访问 memcache。但协议本身简单高效,而且 memcache 使用广泛,所以大部分现代开发语言和平台都提供了 memcache 支持,方便开发者使用 memcache。
Nginx 提供了 ngx_http_memcached 模块,提供从 memcache 读取数据的功能,而不提供向 memcache 写数据的功能。

负载均衡模块
负载均衡模块用于从upstream指令定义的后端主机列表中选取一台主机。Nginx 先使用负载均衡模块找到一台主机,再使用 upstream 模块实现与这台主机的交互。
如果需要使用 ip hash 的负载均衡算法。我们需要写一个类似下面的配置:

upstream test {
    ip_hash;
 
    server 192.168.0.1;
    server 192.168.0.2;
}

从配置我们可以看出负载均衡模块的使用场景:
1、核心指令ip_hash只能在 upstream {}中使用。这条指令用于通知 Nginx 使用 ip hash 负载均衡算法。如果没加这条指令,Nginx 会使用默认的 round robin 负载均衡模块。
2、upstream {}中的指令可能出现在server指令前,可能出现在server指令后,也可能出现在两条server指令之间。-》 ip_hash 指令的确能影响到配置的解析。

core 模块
Nginx 的启动模块
启动模块从启动 Nginx 进程开始,做了一系列的初始化工作,源代码位于src/core/nginx.c,从 main 函数开始:
1、时间、正则、错误日志、ssl 等初始化
2、读入命令行参数
3、OS 相关初始化
4、读入并解析配置
5、核心模块初始化
6、创建各种暂时文件和目录
7、创建共享内存
8、打开 listen 的端口
9、所有模块初始化
10、启动 worker 进程

参考链接:
http://wiki.jikexueyuan.com/project/nginx/nginx-framework.html
jianshu.com/p/849343f679aa
https://www.cnblogs.com/zengfp/p/9897026.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值