Nginx由内核和模块组成,其中,内核的设计很是微小和简洁,完成的工做也很是简单,仅仅经过查找配置文件将客户端请求映射到一个location block(location是Nginx配置中的一个指令,用于URL匹配),而在这个location中所配置的每一个指令将会启动不一样的模块去完成相应的工做。php
Nginx的模块从结构上分为核心模块、基础模块和第三方模块:html
核心模块:HTTP模块、EVENT模块和MAIL模块前端
基础模块:HTTP Access模块、HTTP FastCGI模块、HTTP Proxy模块和HTTP Rewrite模块,linux
第三方模块:HTTP Upstream Request Hash模块、Notice模块和HTTP Access Key模块。nginx
用户根据本身的须要开发的模块都属于第三方模块。正是有了这么多模块的支撑,Nginx的功能才会如此强大。apache
Nginx的模块从功能上分为以下三类。编程
Handlers(处理器模块):此模块直接处理请求,并进行输出内容和修改headers信息等操做。Handlers处理器模块通常只能有一个。后端
Filters (过滤器模块):此模块主要对其余处理器模块输出的内容进行修改操做,最后由Nginx输出。浏览器
Proxies (代理类模块):此模块是Nginx的HTTP Upstream之类的模块,这些模块主要与后端一些服务好比FastCGI等进行交互,实现服务代理和负载均衡等功能。安全
图1-1展现了Nginx模块常规的HTTP请求和响应的过程。
Nginx自己作的工做实际不多,当它接到一个HTTP请求时,它仅仅是经过查找配置文件将这次请求映射到一个location block,而此location中所配置的各个指令则会启动不一样的模块去完成工做,所以模块能够看作Nginx真正的劳动工做者。一般一个location中的指令会涉及一个handler模块和多个filter模块(固然,多个location能够复用同一个模块)。handler模块负责处理请求,完成响应内容的生成,而filter模块对响应内容进行处理。
Nginx的模块直接被编译进Nginx,所以属于静态编译方式。启动Nginx后,Nginx的模块被自动加载,不像Apache,首先将模块编译为一个so文件,而后在配置文件中指定是否进行加载。在解析配置文件时,Nginx的每一个模块都有可能去处理某个请求,可是同一个处理请求只能由一个模块来完成。
在工做方式上,Nginx分为单工做进程和多工做进程两种模式。在单工做进程模式下,除主进程外,还有一个工做进程,工做进程是单线程的;在多工做进程模式下,每一个工做进程包含多个线程。Nginx默认为单工做进程模式。
Nginx在启动后,会有一个master进程和多个worker进程。
master进程
主要用来管理worker进程,包含:接收来自外界的信号,向各worker进程发送信号,监控worker进程的运行状态,当worker进程退出后(异常状况下),会自动从新启动新的worker进程。
master进程充当整个进程组与用户的交互接口,同时对进程进行监护。它不须要处理网络事件,不负责业务的执行,只会经过管理worker进程来实现重启服务、平滑升级、更换日志文件、配置文件实时生效等功能。
咱们要控制nginx,只须要经过kill向master进程发送信号就好了。好比kill -HUP pid,则是告诉nginx,从容地重启nginx,咱们通常用这个信号来重启nginx,或从新加载配置,由于是从容地重启,所以服务是不中断的。master进程在接收到HUP信号后是怎么作的呢?首先master进程在接到信号后,会先从新加载配置文件,而后再启动新的worker进程,并向全部老的worker进程发送信号,告诉他们能够光荣退休了。新的worker在启动后,就开始接收新的请求,而老的worker在收到来自master的信号后,就再也不接收新的请求,而且在当前进程中的全部未处理完的请求处理完成后,再退出。固然,直接给master进程发送信号,这是比较老的操做方式,nginx在0.8版本以后,引入了一系列命令行参数,来方便咱们管理。好比,./nginx -s reload,就是来重启nginx,./nginx -s stop,就是来中止nginx的运行。如何作到的呢?咱们仍是拿reload来讲,咱们看到,执行命令时,咱们是启动一个新的nginx进程,而新的nginx进程在解析到reload参数后,就知道咱们的目的是控制nginx来从新加载配置文件了,它会向master进程发送信号,而后接下来的动做,就和咱们直接向master进程发送信号同样了。
worker进程:
而基本的网络事件,则是放在worker进程中来处理了。多个worker进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求,只可能在一个worker进程中处理,一个worker进程,不可能处理其它进程的请求。worker进程的个数是能够设置的,通常咱们会设置与机器cpu核数一致,这里面的缘由与nginx的进程模型以及事件处理模型是分不开的。
worker进程之间是平等的,每一个进程,处理请求的机会也是同样的。当咱们提供80端口的http服务时,一个链接请求过来,每一个进程都有可能处理这个链接,怎么作到的呢?首先,每一个worker进程都是从master进程fork过来,在master进程里面,先创建好须要listen的socket(listenfd)以后,而后再fork出多个worker进程。全部worker进程的listenfd会在新链接到来时变得可读,为保证只有一个进程处理该链接,全部worker进程在注册listenfd读事件前抢accept_mutex,抢到互斥锁的那个进程注册listenfd读事件,在读事件里调用accept接受该链接。当一个worker进程在accept这个链接以后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开链接,这样一个完整的请求就是这样的了。咱们能够看到,一个请求,彻底由worker进程来处理,并且只在一个worker进程中处理。worker进程之间是平等的,每一个进程,处理请求的机会也是同样的。当咱们提供80端口的http服务时,一个链接请求过来,每一个进程都有可能处理这个链接,怎么作到的呢?首先,每一个worker进程都是从master进程fork过来,在master进程里面,先创建好须要listen的socket(listenfd)以后,而后再fork出多个worker进程。全部worker进程的listenfd会在新链接到来时变得可读,为保证只有一个进程处理该链接,全部worker进程在注册listenfd读事件前抢accept_mutex,抢到互斥锁的那个进程注册listenfd读事件,在读事件里调用accept接受该链接。当一个worker进程在accept这个链接以后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开链接,这样一个完整的请求就是这样的了。咱们能够看到,一个请求,彻底由worker进程来处理,并且只在一个worker进程中处理。
nginx的进程模型,能够由下图来表示:
一、什么是 FastCGI
FastCGI是一个可伸缩地、高速地在HTTP server和动态脚本语言间通讯的接口。多数流行的HTTP server都支持FastCGI,包括Apache、Nginx和lighttpd等。同时,FastCGI也被许多脚本语言支持,其中就有PHP。
FastCGI是从CGI发展改进而来的。传统CGI接口方式的主要缺点是性能不好,由于每次HTTP服务器遇到动态程序时都须要从新启动脚本解析器来执行解析,而后将结果返回给HTTP服务器。这在处理高并发访问时几乎是不可用的。另外传统的CGI接口方式安全性也不好,如今已经不多使用了。
FastCGI接口方式采用C/S结构,能够将HTTP服务器和脚本解析服务器分开,同时在脚本解析服务器上启动一个或者多个脚本解析守护进程。当HTTP服务器每次遇到动态程序时,能够将其直接交付给FastCGI进程来执行,而后将获得的结果返回给浏览器。这种方式可让HTTP服务器专注地处理静态请求或者将动态脚本服务器的结果返回给客户端,这在很大程度上提升了整个应用系统的性能。
二、Nginx+FastCGI运行原理
Nginx不支持对外部程序的直接调用或者解析,全部的外部程序(包括PHP)必须经过FastCGI接口来调用。FastCGI接口在Linux下是socket(这个socket能够是文件socket,也能够是ip socket)。
wrapper:为了调用CGI程序,还须要一个FastCGI的wrapper(wrapper能够理解为用于启动另外一个程序的程序),这个wrapper绑定在某个固定socket上,如端口或者文件socket。当Nginx将CGI请求发送给这个socket的时候,经过FastCGI接口,wrapper接收到请求,而后Fork(派生)出一个新的线程,这个线程调用解释器或者外部程序处理脚本并读取返回数据;接着,wrapper再将返回的数据经过FastCGI接口,沿着固定的socket传递给Nginx;最后,Nginx将返回的数据(html页面或者图片)发送给客户端。这就是Nginx+FastCGI的整个运做过程,如图1-3所示。
因此,咱们首先须要一个wrapper,这个wrapper须要完成的工做:
经过调用fastcgi(库)的函数经过socket和ningx通讯(读写socket是fastcgi内部实现的功能,对wrapper是非透明的)
调度thread,进行fork和kill
和application(php)进行通讯
三、spawn-fcgi与PHP-FPM
FastCGI接口方式在脚本解析服务器上启动一个或者多个守护进程对动态脚本进行解析,这些进程就是FastCGI进程管理器,或者称为FastCGI引擎。 spawn-fcgi与PHP-FPM就是支持PHP的两个FastCGI进程管理器。所以HTTPServer彻底解放出来,能够更好地进行响应和并发处理。
spawn-fcgi与PHP-FPM的异同:
1)spawn-fcgi是HTTP服务器lighttpd的一部分,目前已经独立成为一个项目,通常与lighttpd配合使用来支持PHP。可是ligttpd的spwan-fcgi在高并发访问的时候,会出现内存泄漏甚至自动重启FastCGI的问题。即:PHP脚本处理器当机,这个时候若是用户访问的话,可能就会出现白页(即PHP不能被解析或者出错)。
2)Nginx是个轻量级的HTTP server,必须借助第三方的FastCGI处理器才能够对PHP进行解析,所以其实这样看来nginx是很是灵活的,它能够和任何第三方提供解析的处理器实现链接从而实现对PHP的解析(在nginx.conf中很容易设置)。nginx也可使用spwan-fcgi(须要一同安装lighttpd,可是须要为nginx避开端口,一些较早的blog有这方面安装的教程),可是因为spawn-fcgi具备上面所述的用户逐渐发现的缺陷,如今慢慢减小用nginx+spawn-fcgi组合了。
因为spawn-fcgi的缺陷,如今出现了第三方(目前已经加入到PHP core中)的PHP的FastCGI处理器PHP-FPM,它和spawn-fcgi比较起来有以下优势:
因为它是做为PHP的patch补丁来开发的,安装的时候须要和php源码一块儿编译,也就是说编译到php core中了,所以在性能方面要优秀一些;
同时它在处理高并发方面也优于spawn-fcgi,至少不会自动重启fastcgi处理器。所以,推荐使用Nginx+PHP/PHP-FPM这个组合对PHP进行解析。
相对Spawn-FCGI,PHP-FPM在CPU和内存方面的控制都更胜一筹,并且前者很容易崩溃,必须用crontab进行监控,而PHP-FPM则没有这种烦恼。
FastCGI 的主要优势是把动态语言和HTTP Server分离开来,因此Nginx与PHP/PHP-FPM常常被部署在不一样的服务器上,以分担前端Nginx服务器的压力,使Nginx专注处理静态请求和转发动态请求,而PHP/PHP-FPM服务器专注解析PHP动态请求。
四、Nginx+PHP-FPM
PHP-FPM是管理FastCGI的一个管理器,它做为PHP的插件存在,在安装PHP要想使用PHP-FPM时在老php的老版本(php5.3.3以前)就须要把PHP-FPM以补丁的形式安装到PHP中,并且PHP要与PHP-FPM版本一致,这是必须的)
PHP-FPM实际上是PHP源代码的一个补丁,旨在将FastCGI进程管理整合进PHP包中。必须将它patch到你的PHP源代码中,在编译安装PHP后才可使用。
PHP5.3.3已经集成php-fpm了,再也不是第三方的包了。PHP-FPM提供了更好的PHP进程管理方式,能够有效控制内存和进程、能够平滑重载PHP配置,比spawn-fcgi具备更多优势,因此被PHP官方收录了。在./configure的时候带 –enable-fpm参数便可开启PHP-FPM。
fastcgi已经在php5.3.5的core中了,没必要在configure时添加 --enable-fastcgi了。老版本如php5.2的须要加此项。
当咱们安装Nginx和PHP-FPM完后,配置信息:
PHP-FPM的默认配置php-fpm.conf:
listen_address 127.0.0.1:9000 #这个表示php的fastcgi进程监听的ip地址以及端口
start_servers
min_spare_servers
max_spare_servers
Nginx配置运行php: 编辑nginx.conf加入以下语句:
location ~ \.php$ {
root html;
fastcgi_pass 127.0.0.1:9000; 指定了fastcgi进程侦听的端口,nginx就是经过这里与php交互的
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/local/nginx/html$fastcgi_script_name;
}
Nginx经过location指令,将全部以php为后缀的文件都交给127.0.0.1:9000来处理,而这里的IP地址和端口就是FastCGI进程监听的IP地址和端口。
其总体工做流程:
1)、FastCGI进程管理器php-fpm自身初始化,启动主进程php-fpm和启动start_servers个CGI 子进程。
主进程php-fpm主要是管理fastcgi子进程,监听9000端口。
fastcgi子进程等待来自Web Server的链接。
2)、当客户端请求到达Web Server Nginx是时,Nginx经过location指令,将全部以php为后缀的文件都交给127.0.0.1:9000来处理,即Nginx经过location指令,将全部以php为后缀的文件都交给127.0.0.1:9000来处理。
3)FastCGI进程管理器PHP-FPM选择并链接到一个子进程CGI解释器。Web server将CGI环境变量和标准输入发送到FastCGI子进程。
4)、FastCGI子进程完成处理后将标准输出和错误信息从同一链接返回Web Server。当FastCGI子进程关闭链接时,请求便告处理完成。
5)、FastCGI子进程接着等待并处理来自FastCGI进程管理器(运行在 WebServer中)的下一个链接。
一、nginx采用多进程模型好处
首先,对于每一个worker进程来讲,独立的进程,不须要加锁,因此省掉了锁带来的开销,同时在编程以及问题查找时,也会方便不少。
其次,采用独立的进程,可让互相之间不会影响,一个进程退出后,其它进程还在工做,服务不会中断,master进程则很快启动新的worker进程。固然,worker进程的异常退出,确定是程序有bug了,异常退出,会致使当前worker上的全部请求失败,不过不会影响到全部请求,因此下降了风险。
二、nginx多进程事件模型:异步非阻塞
虽然nginx采用多worker的方式来处理请求,每一个worker里面只有一个主线程,那可以处理的并发数颇有限啊,多少个worker就能处理多少个并发,何来高并发呢?非也,这就是nginx的高明之处,nginx采用了异步非阻塞的方式来处理请求,也就是说,nginx是能够同时处理成千上万个请求的。一个worker进程能够同时处理的请求数只受限于内存大小,并且在架构设计上,不一样的worker进程之间处理并发请求时几乎没有同步锁的限制,worker进程一般不会进入睡眠状态,所以,当Nginx上的进程数与CPU核心数相等时(最好每个worker进程都绑定特定的CPU核心),进程间切换的代价是最小的。
而apache的经常使用工做方式(apache也有异步非阻塞版本,但因其与自带某些模块冲突,因此不经常使用),每一个进程在一个时刻只处理一个请求,所以,当并发数上到几千时,就同时有几千的进程在处理请求了。这对操做系统来讲,是个不小的挑战,进程带来的内存占用很是大,进程的上下文切换带来的cpu开销很大,天然性能就上不去了,而这些开销彻底是没有意义的。
为何nginx能够采用异步非阻塞的方式来处理呢,或者异步非阻塞究竟是怎么回事呢?
咱们先回到原点,看看一个请求的完整过程:首先,请求过来,要创建链接,而后再接收数据,接收数据后,再发送数据。
具体到系统底层,就是读写事件,而当读写事件没有准备好时,必然不可操做,若是不用非阻塞的方式来调用,那就得阻塞调用了,事件没有准备好,那就只能等了,等事件准备好了,你再继续吧。阻塞调用会进入内核等待,cpu就会让出去给别人用了,对单线程的worker来讲,显然不合适,当网络事件越多时,你们都在等待呢,cpu空闲下来没人用,cpu利用率天然上不去了,更别谈高并发了。好吧,你说加进程数,这跟apache的线程模型有什么区别,注意,别增长无谓的上下文切换。因此,在nginx里面,最忌讳阻塞的系统调用了。不要阻塞,那就非阻塞喽。非阻塞就是,事件没有准备好,立刻返回EAGAIN,告诉你,事件还没准备好呢,你慌什么,过会再来吧。好吧,你过一会,再来检查一下事件,直到事件准备好了为止,在这期间,你就能够先去作其它事情,而后再来看看事件好了没。虽然不阻塞了,但你得不时地过来检查一下事件的状态,你能够作更多的事情了,但带来的开销也是不小的。
nginx支持的事件模型以下(nginx的wiki):
Nginx支持以下处理链接的方法(I/O复用方法),这些方法能够经过use指令指定。
select– 标准方法。 若是当前平台没有更有效的方法,它是编译时默认的方法。你可使用配置参数 –with-select_module 和 –without-select_module 来启用或禁用这个模块。
poll– 标准方法。 若是当前平台没有更有效的方法,它是编译时默认的方法。你可使用配置参数 –with-poll_module 和 –without-poll_module 来启用或禁用这个模块。
kqueue– 高效的方法,使用于 FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0 和 MacOS X. 使用双处理器的MacOS X系统使用kqueue可能会形成内核崩溃。
epoll – 高效的方法,使用于Linux内核2.6版本及之后的系统。在某些发行版本中,如SuSE 8.2, 有让2.4版本的内核支持epoll的补丁。
rtsig – 可执行的实时信号,使用于Linux内核版本2.2.19之后的系统。默认状况下整个系统中不能出现大于1024个POSIX实时(排队)信号。这种状况 对于高负载的服务器来讲是低效的;因此有必要经过调节内核参数 /proc/sys/kernel/rtsig-max 来增长队列的大小。但是从Linux内核版本2.6.6-mm2开始, 这个参数就再也不使用了,而且对于每一个进程有一个独立的信号队列,这个队列的大小能够用 RLIMIT_SIGPENDING 参数调节。当这个队列过于拥塞,nginx就放弃它而且开始使用 poll 方法来处理链接直到恢复正常。
/dev/poll – 高效的方法,使用于 Solaris 7 11/99+, HP/UX 11.22+ (eventport), IRIX 6.5.15+ 和 Tru64 UNIX 5.1A+.
eventport – 高效的方法,使用于 Solaris 10. 为了防止出现内核崩溃的问题, 有必要安装这个 安全补丁。
在linux下面,只有epoll是高效的方法
下面再来看看epoll究竟是如何高效的
Epoll是Linux内核为处理大批量句柄而做了改进的poll。 要使用epoll只须要这三个系统调用:epoll_create(2), epoll_ctl(2), epoll_wait(2)。它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44),在2.6内核中获得普遍应用。
epoll的优势
支持一个进程打开大数目的socket描述符(FD)
select 最不能忍受的是一个进程所打开的FD是有必定限制的,由FD_SETSIZE设置,默认值是2048。对于那些须要支持的上万链接数目的IM服务器来讲显 然太少了。这时候你一是能够选择修改这个宏而后从新编译内核,不过资料也同时指出这样会带来网络效率的降低,二是能够选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面建立进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,因此也不是一种完 美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大能够打开文件的数目,这个数字通常远大于2048,举个例子,在1GB内存的机器上大约是10万左 右,具体数目能够cat /proc/sys/fs/file-max察看,通常来讲这个数目和系统内存关系很大。
IO效率不随FD数目增长而线性降低
传统的select/poll另外一个致命弱点就是当你拥有一个很大的socket集合,不过因为网络延时,任一时间只有部分的socket是”活跃”的,但 是select/poll每次调用都会线性扫描所有的集合,致使效率呈现线性降低。可是epoll不存在这个问题,它只会对”活跃”的socket进行操 做—这是由于在内核实现中epoll是根据每一个fd上面的callback函数实现的。那么,只有”活跃”的socket才会主动的去调用 callback函数,其余idle状态socket则不会,在这点上,epoll实现了一个”伪”AIO,由于这时候推进力在os内核。在一些 benchmark中,若是全部的socket基本上都是活跃的—好比一个高速LAN环境,epoll并不比select/poll有什么效率,相 反,若是过多使用epoll_ctl,效率相比还有稍微的降低。可是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。
使用mmap加速内核与用户空间的消息传递。
这 点实际上涉及到epoll的具体实现了。不管是select,poll仍是epoll都须要内核把FD消息通知给用户空间,如何避免没必要要的内存拷贝就很 重要,在这点上,epoll是经过内核于用户空间mmap同一块内存实现的。而若是你想我同样从2.5内核就关注epoll的话,必定不会忘记手工 mmap这一步的。
内核微调
这一点其实不算epoll的优势了,而是整个linux平台的优势。也许你能够怀疑linux平台,可是你没法回避linux平台赋予你微调内核的能力。好比,内核TCP/IP协 议栈使用内存池管理sk_buff结构,那么能够在运行时期动态调整这个内存pool(skb_head_pool)的大小— 经过echo XXXX>/proc/sys/net/core/hot_list_length完成。再好比listen函数的第2个参数(TCP完成3次握手 的数据包队列长度),也能够根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每一个数据包自己大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。
推荐设置worker的个数为cpu的核数,在这里就很容易理解了,更多的worker数,只会致使进程来竞争cpu资源了,从而带来没必要要的上下文切换。并且,nginx为了更好的利用多核特性,提供了cpu亲缘性的绑定选项,咱们能够将某一个进程绑定在某一个核上,这样就不会由于进程的切换带来cache的失效。像这种小的优化在nginx中很是常见,同时也说明了nginx做者的苦心孤诣。好比,nginx在作4个字节的字符串比较时,会将4个字符转换成一个int型,再做比较,以减小cpu的指令数等等。