如果你在使用nginx作为Web服务器,很可能在寻找性能提升和更快的速度。Nginx在默认情况下速度就很快,但是你可以优化其性能以及与nginx协同运行的所有组件(比如PHP和MySQL)的性能。下面是一份并不全面的列表,介绍了配置LEMP系统(Linux、nginx、MySQL和PHP-FPM),以获得***性能的要点和技巧。这些技巧对我来说很管用,但是你的情况可能不一样。别同时运用所有这些要点和技巧,而是应该逐个试一试,看看这些改动对你的系统的性能有什么样的影响。
我并不保证本文会适用于你的情况!
1. 借助noatime和nodiratime挂载分区,减少磁盘的输入/输出
把noatime和nodiratime添加到/etc/fstab中的挂载选项:
vi /etc/fstab
# /etc/fstab:静态文件系统信息。
#
# 使用“blkid”打印设备的全局唯一标识符;这可与UUID=结合使用,
# 作为一种更可靠的方式来命名即使磁盘被添加和移除仍可正常工作的设备。参阅fstab(5)。
#
# <文件系统> <挂载点> <类型> <选项> <转储> <传递>
proc /proc proc defaults 0 0
# 安装过程中,/出现在/dev/sda2上
UUID=9cc886cd-98f3-435a-9830-46b316e2a20e / ext3 errors=remount-ro,noatime,nodiratime,usrjquota=quota.user,grpjquota=quota.group,jqfmt=vfsv0 0 1
# 安装过程中,置换出现在/dev/sda1上
UUID=bba13162-121d-40a4-90a7-10f78a0097ae none swap sw 0 0
/dev/scd0 /media/cdrom0 udf,iso9660 user,noauto 0 0
#Parallels共享文件夹挂载
none /media/psf prl_fs sync,nosuid,nodev,noatime,share,nofail 0 0
重新挂载修改后的分区,方法如下(确保你为每个分区使用正确的挂载点):
mount -o remount /
#p# 2. 调整nginx
2.1 worker_processes
确保你在/etc/nginx/nginx.conf中使用了数量正确的worker_processes(工作进程)。这个数应该相当于下列命名的输出中的处理器核心数量:
cat /proc/cpuinfo | grep processor
root@server1:~# cat /proc/cpuinfo | grep processor
processor : 0
processor : 1
processor : 2
processor : 3
processor : 4
processor : 5
processor : 6
processor : 7
root@server1:~#
在该例子中,我们有八个处理器核心,于是我们设置
vi /etc/nginx/nginx.conf
[...]
worker_processes 8;
[...]
2.2 keepalive_timeout、sendfile、tcp_nopush和tcp_nodelay
把keepalive_timeout设成一个合理的值,比如2秒。启用sendfile、tcp_nopush和tcp_nodelay:
vi /etc/nginx/nginx.conf
[...]
http {
[...]
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 2;
types_hash_max_size 2048;
server_tokens off;
[...]
}
[...]
#p# 2.3 文件缓存
启用nginx文件缓存:
vi /etc/nginx/nginx.conf
[...]
http {
[...]
##
# 文件缓存的设置
##
open_file_cache max=5000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
[...]
}
[...]
#p# 2.4 启用Gzip压缩
你可以在下面进一步了解Gzip压缩:《如何使用nginx的HttpGzipModule(Debian Squeeze)节省流量?》(http://www.howtoforge.com/how-to-save-traffic-with-nginxs-httpgzipmodule-debian-squeeze)。
vi /etc/nginx/nginx.conf
[...]
http {
[...]
##
# Gzip设置
##
gzip on;
gzip_static on;
gzip_disable "msie6";
gzip_http_version 1.1;
gzip_vary on;
gzip_comp_level 6;
gzip_proxied any;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript text/x-js;
gzip_buffers 16 8k;
[...]
}
[...]
#p# 2.5 启用SSL会话缓存
如果你服务于https网站,就应该启用SSL会话缓存:
vi /etc/nginx/nginx.conf
[...]
http {
[...]
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
[...]
}
[...]
#p# 2.6 使用FastCGI缓存
如果你有可以缓存的PHP内存,可以使用nginx FastCGI缓存来缓存该内容。在你的nginx.conf中,添加一行,类似这一行:
vi /etc/nginx/nginx.conf
[...]
http {
[...]
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=microcache:10m max_size=1000m inactive=60m;
[...]
}
[...]
缓存目录/var/cache/nginx必须要有,而且对nginx来说必须是可写的:
mkdir /var/cache/nginx
chown www-data:www-data /var/cache/nginx
如果使用tmpfs,你甚至可以把目录直接放入到你服务器的内存中,这在速度上提供了另一个小小的优势——想了解更多的信息,请参阅这篇教程:《使用tmpfs,将文件/目录存储在内存中》(http://www.howtoforge.com/storing-files-directories-in-memory-with-tmpfs)。
在你的vhost配置中,把下列语句块添加到你的location ~ \.php$ {}部分(你可以修改代码段,这取决于内容何时缓存、何时不缓存):
[...]
# 设置var默认值
set $no_cache "";
# 如果没有GET/HEAD,不缓存&通过cookie将用户标记为不可缓存1秒
if ($request_method !~ ^(GET|HEAD)$) {
set $no_cache "1";
}
# 如果需要,不丢弃任何缓存cookie
# (由于某种原因,如果添加到之前的if语句块,add_header失败)
if ($no_cache = "1") {
add_header Set-Cookie "_mcnc=1; Max-Age=2; Path=/";
add_header X-Microcachable "0";
}
# 如果非缓存cookie已被设置,绕过缓存
if ($http_cookie ~* "_mcnc") {
set $no_cache "1";
}
# 如果标记已被设置,绕过缓存
fastcgi_no_cache $no_cache;
fastcgi_cache_bypass $no_cache;
fastcgi_cache microcache;
fastcgi_cache_key $scheme$host$request_uri$request_method;
fastcgi_cache_valid 200 301 302 10m;
fastcgi_cache_use_stale updating error timeout invalid_header http_500;
fastcgi_pass_header Set-Cookie;
fastcgi_pass_header Cookie;
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
[...]
所以整个location ~ \.php$ {}部分看起来如下:
[...]
location ~ \.php$ {
# 设置var默认值
set $no_cache "";
#如果没有GET/HEAD,不缓存&通过cookie将用户标记为不可缓存1秒
if ($request_method !~ ^(GET|HEAD)$) {
set $no_cache "1";
}
#如果需要,不丢弃任何缓存cookie
#(由于某种原因,如果添加到之前的if语句块,add_header失败)
if ($no_cache = "1") {
add_header Set-Cookie "_mcnc=1; Max-Age=2; Path=/";
add_header X-Microcachable "0";
}
#如果非缓存cookie已被设置,绕过缓存
if ($http_cookie ~* "_mcnc") {
set $no_cache "1";
}
#如果标记已被设置,绕过缓存
fastcgi_no_cache $no_cache;
fastcgi_cache_bypass $no_cache;
fastcgi_cache microcache;
fastcgi_cache_key $scheme$host$request_uri$request_method;
fastcgi_cache_valid 200 301 302 10m;
fastcgi_cache_use_stale updating error timeout invalid_header http_500;
fastcgi_pass_header Set-Cookie;
fastcgi_pass_header Cookie;
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_pass unix:/var/lib/php5-fpm/web1.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_script_name;
fastcgi_intercept_errors on;
}
[...]
这将把返回代码是200、301和302的页面缓存10分钟。
#p# 2.7 使用FastCGI缓冲器
在你的vhost配置中,你可以把下列几行添加到你的location ~ \.php$ {}部分:
[...]
fastcgi_buffer_size 128k;
fastcgi_buffers 256 16k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;
fastcgi_read_timeout 240;
[...]
整个location ~ \.php$ {}部分看起来如下:
[...]
location ~ \.php$ {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_pass unix:/var/lib/php5-fpm/web1.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_script_name;
fastcgi_intercept_errors on;
fastcgi_buffer_size 128k;
fastcgi_buffers 256 16k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;
fastcgi_read_timeout 240;
}
[...]
#p# 2.8 使用memcached
nginx可以读取直接来自memcached的整个页面。所以,如果你的W eb应用程序能够在memcached中存储整个页面,nginx就能从memcached读取该页面。示例配置应该如下:
[...]
location ~ \.php$ {
set $no_cache "";
if ($query_string ~ ".+") {
set $no_cache "1";
}
if ($request_method !~ ^(GET|HEAD)$ ) {
set $no_cache "1";
}
if ($request_uri ~ "nocache") {
set $no_cache "1";
}
if ($no_cache = "1") {
return 405;
}
set $memcached_key $host$request_uri;
memcached_pass 127.0.0.1:11211;
default_type text/html;
error_page 404 405 502 = @php;
expires epoch;
}
location @php {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_pass unix:/var/lib/php5-fpm/web1.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_script_name;
fastcgi_intercept_errors on;
}
[...]
重要的是,你的Web应用程序在memcached中存储页面所使用的密钥与nginx从memcached中读取这些页面所使用的密码是同一把(本例中是$host$request_uri),不过这不管用。
如果你在memcached中存储了许多数据,就要确保你已为memcached分配了足够的内容,比如:
vi /etc/memcached.conf
[...]
# 开始内存限额为64MB。这很合理。
# 注意:守护程序会增加到这个大小,但是不会一开始就占用这么大的内存。
-m 512
[...]
#p# 2.9 使用expires指令,让浏览器缓存静态文件
只要使用expires指令,就可以让访客的浏览器缓存不经常变化的文件,如图片、CSS和JS等文件(详见http://wiki.nginx.org/HttpHeadersModule#expires):
[...]
location ~* \.(jpg|jpeg|png|gif|ico)$ {
expires 365d;
}
[...]
2.10 禁用静态文件的日志功能
将图片或CSS文件记入到访问日志中通常没有多大意义。为了减少磁盘的输入/输出,我们可以禁用这类文件的日志功能,比如如下:
[...]
location ~* \.(jpg|jpeg|png|gif|ico)$ {
log_not_found off;
access_log off;
}
[...]
#p# 3 调整PHP-FPM
3.1 使用A PHP Opcode缓存,比如Xcache或APC
确保你已安装了PHP opcode缓存,比如Xcache或APC。在Debian/Ubuntu上,可以按如下方式安装Xcache:
apt-get install php5-xcache
可以按如下方式安装APC:
apt-get install php-apc
确保你只安装了其中一个(安装了Xcache或APC),而不是两个都安装了。安装之后重新装入PHP-FPM:
/etc/init.d/php5-fpm reload
3.2 为Xcache/APC分配足够的内存
如果你有大量的PHP脚本,可能应该增加分配给Xcache或APC的内容。如果是Xcache,你可以在/etc/php5/conf.d/xcache.ini中这么操作:
vi /etc/php5/conf.d/xcache.ini
[...]
xcache.size = 512M
[...]
如果是APC,方法类似:
vi /etc/php5/conf.d/apc.ini
[...]
apc.shm_size="512"
[...]
修改后重新装入PHP-FPM:
/etc/init.d/php5-fpm reload
#p# 3.3 PHP-FPM紧急设置
这更像是提高可靠性的设置,而不是提升性能的设置:如果PHP-FPM停止工作,它会重启自己:
vi /etc/php5/fpm/php-fpm.conf
[...]
; 如果在emergency_restart_interval设定的时间间隔内,这个数量的子进程以SIGSEGV或SIGBUS退出,
; 那么FPM会重启。值为“o”意味着“断开”。
; 默认值:0
emergency_restart_threshold = 10
; emergency_restart_interval所用的时间间隔决定了平稳重启何时开始。
; 这有助于避开方向感应器的共享内存里面偶尔出现的故障。
; 可用单位:s(秒)、m(分)、h(小时)或d(天)
; 默认单位:秒
; 默认值:0
emergency_restart_interval = 1m
; 子进程等待来自主进程的信号有所反应的时间限制。
; 可用单位:s(秒)、m(分)、h(小时)或d(天)
; 默认单位:秒
; 默认值:0
process_control_timeout = 10s
[...]
#p# 3.4 如果PHP >= 5.3.9,使用ondemand进程管理器
如果你使用PHP >= 5.3.9,可以使用PHP-FPM池中的ondemand进程管理器,而不是静态或动态,这将为你节省一些内存:
[...]
pm = ondemandpm.max_children = 100pm.process_idle_timeout = 5s
[...]
3.5 使用Unix套接字,而不是TCP套接字
为了降低网络开销,你应该配置PHP-FPM池,以便使用Unix套接字,而不是TCP套接字:
[...]
;listen = 127.0.0.1:9000listen = /var/lib/php5-fpm/www.socklisten.owner = www-datalisten.group = www-datalisten.mode = 0660
[...]
如果你变更这个必须,当然必须调整nginx vhost中的location ~ \.php$ {}部分,以便使用该套接字(fastcgi_pass unix:/var/lib/php5-fpm/www.sock,而不是fastcgi_pass 127.0.0.1:9000):
[...]
location ~ \.php$ {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
##fastcgi_pass 127.0.0.1:9000;
fastcgi_pass unix:/var/lib/php5-fpm/www.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_script_name;
fastcgi_intercept_errors on;
}
[...]
#p# 3.6 使用套接字,避免忙碌网站上的502坏网关错误
如果你使用Unix套接字和PHP-FPM,你可能会遇到忙碌网站随机出现的502坏网关错误。为了避免这个问题,我们提高了允许连接至套接字的***数量。打开/etc/sysctl.conf...
vi /etc/sysctl.conf
然后设置:
[...]
net.core.somaxconn = 4096
[...]
运行:
sysctl -p
之后等变更生效。
#p# 4 调整MySQL
4.1 优化你的my.cnf
你应该使用mysqltuner.pl或tuning-primer.sh脚本,查看你应该在my.cnf文件中调整哪些设置。最重要的变量之一是query_cache_size,如果你使用InnoDB表,
innodb_buffer_pool_size.
这是来自配备16GB内存的测试服务器的示例配置,大约30个数据库有一半是MyISAM表,另一半是是InnoDB表——这对于使用基准测试工具(ab)进行压力测试的数据库驱动的测试网站来说效果相当好:
[...]
key_buffer = 256M
max_allowed_packet = 16M
thread_stack = 192K
thread_cache_size = 100
table_open_cache = 16384
table_definition_cache = 8192
sort_buffer_size = 256K
read_buffer_size = 128K
read_rnd_buffer_size = 256K
myisam_sort_buffer_size = 64M
myisam_use_mmap = 1
thread_concurrency = 10
wait_timeout = 30
myisam-recover = BACKUP,FORCE
query_cache_limit = 10M
query_cache_size = 1024M
query_cache_type = 1
join_buffer_size = 4M
log_slow_queries = /var/log/mysql/mysql-slow.log
long_query_time = 1
expire_logs_days = 10
max_binlog_size = 100M
innodb_buffer_pool_size = 2048M
innodb_log_file_size = 256M
innodb_log_buffer_size = 16M
innodb_flush_log_at_trx_commit = 0
innodb_thread_concurrency = 8
innodb_read_io_threads = 64
innodb_write_io_threads = 64
innodb_io_capacity = 50000
innodb_flush_method = O_DIRECT
innodb_file_per_table
innodb_additional_mem_pool_size = 256M
transaction-isolation = READ-COMMITTED
innodb_support_xa = 0
innodb_commit_concurrency = 8
innodb_old_blocks_time = 1000
[...]
请注意:如果你需要遵守ACID规定,必须将innodb_flush_log_at_trx_commit设成1。想了解这方面的更多信息,请访问:http://dev.mysql.com/doc/refman/5.5/en/innodb-parameters.html#sysvar_innodb_flush_log_at_trx_commit。
只要你在固态硬盘上使用MySQL,才应该将innodb_io_capacity设成很高的值。如果你在普通硬盘上使用MySQL,***别理这一行。
4.2 使用固态硬盘
如果你在固态硬盘上使用MySQL,可以大幅提升性能,因为这大大降低磁盘的输入/输出。为此,最容易的办法就是把/var/lib/mysql目录挂载到固态硬盘上。
#p# 5 Web应用程序缓存
许多Web应用程序提供了创建整个页面缓存的可能性,这个缓存存储在硬盘上,可以直接被nginx访问,那样它就能绕开整个PHP-MySQL堆栈架构。这样的Web应用程序包括:WordPress(带WP Super Cache或W3 Total Cache插件)、Drupal(带Boost模块)以及TYPO3(带nc_staticfilecache扩展)。这大幅提升了性能。
你还可以进一步为静态文件缓存提速,只要使用tmpfs文件系统把静态文件缓存直接放入到服务器的内存中:
《使用tmpfs,在内存中存储文件/目录》(http://www.howtoforge.com/storing-files-directories-in-memory-with-tmpfs)
6 相关链接
•nginx维基:http://wiki.nginx.org/Main
•PHP:http://www.php.net/
•PHP-FPM:http://php-fpm.org/
•MySQL:http://www.mysql.com/