NGINX 以高性能的负载均衡器,缓存,和 web 服务器闻名,驱动了全球超过 40% 最繁忙的网站。在大多数场景下,默认的 NGINX 和 Linux 设置可以很好的工作,但要达到最佳性能,有些时候必须做些调整。首先我们先了解其工作原理。
1. Nginx 的模块与工作原理
Nginx 由内核和模块组成,其中,内核的设计非常微小和简洁,完成的工作也非常简单,仅仅通过查找配置文件将客户端请求映射到一个 location block(location 是 Nginx 配置中的一个指令,用于 URL 匹配),而在这个 location 中所配置的每个指令将会启动不同的模块去完成相应的工作。
Nginx 的模块从结构上分为核心模块、基础模块和第三方模块:
核心模块:HTTP 模块、EVENT 模块和 MAIL 模块
基础模块:HTTP Access 模块、HTTP FastCGI 模块、HTTP Proxy 模块和 HTTP Rewrite 模块,
第三方模块:HTTP Upstream Request Hash 模块、Notice 模块和 HTTP Access Key 模块。
用户根据自己的需要开发的模块都属于第三方模块。正是有了这么多模块的支撑,Nginx 的功能才会如此强大。
Nginx 的模块从功能上分为如下三类。
Handlers(处理器模块)。此类模块直接处理请求,并进行输出内容和修改 headers 信息等操作。Handlers 处理器模块一般只能有一个。
Filters (过滤器模块)。此类模块主要对其他处理器模块输出的内容进行修改操作,最后由 Nginx 输出。
Proxies (代理类模块)。此类模块是 Nginx 的 HTTP Upstream 之类的模块,这些模块主要与后端一些服务比如 FastCGI 等进行交互,实现服务代理和负载均衡等功能。
图 1-1 展示了 Nginx 模块常规的 HTTP 请求和响应的过程。
图 1-1 展示了 Nginx 模块常规的 HTTP 请求和响应的过程。
Nginx 本身做的工作实际很少,当它接到一个 HTTP 请求时,它仅仅是通过查找配置文件将此次请求映射到一个 location block,而此 location 中所配置的各个指令则会启动不同的模块去完成工作,因此模块可以看做 Nginx 真正的劳动工作者。通常一个 location 中的指令会涉及一个 handler 模块和多个 filter 模块(当然,多个 location 可以复用同一个模块)。handler 模块负责处理请求,完成响应内容的生成,而 filter 模块对响应内容进行处理。
Nginx 的模块直接被编译进 Nginx,因此属于静态编译方式。启动 Nginx 后,Nginx 的模块被自动加载,不像 Apache,首先将模块编译为一个 so 文件,然后在配置文件中指定是否进行加载。在解析配置文件时,Nginx 的每个模块都有可能去处理某个请求,但是同一个处理请求只能由一个模块来完成。
2. 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 的进程模型,可以由下图来表示:
3. Nginx+FastCGI 运行原理
1、什么是 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 服务器专一地处理静态请求或者将动态脚本服务器的结果返回给客户端,这在很大程度上提高了整个应用系统的性能。
2、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)进行通信
3、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 动态请求。
4、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 中)的下一个连接。
4. Nginx+PHP 正确配置
一般 web 都做统一入口:把 PHP 请求都发送到同一个文件上,然后在此文件里通过解析「REQUEST_URI」实现路由。
Nginx 配置文件分为好多块,常见的从外到内依次是「http」、「server」、「location」等等,缺省的继承关系是从外到内,也就是说内层块会自动获取外层块的值作为缺省值。
例如:
server {
listen 80;
server_name foo.com;
root /path;
location / {
index index.html index.htm index.php;
if (!-e $request_filename) {
rewrite . /index.php last;
}
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /path$fastcgi_script_name;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
}
}
1) 不应该在 location 模块定义 index
一旦未来需要加入新的「location」,必然会出现重复定义的「index」指令,这是因为多个「location」是平级的关系,不存在继承,此时应该在「server」里定义「index」,借助继承关系,「index」指令在所有的「location」中都能生效。
2) 使用 try_files
接下来看看「if」指令,说它是大家误解最深的 Nginx 指令毫不为过:
if (!-e $request_filename) {
rewrite . /index.php last;
}
很多人喜欢用「if」指令做一系列的检查,不过这实际上是「try_files」指令的职责:
try_files $uri $uri/ /index.php;
除此以外,初学者往往会认为「if」指令是内核级的指令,但是实际上它是 rewrite 模块的一部分,加上 Nginx 配置实际上是声明式的,而非过程式的,所以当其和非 rewrite 模块的指令混用时,结果可能会非你所愿。
3)fastcgi_params」配置文件:
include fastcgi_params;
Nginx 有两份 fastcgi 配置文件,分别是「fastcgi_params」和「fastcgi.conf」,它们没有太大的差异,唯一的区别是后者比前者多了一行「SCRIPT_FILENAME」的定义:
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
注意:$document_root 和 $fastcgi_script_name 之间没有 /。
原本 Nginx 只有「fastcgi_params」,后来发现很多人在定义「SCRIPT_FILENAME」时使用了硬编码的方式,于是为了规范用法便引入了「fastcgi.conf」。
不过这样的话就产生一个疑问:为什么一定要引入一个新的配置文件,而不是修改旧的配置文件?这是因为「fastcgi_param」指令是数组型的,和普通指令相同的是:内层替换外层;和普通指令不同的是:当在同级多次使用的时候,是新增而不是替换。换句话说,如果在同级定义两次「SCRIPT_FILENAME」,那么它们都会被发送到后端,这可能会导致一些潜在的问题,为了避免此类情况,便引入了一个新的配置文件。
此外,我们还需要考虑一个安全问题:在 PHP 开启「cgi.fix_pathinfo」的情况下,PHP 可能会把错误的文件类型当作 PHP 文件来解析。如果 Nginx 和 PHP 安装在同一台服务器上的话,那么最简单的解决方法是用「try_files」指令做一次过滤:
try_files $uri =404;
依照前面的分析,给出一份改良后的版本,是不是比开始的版本清爽了很多:
server {
listen 80;
server_name foo.com;
root /path;
index index.html index.htm index.php;
location / {
try_files $uri $uri/ /index.php;
}
location ~ \.php$ {
try_files $uri =404;
include fastcgi.conf;
fastcgi_pass 127.0.0.1:9000;
}
}
5. Nginx 为啥性能高-多进程 IO 模型
参考 http://mp.weixin.qq.com/s?__biz=MjM5NTg2NTU0Ng==&mid=407889757&idx=3&sn=cfa8a70a5fd2a674a91076f67808273c&scene=23&srcid=0401aeJQEraSG6uvLj69Hfve#rd
1、nginx 采用多进程模型好处
首先,对于每个 worker 进程来说,独立的进程,不需要加锁,所以省掉了锁带来的开销,同时在编程以及问题查找时,也会方便很多。
其次,采用独立的进程,可以让互相之间不会影响,一个进程退出后,其它进程还在工作,服务不会中断,master 进程则很快启动新的 worker 进程。当然,worker 进程的异常退出,肯定是程序有 bug 了,异常退出,会导致当前 worker 上的所有请求失败,不过不会影响到所有请求,所以降低了风险。
1、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,告诉你,事件还没准备好呢,你慌什么,过会再来吧。好吧,你过一会,再来检查一下事件,直到事件准备好了为止,在这期间,你就可以先去做其它事情,然后再来看看事件好了没。虽然不阻塞了,但你得不时地过来检查一下事件的状态,你可以做更多的事情了,但带来的开销也是不小的。
关于 IO 模型:http://blog.csdn.net/hguisu/article/details/7453390
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 网卡驱动架构。
(epoll 内容,参考 epoll_互动百科)
推荐设置 worker 的个数为 cpu 的核数,在这里就很容易理解了,更多的 worker 数,只会导致进程来竞争 cpu 资源了,从而带来不必要的上下文切换。而且,nginx 为了更好的利用多核特性,提供了 cpu 亲缘性的绑定选项,我们可以将某一个进程绑定在某一个核上,这样就不会因为进程的切换带来 cache 的失效。像这种小的优化在 nginx 中非常常见,同时也说明了 nginx 作者的苦心孤诣。比如,nginx 在做 4 个字节的字符串比较时,会将 4 个字符转换成一个 int 型,再作比较,以减少 cpu 的指令数等等。
代码来总结一下 nginx 的事件处理模型:
while (true) {
for t in run_tasks:
t.handler();
update_time(&now);
timeout = ETERNITY;
for t in wait_tasks: /* sorted already */
if (t.time <= now) {
t.timeout_handler();
} else {
timeout = t.time - now;
break;
}
nevents = poll_function(events, timeout);
for i in nevents:
task t;
if (events[i].type == READ) {
t.handler = read_handler;
} else { /* events[i].type == WRITE */
t.handler = write_handler;
}
run_tasks_add(t);
}
while (true) {
for t in run_tasks:
t.handler();
update_time(&now);
timeout = ETERNITY;
for t in wait_tasks: /* sorted already */
if (t.time <= now) {
t.timeout_handler();
} else {
timeout = t.time - now;
break;
}
nevents = poll_function(events, timeout);
for i in nevents:
task t;
if (events[i].type == READ) {
t.handler = read_handler;
} else { /* events[i].type == WRITE */
t.handler = write_handler;
}
run_tasks_add(t);
}
6. Nginx 优化
1. 编译安装过程优化
1). 减小 Nginx 编译后的文件大小
在编译 Nginx 时,默认以 debug 模式进行,而在 debug 模式下会插入很多跟踪和 ASSERT 之类的信息,编译完成后,一个 Nginx 要有好几兆字节。而在编译前取消 Nginx 的 debug 模式,编译完成后 Nginx 只有几百千字节。因此可以在编译之前,修改相关源码,取消 debug 模式。具体方法如下:
在 Nginx 源码文件被解压后,找到源码目录下的 auto/cc/gcc 文件,在其中找到如下几行:
- # debug
- CFLAGS=”$CFLAGS -g”
注释掉或删掉这两行,即可取消 debug 模式。
2. 为特定的 CPU 指定 CPU 类型编译优化
在编译 Nginx 时,默认的 GCC 编译参数是 “-O”,要优化 GCC 编译,可以使用以下两个参数:
- --with-cc-opt='-O3'
- --with-cpu-opt=CPU # 为特定的 CPU 编译,有效的值包括:
pentium, pentiumpro, pentium3, # pentium4, athlon, opteron, amd64, sparc32, sparc64, ppc64
要确定 CPU 类型,可以通过如下命令:
- [[email protected] home]#cat /proc/cpuinfo | grep "model name"
2. 利用 TCMalloc 优化 Nginx 的性能
TCMalloc 的全称为 Thread-Caching Malloc,是谷歌开发的开源工具 google-perftools 中的一个成员。与标准的 glibc 库的 Malloc 相比,TCMalloc 库在内存分配效率和速度上要高很多,这在很大程度上提高了服务器在高并发情况下的性能,从而降低了系统的负载。下面简单介绍如何为 Nginx 添加 TCMalloc 库支持。
要安装 TCMalloc 库,需要安装 libunwind(32 位操作系统不需要安装)和 google-perftools 两个软件包,libunwind 库为基于 64 位 CPU 和操作系统的程序提供了基本函数调用链和函数调用寄存器功能。下面介绍利用 TCMalloc 优化 Nginx 的具体操作过程。
1). 安装 libunwind 库
可以从 http://download.savannah.gnu.org/releases/libunwind 下载相应的 libunwind 版本,这里下载的是 libunwind-0.99-alpha.tar.gz。安装过程如下:
- [[email protected] home]#tar zxvf libunwind-0.99-alpha.tar.gz
- [[email protected] home]# cd libunwind-0.99-alpha/
- [[email protected] libunwind-0.99-alpha]#CFLAGS=-fPIC ./configure
- [[email protected] libunwind-0.99-alpha]#make CFLAGS=-fPIC
- [[email protected] libunwind-0.99-alpha]#make CFLAGS=-fPIC install
2). 安装 google-perftools
可以从 http://google-perftools.googlecode.com 下载相应的 google-perftools 版本,这里下载的是 google-perftools-1.8.tar.gz。安装过程如下:
- [[email protected] home]#tar zxvf google-perftools-1.8.tar.gz
- [[email protected] home]#cd google-perftools-1.8/
- [[email protected] google-perftools-1.8]# ./configure
- [[email protected] google-perftools-1.8]#make && make install
- [[email protected] google-perftools-1.8]#echo "/usr/
local/lib" > /etc/ld.so.conf.d/usr_local_lib.conf - [[email protected] google-perftools-1.8]# ldconfig
至此,google-perftools 安装完成。
3). 重新编译 Nginx
为了使 Nginx 支持 google-perftools,需要在安装过程中添加 “–with-google_perftools_module” 选项重新编译 Nginx。安装代码如下:
- [[email protected]]#./configure \
- >--with-google_perftools_module --with-http_stub_status_module --prefix=/opt/nginx
- [[email protected] nginx-0.7.65]#make
- [[email protected] nginx-0.7.65]#make install
到这里 Nginx 安装完成。
4). 为 google-perftools 添加线程目录
创建一个线程目录,这里将文件放在 / tmp/tcmalloc 下。操作如下:
- [[email protected] home]#mkdir /tmp/tcmalloc
- [[email protected] home]#chmod 0777 /tmp/tcmalloc
5). 修改 Nginx 主配置文件
修改 nginx.conf 文件,在 pid 这行的下面添加如下代码:
- #pid logs/nginx.pid;
- google_perftools_profiles /tmp/tcmalloc;
接着,重启 Nginx 即可完成 google-perftools 的加载。
6). 验证运行状态
为了验证 google-perftools 已经正常加载,可通过如下命令查看:
- [[email protected] localhost home]# lsof -n | grep tcmalloc
- nginx 2395 nobody 9w REG 8,8 0 1599440 /tmp/tcmalloc.2395
- nginx 2396 nobody 11w REG 8,8 0 1599443 /tmp/tcmalloc.2396
- nginx 2397 nobody 13w REG 8,8 0 1599441 /tmp/tcmalloc.2397
- nginx 2398 nobody 15w REG 8,8 0 1599442 /tmp/tcmalloc.2398
由于在 Nginx 配置文件中设置 worker_processes 的值为 4,因此开启了 4 个 Nginx 线程,每个线程会有一行记录。每个线程文件后面的数字值就是启动的 Nginx 的 pid 值。
至此,利用 TCMalloc 优化 Nginx 的操作完成。
3.Nginx 内核参数优化
内核参数的优化,主要是在 Linux 系统中针对 Nginx 应用而进行的系统内核参数优化。
下面给出一个优化实例以供参考。
- net.ipv4.tcp_max_tw_buckets = 6000
- net.ipv4.ip_local_port_range = 1024 65000
- net.ipv4.tcp_tw_recycle = 1
- net.ipv4.tcp_tw_reuse = 1
- net.ipv4.tcp_syncookies = 1
- net.core.somaxconn = 262144
- net.core.netdev_max_backlog = 262144
- net.ipv4.tcp_max_orphans = 262144
- net.ipv4.tcp_max_syn_backlog = 262144
- net.ipv4.tcp_synack_retries = 1
- net.ipv4.tcp_syn_retries = 1
- net.ipv4.tcp_fin_timeout = 1
- net.ipv4.tcp_keepalive_time = 30
将上面的内核参数值加入 / etc/sysctl.conf 文件中,然后执行如下命令使之生效:
- [[email protected] localhost home]#/sbin/sysctl -p
下面对实例中选项的含义进行介绍:
TCP 参数设置:
net.ipv4.tcp_max_tw_buckets :选项用来设定 timewait 的数量,默认是 180 000,这里设为 6000。
net.ipv4.ip_local_port_range: 选项用来设定允许系统打开的端口范围。在高并发情况否则端口号会不够用。当 NGINX 充当代理时,每个到上游服务器的连接都使用一个短暂或临时端口。
net.ipv4.tcp_tw_recycle: 选项用于设置启用 timewait 快速回收.
net.ipv4.tcp_tw_reuse: 选项用于设置开启重用,允许将 TIME-WAIT sockets 重新用于新的 TCP 连接。
net.ipv4.tcp_syncookies: 选项用于设置开启 SYN Cookies,当出现 SYN 等待队列溢出时,启用 cookies 进行处理。
net.ipv4.tcp_max_orphans: 选项用于设定系统中最多有多少个 TCP 套接字不被关联到任何一个用户文件句柄上。如果超过这个数字,孤立连接将立即被复位并打印出警告信息。这个限制只是为了防止简单的 DoS 攻击。不能过分依靠这个限制甚至人为减小这个值,更多的情况下应该增加这个值。
net.ipv4.tcp_max_syn_backlog: 选项用于记录那些尚未收到客户端确认信息的连接请求的最大值。对于有 128MB 内存的系统而言,此参数的默认值是 1024,对小内存的系统则是 128。
net.ipv4.tcp_synack_retries 参数的值决定了内核放弃连接之前发送 SYN+ACK 包的数量。
net.ipv4.tcp_syn_retries 选项表示在内核放弃建立连接之前发送 SYN 包的数量。
net.ipv4.tcp_fin_timeout 选项决定了套接字保持在 FIN-WAIT-2 状态的时间。默认值是 60 秒。正确设置这个值非常重要,有时即使一个负载很小的 Web 服务器,也会出现大量的死套接字而产生内存溢出的风险。
net.ipv4.tcp_syn_retries 选项表示在内核放弃建立连接之前发送 SYN 包的数量。
如果发送端要求关闭套接字,net.ipv4.tcp_fin_timeout 选项决定了套接字保持在 FIN-WAIT-2 状态的时间。接收端可以出错并永远不关闭连接,甚至意外宕机。
net.ipv4.tcp_fin_timeout 的默认值是 60 秒。需要注意的是,即使一个负载很小的 Web 服务器,也会出现因为大量的死套接字而产生内存溢出的风险。FIN-WAIT-2 的危险性比 FIN-WAIT-1 要小,因为它最多只能消耗 1.5KB 的内存,但是其生存期长些。
net.ipv4.tcp_keepalive_time 选项表示当 keepalive 启用的时候,TCP 发送 keepalive 消息的频度。默认值是 2(单位是小时)。
缓冲区队列:
net.core.somaxconn: 选项的默认值是 128, 这个参数用于调节系统同时发起的 tcp 连接数,在高并发的请求中,默认的值可能会导致链接超时或者重传,因此,需要结合并发请求数来调节此值。
由 NGINX 可接受的数目决定。默认值通常很低,但可以接受,因为 NGINX 接收连接非常快,但如果网站流量大时,就应该增加这个值。内核日志中的错误消息会提醒这个值太小了,把值改大,直到错误提示消失。
注意: 如果设置这个值大于 512,相应地也要改变 NGINX listen 指令的 backlog 参数。
net.core.netdev_max_backlog: 选项表示当每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许发送到队列的数据包的最大数目。
4. PHP-FPM 的优化
如果您高负载网站使用 PHP-FPM 管理 FastCGI,这些技巧也许对您有用:
1)增加 FastCGI 进程数
把 PHP FastCGI 子进程数调到 100 或以上,在 4G 内存的服务器上 200 就可以建议通过压力测试获取最佳值。
2)增加 PHP-FPM 打开文件描述符的限制
标签 rlimit_files 用于设置 PHP-FPM 对打开文件描述符的限制,默认值为 1024。这个标签的值必须和 Linux 内核打开文件数关联起来,例如,要将此值设置为 65 535,就必须在 Linux 命令行执行 “ulimit -HSn 65536”。
然后 增加 PHP-FPM 打开文件描述符的限制:
# vi /path/to/php-fpm.conf
找到 “<valuename="rlimit_files">1024</value>”
把 1024 更改为 4096 或者更高.
重启 PHP-FPM.
ulimit -n 要调整为 65536 甚至更大。如何调这个参数,可以参考网上的一些文章。命令行下执行 ulimit -n 65536 即可修改。如果不能修改,需要设置 /etc/security/limits.conf,加入
* hard nofile65536
* soft nofile 65536
3)适当增加 max_requests
标签 max_requests 指明了每个 children 最多处理多少个请求后便会被关闭,默认的设置是 500。
<value name="max_requests"> 500 </value>
4.nginx.conf 的参数优化
nginx 要开启的进程数 一般等于 cpu 的总核数 其实一般情况下开 4 个或 8 个就可以。
每个 nginx 进程消耗的内存 10 兆的模样
worker_cpu_affinity
仅适用于 linux,使用该选项可以绑定 worker 进程和 CPU(2.4 内核的机器用不了)
假如是 8 cpu 分配如下:
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000
00100000 01000000 10000000
nginx 可以使用多个 worker 进程,原因如下:
to use SMP
to decrease latency when workers blockend on disk I/O
to limit number of connections per process when select()/poll() is
used The worker_processes and worker_connections from the event sections
allows you to calculate maxclients value: k max_clients = worker_processes * worker_connections
worker_rlimit_nofile 102400;
每个 nginx 进程打开文件描述符最大数目 配置要和系统的单进程打开文件数一致, linux 2.6 内核下开启文件打开数为 65535,worker_rlimit_nofile 就相应应该填写 65535 nginx 调度时分配请求到进程并不是那么的均衡,假如超过会返回 502 错误。我这里写的大一点
use epoll
Nginx 使用了最新的 epoll(Linux 2.6 内核)和 kqueue(freebsd)网络 I/O 模型,而 Apache 则使用的是传统的 select 模型。
处理大量的连接的读写,Apache 所采用的 select 网络 I/O 模型非常低效。在高并发服务器中,轮询 I/O 是最耗时间的操作 目前 Linux 下能够承受高并发
访问的 Squid、Memcached 都采用的是 epoll 网络 I/O 模型。
worker_processes
NGINX 工作进程数(默认值是 1)。在大多数情况下,一个 CPU 内核运行一个工作进程最好,建议将这个指令设置成自动就可以。有时可能想增大这个值,比如当工作进程需要做大量的磁盘 I/O。
worker_connections 65535;
每个工作进程允许最大的同时连接数 (Maxclient = work_processes * worker_connections)
keepalive_timeout 75
keepalive 超时时间
这里需要注意官方的一句话:
The parameters can differ from each other. Line Keep-Alive:
timeout=time understands Mozilla and Konqueror. MSIE itself shuts
keep-alive connection approximately after 60 seconds.
client_header_buffer_size 16k
large_client_header_buffers 4 32k
客户请求头缓冲大小
nginx 默认会用 client_header_buffer_size 这个 buffer 来读取 header 值,如果 header 过大,它会使用 large_client_header_buffers 来读取
如果设置过小 HTTP 头 / Cookie 过大 会报 400 错误 nginx 400 bad request
求行如果超过 buffer,就会报 HTTP 414 错误 (URI Too Long) nginx 接受最长的 HTTP 头部大小必须比其中一个 buffer 大,否则就会报 400 的 HTTP 错误 (Bad Request)。
open_file_cache max 102400
使用字段: http, server, location 这个指令指定缓存是否启用, 如果启用, 将记录文件以下信息: · 打开的文件描述符, 大小信息和修改时间. · 存在的目录信息. · 在搜索文件过程中的错误信息 -- 没有这个文件, 无法正确读取, 参考 open_file_cache_errors 指令选项:
·max - 指定缓存的最大数目, 如果缓存溢出, 最长使用过的文件 (LRU) 将被移除
例: open_file_cache max=1000 inactive=20s; open_file_cache_valid 30s; open_file_cache_min_uses 2; open_file_cache_errors on;
open_file_cache_errors
语法: open_file_cache_errors on | off 默认值: open_file_cache_errors off 使用字段: http, server, location 这个指令指定是否在搜索一个文件是记录 cache 错误.
open_file_cache_min_uses
语法: open_file_cache_min_uses number 默认值: open_file_cache_min_uses 1 使用字段: http, server, location 这个指令指定了在 open_file_cache 指令无效的参数中一定的时间范围内可以使用的最小文件数, 如 果使用更大的值, 文件描述符在 cache 中总是打开状态.
open_file_cache_valid
语法: open_file_cache_valid time 默认值: open_file_cache_valid 60 使用字段: http, server, location 这个指令指定了何时需要检查 open_file_cache 中缓存项目的有效信息.
开启 gzip
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_types text/plain application/x-javascript text/css
application/xml;
gzip_vary on;
缓存静态文件:
location ~* ^.+\.(swf|gif|png|jpg|js|css)$ {
root /usr/local/ku6/ktv/show.ku6.com/;
expires 1m;
}
响应缓冲区:
比如我们 Nginx+Tomcat 代理访问 JS 无法完全加载,这几个参数影响:
proxy_buffer_size 128k;
proxy_buffers 32 128k;
proxy_busy_buffers_size 128k;
Nginx 在代理了相应服务后或根据我们配置的 UpStream 和 location 来获取相应的文件,首先文件会被解析到 nginx 的内存或者临时文件目录中,然后由 nginx 再来响应。那么当 proxy_buffers 和 proxy_buffer_size 以及 proxy_busy_buffers_size 都太小时,会将内容根据 nginx 的配置生成到临时文件中,但是临时文件的大小也有默认值。所以当这四个值都过小时就会导致部分文件只加载一部分。所以要根据我们的服务器情况适当的调整 proxy_buffers 和 proxy_buffer_size 以及 proxy_busy_buffers_size、proxy_temp_file_write_size。具体几个参数的详细如下:
proxy_buffers 32 128k; 设置了 32 个缓存区,每个的大小是 128k
proxy_buffer_size 128k; 每个缓存区的大小是 128k,当两个值不一致时没有找到具体哪个有效,建议和上面的设置一致。
proxy_busy_buffers_size 128k; 设置使用中的缓存区的大小,控制传输至客户端的缓存的最大
proxy_temp_file_write_size 缓存文件的大小
5. 访问日志
记录每个请求会消耗 CPU 和 I/O 周期,一种降低这种影响的方式是缓冲访问日志。使用缓冲,而不是每条日志记录都单独执行写操作,NGINX 会缓冲一连串的日志记录,使用单个操作把它们一起写到文件中。
要启用访问日志的缓存,就涉及到在 access_log 指令中 buffer=size 这个参数。当缓冲区达到 size 值时,NGINX 会把缓冲区的内容写到日志中。让 NGINX 在指定的一段时间后写缓存,就包含 flush=time 参数。当两个参数都设置了,当下个日志条目超出缓冲区值或者缓冲区中日志条目存留时间超过设定的时间值,NGINX 都会将条目写入日志文件。当工作进程重新打开它的日志文件或退出时,也会记录下来。要完全禁用访问日志记录的功能,将 access_log 指令设置成 off 参数。
6. 限流
你可以设置多个限制,防止用户消耗太多的资源,避免影响系统性能和用户体验及安全。 以下是相关的指令:
limit_conn and limit_conn_zone:NGINX 接受客户连接的数量限制,例如单个 IP 地址的连接。设置这些指令可以防止单个用户打开太多的连接,消耗超出自己的资源。
limit_rate:传输到客户端响应速度的限制(每个打开多个连接的客户消耗更多的带宽)。设置这个限制防止系统过载,确保所有客户端更均匀的服务质量。
limit_req and limit_req_zone:NGINX 处理请求的速度限制,与 limit_rate 有相同的功能。可以提高安全性,尤其是对登录页面,通过对用户限制请求速率设置一个合理的值,避免太慢的程序覆盖你的应用请求(比如 DDoS 攻击)。
max_conns:上游配置块中服务器指令参数。在上游服务器组中单个服务器可接受最大并发数量。使用这个限制防止上游服务器过载。设置值为 0(默认值)表示没有限制。
queue (NGINX Plus) :创建一个队列,用来存放在上游服务器中超出他们最大 max_cons 限制数量的请求。这个指令可以设置队列请求的最大值,还可以选择设置在错误返回之前最大等待时间(默认值是 60 秒)。如果忽略这个指令,请求不会放入队列。
7. 错误排查
1、Nginx 502 Bad Gateway:5 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。
常见原因:
1、后端服务挂了的情况, 直接 502 (nginx error 日志:connect() failed (111: Connection refused) )
2、后端服务在重启
实例:将后端服务关掉,然后向 nginx 发送请求后端接口,nginx 日志可以看到 502 错误。
如果 nginx+php 出现 502, 错误分析:
php-cgi 进程数不够用、php 执行时间长(mysql 慢)、或者是 php-cgi 进程死掉,都会出现 502 错误
一般来说 Nginx 502 Bad Gateway 和 php-fpm.conf 的设置有关,而 Nginx 504 Gateway Time-out 则是与 nginx.conf 的设置有关
1)、查看当前的 PHP FastCGI 进程数是否够用:
netstat -anpo | grep "php-cgi" | wc -l
如果实际使用的 “FastCGI 进程数” 接近预设的 “FastCGI 进程数”,那么,说明“FastCGI 进程数” 不够用,需要增大。
2)、部分 PHP 程序的执行时间超过了 Nginx 的等待时间,可以适当增加
nginx.conf 配置文件中 FastCGI 的 timeout 时间,例如:
http {
......
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
......
}
2、504 Gateway Timeout :
nginx 作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器(URI 标识出的服务器,例如 HTTP、FTP、LDAP)收到响应。
常见原因:
该接口太耗时,后端服务接收到请求,开始执行,未能在设定时间返回数据给 nginx
后端服务器整体负载太高,接受到请求之后,由于线程繁忙,未能安排给请求的接口,导致未能在设定时间返回数据给 nginx
2、413 Request Entity Too Large
解决:增大 client_max_body_size
client_max_body_size: 指令指定允许客户端连接的最大请求实体大小, 它出现在请求头部的 Content-Length 字段. 如果请求大于指定的值, 客户端将收到一个 "Request Entity Too Large" (413) 错误. 记住, 浏览器并不知道怎样显示这个错误.
php.ini 中增大
post_max_size 和 upload_max_filesize
3 Ngnix error.log 出现:upstream sent too big header while reading response header from upstream 错误
1) 如果是 nginx 反向代理
proxy 是 nginx 作为 client 转发时使用的,如果 header 过大,超出了默认的 1k,就会引发上述的 upstream sent too big header (说白了就是 nginx 把外部请求给后端 server,后端 server 返回的 header 太大 nginx 处理不过来就导致了。
server {
listen 80;
server_name *.xywy.com ;
large_client_header_buffers 4 16k;
location / {
#添加这 3 行
proxy_buffer_size 64k;
proxy_buffers 32 32k;
proxy_busy_buffers_size 128k;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
2) 如果是 nginx+PHPcgi
错误带有 upstream: "fastcgi://127.0.0.1:9000"。就该
多加:
fastcgi_buffer_size 128k;
fastcgi_buffers 4 128k;
server {
listen 80;
server_name ddd.com;
index index.html index.htm index.php;
client_header_buffer_size 128k;
large_client_header_buffers 4 128k;
proxy_buffer_size 64k;
proxy_buffers 8 64k;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 128k;
location / {
......
}
}
8. Nginx 的 php 漏洞
漏洞介绍:nginx 是一款高性能的 web 服务器,使用非常广泛,其不仅经常被用作反向代理,也可以非常好的支持 PHP 的运行。80sec 发现其中存在一个较为严重的安全问题,默认情况下可能导致服务器错误的将任何类型的文件以 PHP 的方式进行解析,这将导致严重的安全问题,使得恶意的攻击者可能攻陷支持 php 的 nginx 服务器。
漏洞分析:nginx 默认以 cgi 的方式支持 php 的运行,譬如在配置文件当中可以以
location ~ .php$ {
root html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
include fastcgi_params;
}
的方式支持对 php 的解析,location 对请求进行选择的时候会使用 URI 环境变量进行选择,其中传递到后端 Fastcgi 的关键变量 SCRIPT_FILENAME 由 nginx 生成的 $fastcgi_script_name 决定,而通过分析可以看到 $fastcgi_script_name 是直接由 URI 环境变量控制的,这里就是产生问题的点。而为了较好的支持 PATH_INFO 的提取,在 PHP 的配置选项里存在 cgi.fix_pathinfo 选项,其目的是为了从 SCRIPT_FILENAME 里取出真正的脚本名。
那么假设存在一个 http://www.80sec.com/80sec.jpg,我们以如下的方式去访问
http://www.80sec.com/80sec.jpg/80sec.php
将会得到一个 URI/80sec.jpg/80sec.php
经过 location 指令,该请求将会交给后端的 fastcgi 处理,nginx 为其设置环境变量 SCRIPT_FILENAME,内容为/scripts/80sec.jpg/80sec.php
而在其他的 webserver 如 lighttpd 当中,我们发现其中的 SCRIPT_FILENAME 被正确的设置为/scripts/80sec.jpg
所以不存在此问题。
后端的 fastcgi 在接受到该选项时,会根据 fix_pathinfo 配置决定是否对 SCRIPT_FILENAME 进行额外的处理,一般情况下如果不对 fix_pathinfo 进行设置将影响使用 PATH_INFO 进行路由选择的应用,所以该选项一般配置开启。Php 通过该选项之后将查找其中真正的脚本文件名字,查找的方式也是查看文件是否存在,这个时候将分离出 SCRIPT_FILENAME 和 PATH_INFO 分别为/scripts/80sec.jpg和80sec.php
最后,以 / scripts/80sec.jpg 作为此次请求需要执行的脚本,攻击者就可以实现让 nginx 以 php 来解析任何类型的文件了。
POC: 访问一个 nginx 来支持 php 的站点,在一个任何资源的文件如 robots.txt 后面加上 / 80sec.php,这个时候你可以看到如下的区别:
访问 http://www.80sec.com/robots.txtHTTP/1.1 200 OK
Server: nginx/0.6.32
Date: Thu, 20 May 2010 10:05:30 GMT
Content-Type: text/plain
Content-Length: 18
Last-Modified: Thu, 20 May 2010 06:26:34 GMT
Connection: keep-alive
Keep-Alive: timeout=20
Accept-Ranges: bytes
访问访问 http://www.80sec.com/robots.txt/80sec.phpHTTP/1.1 200 OK
Server: nginx/0.6.32
Date: Thu, 20 May 2010 10:06:49 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
Keep-Alive: timeout=20
X-Powered-By: PHP/5.2.6
其中的 Content-Type 的变化说明了后端负责解析的变化,该站点就可能存在漏洞。
漏洞厂商:http://www.nginx.org
解决方案:
我们已经尝试联系官方,但是此前你可以通过以下的方式来减少损失关闭cgi.fix_pathinfo为0
或者if ( $fastcgi_script_name ~ ..*/.*php ) {
return 403;
}