nginx架构简介和模块开发

高并发:多进程和异步非阻塞

Nginx采用异步非阻塞的方式,实现高并发。相比apache来说,性能有很大提高。

nginx架构简介和模块开发

对tcp和http的处理

TCP连接处理

Nginx作为tcp服务端

在 Nginx 中 connection 就是对 tcp 连接的封装,其中包括连接的
socket,读事件,写事件。结合一个 tcp 连接的生命周期,我们看看 Nginx
是如何处理一个连接的。

  1. 首先,Nginx 在启动时,会解析配置文件,得到需要监听的端口与 ip 地址,然后在
    Nginx 的 master 进程里面,先初始化好这个监控的 socket(创建 socket,设置
    addrreuse 等选项,绑定到指定的 ip 地址端口,再 listen),然后再 fork
    出多个子进程出来,然后子进程会竞争 accept 新的连接。此时,客户端就可以向
    Nginx 发起连接了。

  2. 当客户端与服务端通过三次握手建立好一个连接后,Nginx 的某一个子进程会 accept
    成功,得到这个建立好的连接的 socket,然后创建 Nginx 对连接的封装,即
    ngx_connection_t 结构体。

  3. 接着,设置读写事件处理函数并添加读写事件来与客户端进行数据的交换。

  4. 最后,Nginx 或客户端来主动关掉连接,到此,一个连接就寿终正寝了。

Nginx作为tcp客户端

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

连接池

Nginx 在实现时,是通过一个连接池来管理的,每个 worker
进程都有一个独立的连接池,连接池的大小是
worker_connections。这里的连接池里面保存的其实不是真实的连接,它只是一个
worker_connections 大小的一个 ngx_connection_t 结构的数组。并且,Nginx
会通过一个链表 free_connections 来保存所有的空闲
ngx_connection_t,每次获取一个连接时,就从空闲连接链表中获取一个,用完后,再放回空闲连接链表里面。

竞争

我们前面有说过一个客户端连接过来后,多个空闲的进程,会竞争这个连接,很容易看到,这种竞争会导致不公平。那么,如何解决这个问题呢?

首先,Nginx 的处理得先打开 accept_mutex 选项,此时,只有获得了 accept_mutex
的进程才会去添加accept事件,也就是说,Nginx会控制进程是否添加 accept 事件。Nginx
使用一个叫 ngx_accept_disabled 的变量来控制是否去竞争 accept_mutex
锁。在第一段代码中,计算 ngx_accept_disabled 的值,这个值是 Nginx
单进程的所有连接总数的八分之一,减去剩下的空闲连接数量,得到的这个
ngx_accept_disabled
有一个规律,当剩余连接数小于总连接数的八分之一时,其值才大于
0,而且剩余的连接数越小,这个值越大。再看第二段代码,当 ngx_accept_disabled
大于 0 时,不会去尝试获取 accept_mutex 锁,并且将 ngx_accept_disabled 减
1,于是,每次执行到此处时,都会去减 1,直到小于 0。不去获取 accept_mutex
锁,就是等于让出获取连接的机会,很显然可以看出,当空余连接越少时,ngx_accept_disable
越大,于是让出的机会就越多,这样其它进程获取锁的机会也就越大。不去
accept,自己的连接就控制下来了,其它进程的连接池就会得到利用,这样,Nginx
就控制了多进程间连接的平衡了。

Http请求处理

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

http请求-响应模型

http 请求是典型的请求-响应类型的的网络协议,而 http
是文本协议,所以我们在分析请求行与请求头,以及输出响应行与响应头,往往是一行一行的进行处理。如果我们自己来写一个
http
服务器,通常在一个连接建立好后,客户端会发送请求过来。然后我们读取一行数据,分析出请求行中包含的
method、uri、http_version 信息。然后再一行一行处理请求头,并根据请求 method
与请求头的信息来决定是否有请求体以及请求体的长度,然后再去读取请求体。得到请求后,我们处理请求产生需要输出的数据,然后再生成响应行,响应头以及响应体。在将响应发送给客户端之后,一个完整的请求就处理完了。当然这是最简单的
webserver 的处理方式,其实 Nginx
也是这样做的,只是有一些小小的区别,比如,当请求头读取完成后,就开始进行请求的处理了。Nginx
通过 ngx_http_request_t 来保存解析请求与输出响应相关的数据。

Nginx 将一个 http 请求的处理分为多个阶段, Nginx
的各种阶段会对请求进行处理,最后会调用 filter 来过滤数据,对数据进行加工,如
truncked 传输、gzip 压缩等。
nginx架构简介和模块开发

Http keepalive

当然,在 Nginx 中,对于 http1.0 与 http1.1 也是支持长连接的。

对于 http1.0 协议来说,如果响应头中有 content-length 头,则以 content-length
的长度就可以知道 body 的长度了,客户端在接收 body
时,就可以依照这个长度来接收数据,接收完后,就表示这个请求完成了。而如果没有
content-length 头,则客户端会一直接收数据,直到服务端主动断开连接,才表示 body
接收完了。

而对于 http1.1 协议来说,如果响应头中的 Transfer-encoding 为 chunked
传输,则表示 body 是流式输出,body
会被分成多个块,每块的开始会标识出当前块的长度,此时,body
不需要通过长度来指定。如果是非 chunked 传输,而且有 content-length,则按照
content-length 来接收数据。否则,如果是非 chunked,并且没有
content-length,则客户端接收数据,直到服务端主动断开连接。

当然,Nginx
不可能一直等待下去,如果客户端一直不发数据过来,岂不是一直占用这个连接?所以当
Nginx 设置了 keepalive
等待下一次的请求时,同时也会设置一个最大等待时间,这个时间是通过选项
keepalive_timeout 来配置的,如果配置为 0,则表示关掉 keepalive,此时,http
版本无论是 1.1 还是 1.0,客户端的 connection 不管是 close 还是
keepalive,都会强制为 close。

Http pipe

在 http1.1 中,引入了一种新的特性,即 pipeline。那么什么是 pipeline 呢?pipeline
其实就是流水线作业,它可以看作为 keepalive 的一种升华,因为 pipeline
也是基于长连接的,目的就是利用一个连接做多次请求。

如果客户端要提交多个请求,对于keepalive来说,那么第二个请求,必须要等到第一个请求的响应接收完全后,才能发起,这和
TCP 的停止等待协议是一样的,得到两个响应的时间至少为2*RTT。而对 pipeline
来说,客户端不必等到第一个请求处理完后,就可以马上发起第二个请求。得到两个响应的时间可能能够达到1*RTT。

Nginx 是直接支持 pipeline 的,但是,Nginx 对 pipeline
中的多个请求的处理却不是并行的,依然是一个请求接一个请求的处理,只是在处理第一个请求的时候,客户端就可以发起第二个请求。这样,Nginx
利用 pipeline 减少了处理完一个请求后,等待第二个请求的请求头数据的时间。

Nginx模块

Nginx
将各功能模块组织成一条链,当有请求到达的时候,请求依次经过这条链上的部分或者全部模块,进行处理。每个模块实现特定的功能。

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:
负载均衡模块,实现特定的算法,在众多的后端服务器中,选择一个服务器出来作为某个请求的转发服务器。

转载自:http://tengine.taobao.org/book/index.html