简介

nginx是一个高性能的http和反向代理web服务器,同时也提供了IMAP、POP3、SMTP服务。。

  • 处理静态文件,索引文件以及自动索引;打开文件描述符缓冲.
  • 无缓存的反向代理加速,简单的负载均衡和容错.
  • FastCGI,简单的负载均衡和容错.
  • 模块化的结构。包括 gzipping, byte ranges, chunked responses,以及 SSI-filter 等 filter。如果由 FastCGI 或其它代理服务器处理单页中存在的多个 SSI,则这项处理可以并行运行,而不需要相互等待。
  • 支持 SSL 和 TLSSNI.

nginx架构

Nginx 在启动后,在 unix 系统中会以 daemon (守护进程)的方式在后台运行,后台进程包含一个 master 进程和多个 worker 进程。我们也可以手动地关掉后台模式,让 Nginx 在前台运行,并且通过配置让 Nginx 取消 master 进程,从而可以使 Nginx 以单进程方式运行。实际上,Nginx 是以多进程的方式来工作的,当然 Nginx 也是支持多线程的方式的,只是我们主流的方式还是多进程的方式,也是 Nginx 的默认方式。
刚才讲到,Nginx 在启动后,会有一个 master 进程和多个 worker 进程。master 进程主要用来管理 worker 进程,包含:接收来自外界的信号,向各 worker 进程发送信号,监控 worker 进程的运行状态,当 worker 进程退出后(异常情况下),会自动重新启动新的 worker 进程。而基本的网络事件,则是放在 worker 进程中来处理了。多个 worker 进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求,只可能在一个 worker 进程中处理,一个 worker 进程,不可能处理其它进程的请求。worker 进程的个数是可以设置的,一般我们会设置与机器cpu核数一致,这里面的原因与 Nginx 的进程模型以及事件处理模型是分不开的。Nginx 的进程模型,可以由下图来表示:
image.png

怎样操作nginx呢?

master 进程会接收来自外界发来的信号,再根据信号做不同的事情。所以我们要控制 Nginx,只需要通过 kill 向 master 进程发送信号就行了。比如kill -HUP pid,则是告诉 Nginx,从容地重启 Nginx,我们一般用这个信号来重启 Nginx,或重新加载配置,因为是从容地重启,因此服务是不中断的。master 进程在接收到 HUP 信号后是怎么做的呢?首先 master 进程在接到信号后,会先重新加载配置文件,然后再启动新的 worker 进程,并向所有老的 worker 进程发送信号,告诉他们可以光荣退休了。新的 worker 在启动后,就开始接收新的请求,而老的 worker 在收到来自 master 的信号后,就不再接收新的请求,并且在当前进程中的所有未处理完的请求处理完成后,再退出。
Nginx在0.8版本之后引入了./nginx -s reload重启nginx。执行命令时,我们是启动一个新的 Nginx 进程,而新的 Nginx 进程在解析到 reload 参数后,就知道我们的目的是控制 Nginx 来重新加载配置文件了,它会向 master 进程发送信号,然后接下来的动作,就和我们直接向 master 进程发送信号一样了。

操作了nginx之后,nginx内部有做了什么事情呢?

当我们提供 80 端口的 http 服务时,一个连接请求过来,每个进程都有可能处理这个连接。首先,每个 worker 进程都是从 master 进程 fork 过来,在 master 进程里面,先建立好需要 listen 的 socket(listenfd)之后,然后再 fork 出多个 worker 进程。所有 worker 进程的 listenfd 会在新连接到来时变得可读,为保证只有一个进程处理该连接,所有 worker 进程在注册 listenfd 读事件前抢 accept_mutex,抢到互斥锁的那个进程注册 listenfd 读事件,在读事件里调用 accept 接受该连接。当一个 worker 进程在 accept 这个连接之后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开连接,这样一个完整的请求就是这样的了。我们可以看到,一个请求,完全由 worker 进程来处理,而且只在一个 worker 进程中处理。

nginx如何处理事件

nginx采用【异步非阻塞】的方式处理请求。
这种事件处理方式是有很大的优势的,不需要创建线程,每个请求占用的内存也很少,没有上下文切换,事件处理非常的轻量级。并发数再多也不会导致无谓的资源浪费(上下文切换)。更多的并发数,只是会占用更多的内存而已。在 24G 内存的机器上,处理的并发请求数达到过 200 万。现在的网络服务器基本都采用这种方式,这也是nginx性能高效的主要原因。

基础概念

connection

对TCP协议的封装,包括连接的 socket,读事件,写事件。
如何处理一个连接?
1、解析nginx.conf配置文件,得到需要监听的ip与端口地址
2、在master进程中初始化这个监控的 socket,fork多个子进程进行竞争。此时客户端可以向nginx发起连接了。
3、三次握手建立连接之后,master下的某个worker进程accept成功,然后创建 Nginx 对连接的封装,即 ngx_connection_t 结构体。
4、设置读写事件处理函数并添加读写事件来与客户端进行数据的交换
5、nginx或者客户端主动关闭连接

request

在nginx中指的是http请求,具体到nginx中的数据结构就是ngx_http_request_t。nginx_http_request_t是对一个HTTP请求的封装,Nginx 通过 ngx_http_request_t 来保存解析请求与输出响应相关的数据。
典型的http请求是这样完成的:
在一个连接建立好后,客户端会发送请求过来;分析出请求行中包含的 method、uri、http_version 信息;然后再一行一行处理请求头,并根据请求 method 与请求头的信息来决定是否有请求体以及请求体的长度,然后再去读取请求体;得到请求后,我们处理请求产生需要输出的数据,然后再生成响应行,响应头以及响应体。
如何处理一个为完整的nginx请求:
从ngx_http_init_request 开始:
1、这个函数会设置读事件为ngx_http_process_request_line,处理请求行,与http请求处理的第一件事就是处理请i去行是一致的。ngx_http_read_request_header 来读取请求数据,然后调用 ngx_http_parse_request_line 函数来解析请求行。整个请求行解析到的参数,会保存到 ngx_http_request_t 结构当中。
2、解析完请求行的数据之后,nginx会设置读取事件的handler为ngx_http_process_request_headers,解析请求头,跟解析请求行一样,还是调用ngx_http_read_request_header 来读取请求头,ngx_http_parse_header_line 来解析一行请求头,解析到的请求头会保存到 ngx_http_request_t 的域 headers_in 中。当 Nginx 解析到两个回车换行符时,就表示请求头的结束。
3、请求头结束调用:ngx_http_process_request设置当前的连接的读写事件处理函数为 ngx_http_request_handler,然后再调用 ngx_http_handler 来真正开始处理一个完整的http请求
真正开始处理数据,是在 ngx_http_handler 这个函数里面,这个函数会设置 write_event_handler 为 ngx_http_core_run_phases,并执行 ngx_http_core_run_phases 函数。ngx_http_core_run_phases 这个函数将执行多阶段请求处理,Nginx 将一个 http 请求的处理分为多个阶段,那么这个函数就是执行这些阶段来产生数据。

image.png

keepalive

在http1.0和1.1中支持【长连接】。
如果客户端的请求头中的connection: false表示关闭长连接/keepalive表示客户端需要打开长连接,没有connection字段,如果是http1.1则默认为keepalive。http1.0默认为close。
如果结果是keepalive,那么nginx就会设置连接的keepalive属性,等待下一次请求,同时设置一个最大等待时间,通过选项keepalive_timeout配置。

pipe

pipeline流水线作业,可以看作是keepalive的升华,基于长连接。
与keepalive的区别:如果客户端要提交多个请求,对于keepalive来说,那么第二个请求,必须要等到第一个请求的响应接收完全后,才能发起,这和 TCP 的停止等待协议是一样的,得到两个响应的时间至少为2RTT。而对 pipeline 来说,客户端不必等到第一个请求处理完后,就可以马上发起第二个请求。得到两个响应的时间可能能够达到1RTT 。但是,Nginx 对 pipeline 中的多个请求的处理却不是并行的。
处理第一个请求的时候,客户端就可以发起第二个请求。这样,Nginx 利用 pipeline 减少了处理完一个请求后,等待第二个请求的请求头数据的时间。其实 Nginx 的做法很简单,前面说到,Nginx 在读取数据时,会将读取的数据放到一个 buffer 里面,所以,如果 Nginx 在处理完前一个请求后,如果发现 buffer 里面还有数据,就认为剩下的数据是下一个请求的开始,然后就接下来处理下一个请求,否则就设置 keepalive。

lingering_close

延迟关闭。 Nginx 要关闭连接时,并非立即关闭连接,而是先关闭 tcp 连接的写,再等待一段时间后再关掉连接的读。

nginx基本数据结构

ngx_str_t

nginx配置系统

指令上下文:nginx.conf 中的配置信息,根据其逻辑上的意义,对它们进行了分类,也就是分成了多个作用域,或者称之为配置指令上下文。不同的作用域含有一个或者多个配置项。
当前 Nginx 支持的几个指令上下文:

  • main: Nginx 在运行时与具体业务功能(比如http服务或者email服务代理)无关的一些参数,比如工作进程数,运行的身份等。
  • http: 与提供 http 服务相关的一些配置参数。例如:是否使用 keepalive 啊,是否使用gzip进行压缩等。
  • server: http 服务上支持若干虚拟主机。每个虚拟主机一个对应的 server 配置项,配置项里面包含该虚拟主机相关的配置。在提供 mail 服务的代理时,也可以建立若干 server,每个 server 通过监听的地址来区分。
  • location: http 服务中,某些特定的URL对应的一系列配置项。
  • mail: 实现 email 相关的 SMTP/IMAP/POP3 代理时,共享的一些配置项(因为可能实现多个代理,工作在多个监听地址上)。

/