本文是我的读书笔记,自己翻译的,不求信达雅,各位看官见谅。
说起nginx的调优,坏消息是事实上没有方法能很大程度优化nginx,不存在一个"神奇的"设置选项可以将负载降低到原来的一半或者可以让PHP运行速度加倍。不过接下来是好消息,nginx本身已经优化的足够好了!其实相比apache,最大的优化在你敲入"apt-get install","yum install"或者"make install"的时候已经产生了,呵呵。
那么怎么还要说到nginx调优这个话题呢?
一个原因是因为nginx有很多的配置选项,这些选项会影响nginx的行为。但是,这些选项的默认值并不是完全针对高负载这种状况优化的,所以需要调整。
另一个原因是nginx所运行的OS的设置也会影响nginx的运行,想要达到理想的运行效果,也需要调整。
我们下面就来分别分析一下相关的问题。
一、操作系统的限制
1.操作系统本身
nginx可以运行在Linux, MacOS, FreeBSD, Solaris, Windows等众多平台上,这些平台都有各自的高性能event polling方法,不过nginx只支持其中的4种。一般来说,运行在freebsd,linux,MacOS,Solaris上性能不会有很巨大的差别,所有选择你自己熟悉的OS比要选择所谓"绝对优化"的,但是不熟悉的OS更加重要。不过,windows是一个例外,在生产环境中,千万不要把nginx运行在windows上。因为windows有自己的event polling方法,而nginx的作者明确说明不支持该方法。
在nginx中,可以在配置文件中指定所使用的event polling模型,指令如下:
use epoll;
Linux系统
use
kqueue;
FreeBSD系统
2.`ulimit -a`命令所列出来的这些参数
这些参数是nginx无法超越的。例如,在许多系统中,默认允许打开的最大文件数为1024,如果nginx运行在这个环境下,那么当流量大时就会出现(24: Too many open files) 错误。而nginx的处理能力远不止1024,因此这个参数必须调整。
二、nginx自身的限制
1.编译优化
默认的nginx编译选项是用debug模式(-g)的(debug模式会插入很多跟踪和ASSERT之类),编译以后一个nginx可执行文件有好几MB。去掉nginx的debug模式编译,编译以后只有几百KB。
在源码auto/cc/gcc中,在最后几行中找到如下内容:
# debug
CFLAGS=”$CFLAGS -g”
把CFLAGS这一行删除或者注释掉,然后再编译即可。
另外,如果只是把nginx作为web server,那么可以禁用一些用不到的modules,可以减少内存footprint,提高服务器性能。
./configure --prefix=/webserver/nginx --without-mail_pop3_module --without-mail_imap_module --without-mail_smtp_module --with-http_ssl_module --with-http_stub_status_module --with-http_gzip_static_module
2.Worker Processes
worker process是整个nginx的关键,一旦主进程绑定到了指定的IP/Port上以后,主进程会使用指定的用户派生出worker process,这些worker process会处理所有的用户请求。worker process不是多线程的,不需要将每个connection在不同CPU内核间切换,因此运行多个worker process是必然的,通常每个CPU core对应一个worker process。通常,如果超过4个worker process,那么CPU肯定不会成为瓶颈了,因为在CPU成为瓶颈之前,nginx的其他部分肯定已经不行了。
cat /proc/cpuinfo |grep 'core id'
3.Worker Connections
worker connctions是一个有点"怪异"的概念,我不确定这个指令的确切目的。但是可以确定的是这个指令可以有效的限制每个worker process在同一时间可以维护多少个连接。如果非要我猜的话,我认为这个指令是一个保险机制,为了防止错误配置的keep-alive耗尽可用端口。
在默认配制文件中,worker connections的值是1024。通常一个浏览器会针对一个站点打开2个连接,那么最大可以并发服务的用户数量为512个。这个数字看起来不小了,但是考虑到keep-alive默认的timeout值是65,那么意味着其实我们每秒只能处理8个connection。显然这个数字超过了大多数人的需求,尤其是当我们使用2-4个worker process的时候。但是对于大流量的站点,并且打开了keep-alvie,那么应该时刻考虑到这一点。
当考虑worker connections的值的时候,其实很简单,流量增加就增加这个值。2048对于绝大多数人应该够用了,不过如果你的站点真的增长这么快的话,那么最大该设置多大,只有你自己尝试了。
4.CPU Affinity
设置CPU affinity的意思是告诉nginx,每个worker process应该使用哪一个CPU core,指定以后该worker process只会使用你指定的那个CPU core。请小心的做这个配置,因为OS的CPU调度器在处理负载均衡方面远比人类做的更好。如果你确实认为你需要在CPU调度器层面做优化的话,你可以选择不同的CPU调度器,不过你需要清楚的知道你自己在干什么,如果不知道,不要碰这个配置。
5.Keep Alive
keep alive是一个HTTP的特性,其允许用户agent(浏览器)和服务器之间保持连接,以便其他请求共用或者直到指定的timeout时间到达。这个特性事实上并不会改善我们的nginx服务器性能,因为nginx自己可以非常好的处理空闲连接。nginx作者指出,处理10000个空闲连接只耗费2.5MB的内存。
在这里提到keep alive的目的很简单。keep alive对于终端用户感觉到的等待时间影响是巨大的。如果你的站点看起来载入很快,那么用户会很开心。Amazon做过调查,用户感觉的等待时间和最终业务成交量之间有直接的关系。
keep alive可以避免在创建HTTP连接过程中许多无用操作,因此它的作用才如此明显。一般我们不需要65这么大的timeout值,但推荐你将这个值设置在10-20之间。
keepalive_timeout 15
6.tcp_nodelay and tcp_nopush
这两个参数的用途很难理解,因为它们在很底层的网络部分影响nginx。一个简单肤浅的解释是这两个指令控制OS如何处理网络缓冲,何时将他们刷新给最终用户。因为这两个参数不会明显提高或改变任何事情,如果你不明白这两个参数的含义,那我建议你不用碰它,保持默认值就好了。
三、硬件的限制
上边已经讨论了OS,nginx本身的一些限制,现在让我们看看怎么把服务器的性能压榨到底。
服务器上能成为瓶颈的就CPU,内存和IO。对于nginx来说,CPU和内存都不会是瓶颈。因此瓶颈只会产生在IO部分。硬盘相对于CPU和内存来说,是非常非常慢的设备。硬盘读取和写入是非常耗时的操作,因此我们需要尽量减少nginx对硬盘的读写操作。
1.Access log
nginx默认会将每个请求写入到日志文件中,日志可以用来审计和统计。但是记录日志的操作会带来IO开销。如果你不需要记录日志,直接关闭这个选项就好。如果需要记录日志,最好将日志记录到内存中,然后定期将日志转储到磁盘上。这样可以避免频繁磁盘IO操作,极大提高性能。
access_log off;
2.Error log
对于error log,其实不应该关闭的。但是为了降低磁盘IO操作,可以调整error log的级别,将这个参数设置成"warn"级别应该足够了,并且也不会产生很大的IO。
3.Open File Cache
4.Buffers
调整buffer的大小对于nginx很重要,如果buffer设置得太小,nginx将不得不把upstream服务器返回来的响应存储在临时文件中。这会导致同时增加磁盘读写操作,流量越大影响越明显。
client_body_buffer_size
指令指定了处理客户端请求body部分的buffer的大小。这个一般用于处理POST数据,表单提交,文件上传等操作。如果你要处理很多大的POST提交,那么这个值要设置足够大。
client_header_buffer_size
指令指定了处理客户端请求header部分的buffer的大小。设置成1K能满足绝大部分需求。
client_max_body_size
指令指定了可以接受的最大的用户请求body大小。通过HTTP头中的Content-Length确认,如果大小超过这个值,则客户端会得到“Request Entity Too Large” (413)错误。
large_client_header_buffers
指令分配用于处理从用户那里来的大文件头请求的buffer的最大数量和buffer大小。请求的header不能比其中的一个buffer大,否则nginx会返回“Request URI too large” (414)错误。最长的header行也必须小于一个buffer的大小,否则客户端会得到“Bad request” (400)错误。
fastcgi_buffers
和
proxy_buffers
指令指定了处理upstream回应的buffer大小,也就是PHP,Apache或其他。上边也说到了,如果这个buffer太小,在响应用户之前,nginx将不得不把upstream服务器返回来的响应存储在临时文件中。注意,nginx的buffer是有上限的,这个上限由
fastcgi_max_temp_file_size
和
proxy_max_temp_file_size
控制。当然也可以通过将
proxy_buffering
设置成off来关闭proxy connections的buffer(通常不是个好主意!)。
示例如下:
client_body_buffer_size 8K;
client_header_buffer_size 1k;
client_max_body_size 2m;
large_client_header_buffers 2 1k;
5.彻底避免磁盘IO
最彻底的避免磁盘IO的方法是不使用磁盘,如果费用不是问题且数据量不大,那么可以使用ramdisk,将所有数据放到内存中。
6.网络IO
为了降低网络IO,我们使用gzip module来压缩传输的数据量。设置gzip_comp_level的值为4-5应该比较合适,如果设置更大没什么用处,白白浪费CPU。
gzip on;
gzip_comp_level 5;
gzip_min_length 1000;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain application/xml;
gzip_disable "MSIE [1-6]\.";
四、什么?上边还不够?!
如果经过上述的优化仍然不能满足的要求,那么建议你考虑增加更多的服务器吧,稍微花费一点钱买服务器,比你在这里浪费时间微调nginx要来得更有效果。
--------
# 与系统CPU的core总数对应
worker_processes 24;
# nginx可以打开的文件描述符的数量
# 通常在OS中用'ulimit -n 200000'
# 或者在/etc/security/limits.conf设置
worker_rlimit_nofile 200000;
# 错误日志仅记录crit以上级别
error_log /var/log/nginx/error.log crit
# 每个worker process可以最多服务多少客户端
# (Max clients = worker_connections * worker_processes)
# "Max clients" 也受限于系统中可用的socket数量(~64k)
worker_connections 4000;
# 使用epoll模型
use epoll;
# 当nginx接到新连接的请求时,会尽可能的接受更多的连接
# 如果worker_connections参数设置较低,则此参数会对nginx产生冲击,谨慎!
multi_accept on;
# 缓存打开的文件描述符,参数值应该根据具体应用场景调整(不要照搬下边的数值!)
open_file_cache max=200000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# 缓冲log写操作,以提高磁盘IO效率,或者直接关闭access_log
#access_log /var/log/nginx/access.log main buffer=16k;
access_log off;
# 调用Sendfile实现内核"zero copy"
# 一般应该打开,如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络I/O处理速度
sendfile on;
# Tcp_nopush选项会让nginx尝试在一个packet内发送其HTTP响应,而不是分帧传送
# 这对优化吞吐率很有用处,对于在调用sendfile函数前prepending headers也有作用
tcp_nopush on;
# 不缓冲data-sends(禁用Nagle算法). Good for sending frequent small bursts of data in real time.
tcp_nodelay on;
# keep-alive连接的超时时间,server会在此时间之后关闭连接
keepalive_timeout 15;
# client的请求可以转换成keep-alive连接的数量,如果为了性能测试,可以设置的高一些,默认100
#keepalive_requests 100000;
# 允许server在client停止响应以后关闭连接,释放分配给该连接的内存
reset_timedout_connection on;
# 如果client对于body的请求超过这个时间,则发送"request timed out"响应,默认60秒
# 防范慢查询攻击
client_body_timeout 10;
# 如果client停止读取数据, 在此时间以后释放该连接,默认是60秒
send_timeout 2;
# 打开gzip压缩,减少数据传输量
gzip on;
gzip_static on;
gzip_comp_level 9;
gzip_min_length 1400;
gzip_vary on;
gzip_http_version 1.1;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css text/xml text/javascript image/gif image/jpeg application/x-javascript application/xml;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
---------------
--------基本的nginx.conf----
user nginx;
worker_processes 24;
error_log /var/log/nginx/error.log crit;
pid /var/run/nginx.pid;
events {
worker_connections 2048;
use epoll;
multi_accept on;
}
worker_rlimit_nofile 200000;
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main buffer=16k;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
open_file_cache max=200000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
client_body_buffer_size 8K;
client_header_buffer_size 1k;
client_max_body_size 2m;
large_client_header_buffers 2 1k;
keepalive_timeout 15;
reset_timedout_connection on;
client_body_timeout 10;
send_timeout 2;
gzip on;
gzip_static on;
gzip_comp_level 9;
gzip_min_length 1400;
gzip_vary on;
gzip_http_version 1.1;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css text/xml text/javascript image/gif image/jpeg application/x-javascript application/xml;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
include /etc/nginx/conf.d/*.conf;
}
防止php文件解析漏洞
# Pass all .php files onto a php-fpm/php-fcgi server.
location ~ \.php$ {
# Zero-day exploit defense.
# http://forum.nginx.org/read.php?2,88845,page=3
# Won't work properly (404 error) if the file is not stored on this server, which is entirely possible with php-fpm/php-fcgi.
# Comment the 'try_files' line out if you set up php-fpm/php-fcgi on another machine. And then cross your fingers that you won't get hacked.
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
#NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
include fastcgi_params;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# fastcgi_intercept_errors on;
# With php5-cgi alone:
# fastcgi_pass 127.0.0.1:9000;
# With php5-fpm:
fastcgi_pass unix:/var/run/php5-fpm.sock;
}
php-fpm中的配置,与nginx 的fastcgi_pass的路径一致即可,目录要有相应读写权限
; Note: This value is mandatory.
listen = /var/run/php5-fpm.sock
---------------