Nginx ("engine x") 是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 Nginx 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,它已经在该站点运行超过两年半了。Igor 将源代码以类BSD许可证的形式发布。
Nginx 超越 Apache 的高性能和稳定性,使得国内使用 Nginx 作为 Web 服务器的网站也越来越多,其中包括新浪博客 、新浪播客 、网易新闻 等门户网站频道,六间房 、56.com 、土豆等视频分享网站,Discuz!官方论坛 、水木社区 等知名论坛,豆瓣 、YUPOO相册 、海内SNS 、迅雷在线、大众点评网 等新兴Web 2.0网
站。
在高并发连接的情况下,Nginx是Apache服务器不错的替代品。Nginx同时也可以作为7层负载均衡服务器来使用。根据我的测试结果,Nginx 0.7.61 + PHP 5.2.10 (FastCGI) 可以承受3万以上的并发连接数,相当于同等环境下Apache的10倍 。
根据我的经验,4GB内存的服务器+Apache(prefork模式)一般只能处理3000个并发连接,因为它们将占用3GB以上的内存,还得为系统预留1GB的内存。我曾经就有两台Apache服务器,因为在配置文件中设置的MaxClients为4000,当Apache并发连接数达到3800 时,导致服务器内存和Swap空间用满而崩溃。
而这台 Nginx 0.7.61 + PHP 5.2.10 (FastCGI) 服务器在3万并发连接下,开启的10个Nginx进程消耗150M内存(15M*10=150M),开启的64个php-cgi进程消耗1280M内存(20M*64=1280M),加上系统自身消耗的内存,总共消耗不到2GB内存。如果服务器内存较小,完全可以只开启25个php-cgi进程,这样 php-cgi消耗的总内存数才500M。
在3万并发连接下,访问Nginx 0.7.61 + PHP 5.2.10 (FastCGI) 服务器的PHP程序,仍然速度飞快。下图为Nginx的状态监控页面,显示的活动连接数为28457(关于Nginx的监控页配置,会在本文接下来所给出的Nginx配置文件中写明):
![](http://blogimg.chinaunix.net/blog/upfile2/090903163705.jpg)
![](http://blogimg.chinaunix.net/blog/upfile2/090903174727.gif)
下面是用100个并发连接分别去压生产环境中同一负载均衡器VIP下、提供相同服务的两台服务器,一台为Nginx,另一台为Apache,Nginx每秒处理的请求数是Apache的两倍多,Nginx服务器的系统负载、CPU使用率远低于Apache:
你可以将连接数开到10000~30000,去压Nginx和Apache上的phpinfo.php,这是用浏览器访问Nginx上的 phpinfo.php一切正常,而访问Apache服务器的phpinfo.php,则是该页无法显示。4G内存的服务器,即使再优化,Apache也很难在“webbench -c 30000 -t 60 http://xxx.xxx.xxx.xxx/phpinfo.php ”的压力情况下正常访问,而调整参数优化后的Nginx可以。
webbench 下载地址:http://blog.s135.com/post/288/
注意:webbench 做压力测试时,该软件自身也会消耗CPU和内存资源,为了测试准确,请将 webbench 安装在别的服务器上。
测试结果:##### Nginx + PHP #####
[root@localhost webbench-1.5]# webbench -c 100 -t 30 http://192.168.1.21/phpinfo.php Benchmarking: GET http://192.168.1.21/phpinfo.php Speed=102450 pages/min, 16490596 bytes/sec. top - 14:06:13 up 27 days, 2:25, 2 users, load average: 14.57, 9.89, 6.51 |
测试结果:##### Apache + PHP #####
[root@localhost webbench-1.5]# webbench -c 100 -t 30 http://192.168.1.27/phpinfo.php Benchmarking: GET http://192.168.1.27/phpinfo.php Speed=42184 pages/min, 31512914 bytes/sec. top - 14:06:20 up 27 days, 2:13, 2 users, load average: 62.15, 26.36, 13.42 |
处理大量的连接的读写,Apache所采用的select网络I/O模型非常低效。下面用一个比喻来解析Apache采用的select模型和Nginx采用的epoll模型进行之间的区别:
假设你在大学读书,住的宿舍楼有很多间房间,你的朋友要来找你。select版宿管大妈就会带着你的朋友挨个房间去找,直到找到你为止。而epoll版宿管大妈会先记下每位同学的房间号,你的朋友来时,只需告诉你的朋友你住在哪个房间即可,不用亲自带着你的朋友满大楼找人。如果来了10000个人,都要找自己住这栋楼的同学时,select版和epoll版宿管大妈,谁的效率更高,不言自明。同理,在高并发服务器中,轮询I/O是最耗时间的操作之一,select和epoll的性能谁的性能更高,同样十分明了。
(系统要求:Linux 2.6+ 内核,本文中的Linux操作系统为CentOS 5.3,另在RedHat AS5上也安装成功)
sudo -s LANG=C yum -y install gcc gcc-c++ autoconf libjpeg libjpeg-devel libpng libpng-devel freetype freetype-devel libxml2 libxml2-devel zlib zlib-devel glibc glibc-devel glib2 glib2-devel bzip2 bzip2-devel ncurses ncurses-devel curl curl-devel e2fsprogs e2fsprogs-devel krb5 krb5-devel libidn libidn-devel openssl openssl-devel openldap openldap-devel nss_ldap openldap-clients openldap-servers |
2、【适用RedHat操作系统】RedHat等其他Linux发行版可从安装光盘中找到这些程序库的RPM包(事先可通过类似 “rpm -qa | grep libjpeg”的命令查看所需的RPM包是否存在,通常是“xxx-devel”不存在,需要安装)。RedHat可以直接利用CentOS的RPM包安装,以下是RPM包下载网址:
①、RedHat AS4 & CentOS 4
http://mirrors.163.com/centos/4/os/i386/CentOS/RPMS/
http://mirrors.163.com/centos/4/os/x86_64/CentOS/RPMS/
②、RedHat AS5 & CentOS 5
http://mirrors.163.com/centos/5/os/i386/CentOS/
http://mirrors.163.com/centos/5/os/x86_64/CentOS/
③、RPM包搜索网站
http://rpm.pbone.net/
http://www.rpmfind.net/
④、RedHat AS4 系统环境,通常情况下缺少的支持包安装:
Ⅰ、i386 系统
wget http://blog.s135.com/soft/linux/nginx_php/rpm/i386/libjpeg-devel-6b-33.i386.rpm rpm -ivh libjpeg-devel-6b-33.i386.rpm wget http://blog.s135.com/soft/linux/nginx_php/rpm/i386/freetype-devel-2.1.9-1.i386.rpm rpm -ivh freetype-devel-2.1.9-1.i386.rpm wget http://blog.s135.com/soft/linux/nginx_php/rpm/i386/libpng-devel-1.2.7-1.i386.rpm rpm -ivh libpng-devel-1.2.7-1.i386.rpm |
wget http://blog.s135.com/soft/linux/nginx_php/rpm/x86_64/libjpeg-devel-6b-33.x86_64.rpm rpm -ivh libjpeg-devel-6b-33.x86_64.rpm wget http://blog.s135.com/soft/linux/nginx_php/rpm/x86_64/freetype-devel-2.1.9-1.x86_64.rpm rpm -ivh freetype-devel-2.1.9-1.x86_64.rpm wget http://blog.s135.com/soft/linux/nginx_php/rpm/x86_64/libpng-devel-1.2.7-1.x86_64.rpm rpm -ivh libpng-devel-1.2.7-1.x86_64.rpm |
本文中提到的所有开源软件为截止到 2009年06月26日 的最新稳定版。
①、从软件的官方网站下载:
1、编译安装PHP 5.2.10所需的支持库:
libiconv支持所有编码方式的任意转换 tar zxvf libiconv-1.13.tar.gz |
2、编译安装MySQL 5.1.35
/usr/sbin/groupadd mysql /usr/sbin/useradd -g mysql mysql tar zxvf mysql-5.1.35.tar.gz cd mysql-5.1.35/ ./configure --prefix=/usr/local/mysql / --enable-assembler / --with-extra-charsets=complex / --enable-thread-safe-client / --with-big-tables / --with-readline / --with-ssl / --with-embedded-server / --enable-local-infile / --with-plugins=innobase make && make install chmod +w /usr/local/webserver/mysql chown -R mysql:mysql /usr/local/webserver/mysql cd ../ |
附:以下为附加步骤,如果你想在这台服务器上运行MySQL数据库,则执行以下两步。如果你只是希望让PHP支持MySQL扩展库,能够连接其他服务器上的MySQL数据库,那么,以下两步无需执行。
①、创建MySQL数据库存放目录
mkdir -p /var/mysql/data chown -R mysql:mysql /var/mysql |
②、以mysql用户帐号的身份建立数据表:
/usr/local/mysql/bin/mysql_install_db --basedir=/usr/local/mysql --datadir=/var/mysql/data --user=mysql |
③、创建my.cnf配置文件:
vi /etc/my.cnf |
输入以下内容:
[client] default-character-set = utf8 port = 3306 socket = /tmp/mysql.sock [mysql] prompt="(/u:blog.s135.com:)[/d]> " no-auto-rehash [mysqld] #default-character-set = utf8 user = mysql port = 3306 socket = /tmp/mysql.sock basedir = /usr/localmysql datadir = /var/mysql/data open_files_limit = 10240 back_log = 600 max_connections = 3000 max_connect_errors = 6000 table_cache = 614 external-locking = FALSE max_allowed_packet = 32M sort_buffer_size = 2M join_buffer_size = 2M thread_cache_size = 300 thread_concurrency = 8 query_cache_size = 32M query_cache_limit = 2M query_cache_min_res_unit = 2k default-storage-engine = MyISAM default_table_type = MyISAM thread_stack = 192K transaction_isolation = READ-COMMITTED tmp_table_size = 246M max_heap_table_size = 246M long_query_time = 1 log_long_format log-bin = /var/mysql/data/binlog binlog_cache_size = 4M binlog_format = MIXED max_binlog_cache_size = 8M max_binlog_size = 512M expire_logs_days = 7 key_buffer_size = 256M read_buffer_size = 1M read_rnd_buffer_size = 16M bulk_insert_buffer_size = 64M myisam_sort_buffer_size = 128M myisam_max_sort_file_size = 10G myisam_max_extra_sort_file_size = 10G myisam_repair_threads = 1 myisam_recover skip-name-resolve master-connect-retry = 10 slave-skip-errors = 1032,1062,126,1114,1146,1048,1396 server-id = 1 innodb_additional_mem_pool_size = 16M innodb_buffer_pool_size = 2048M innodb_data_file_path = ibdata1:1024M:autoextend innodb_file_io_threads = 4 innodb_thread_concurrency = 8 innodb_flush_log_at_trx_commit = 2 innodb_log_buffer_size = 16M innodb_log_file_size = 128M innodb_log_files_in_group = 3 innodb_max_dirty_pages_pct = 90 innodb_lock_wait_timeout = 120 innodb_file_per_table = 0 [mysqldump] quick max_allowed_packet = 32M |
vi /var/mysql/data/mysql |
#!/bin/sh mysql_port=3306 mysql_username="admin" mysql_password="12345678" function_start_mysql() { printf "Starting MySQL.../n" /bin/sh /usr/local/mysql/bin/mysqld_safe --defaults-file=/var/mysql/${mysql_port}/my.cnf 2>&1 > /dev/null & } function_stop_mysql() { printf "Stoping MySQL.../n" /usr/local/mysql/bin/mysqladmin -u ${mysql_username} -p${mysql_password} -S /tmp/mysql.sock shutdown } function_restart_mysql() { printf "Restarting MySQL.../n" function_stop_mysql sleep 5 function_start_mysql } function_kill_mysql() { kill -9 $(ps -ef | grep 'bin/mysqld_safe' | grep ${mysql_port} | awk '{printf $2}') kill -9 $(ps -ef | grep 'libexec/mysqld' | grep ${mysql_port} | awk '{printf $2}') } if [ "$1" = "start" ]; then function_start_mysql elif [ "$1" = "stop" ]; then function_stop_mysql elif [ "$1" = "restart" ]; then function_restart_mysql elif [ "$1" = "kill" ]; then function_kill_mysql else printf "Usage: /var/mysql/${mysql_port}/mysql {start|stop|restart|kill}/n" fi |
chmod +x /var/mysql/data/mysql |
/var/mysql/data/mysql start |
/usr/local/mysql/bin/mysql -u root -p -S /tmp/mysql.sock |
GRANT ALL PRIVILEGES ON *.* TO 'admin'@'localhost' IDENTIFIED BY '12345678'; GRANT ALL PRIVILEGES ON *.* TO 'admin'@'127.0.0.1' IDENTIFIED BY '12345678'; |
tar zxvf php-5.2.10.tar.gz ./configure --prefix=/usr/local/php / make ZEND_EXTRA_LIBS='-liconv' 如果mysql是用rpm安装,.configure中红色的部分去 |
tar zxvf memcache-2.2.5.tgz --with-pdo-mysql=/usr/local/mysql |
手工修改: 查找/usr/local/php/etc/php.ini中的extension_dir = "./"
修改为extension_dir = "/usr/local/php/lib/php/extensions/no-debug-non-zts-20060613/"
并在此行后增加以下几行,然后保存:
extension = "memcache.so"
extension = "pdo_mysql.so"
extension = "imagick.so"
再查找output_buffering = Off
修改为output_buffering = On
自动修改: 若嫌手工修改麻烦,可执行以下shell命令,自动完成对php.ini文件的修改:
sed -i 's#extension_dir = "./"#extension_dir = "/usr/local/php/lib/php/extensions/no-debug-non-zts-20060613/"/nextension = "memcache.so"/nextension = "pdo_mysql.so"/nextension = "imagick.so"/n#' /usr/local/php/etc/php.ini sed -i 's#output_buffering = Off#output_buffering = On#' /usr/local/php/etc/php.ini sed -i "s#; always_populate_raw_post_data = On#always_populate_raw_post_data = On#g" /usr/local/php/etc/php.ini |
mkdir -p /usr/local/webserver/eaccelerator_cache vi /usr/local/webserver/php/etc/php.ini |
[eaccelerator] zend_extension="/usr/local/php/lib/php/extensions/no-debug-non-zts-20060613/eaccelerator.so" eaccelerator.shm_size="64" eaccelerator.cache_dir="/usr/local/eaccelerator_cache" eaccelerator.enable="1" eaccelerator.optimizer="1" eaccelerator.check_mtime="1" eaccelerator.debug="0" eaccelerator.filter="" eaccelerator.shm_max="0" eaccelerator.shm_ttl="3600" eaccelerator.shm_prune_period="3600" eaccelerator.shm_only="0" eaccelerator.compress="1" eaccelerator.compress_level="9" |
/usr/sbin/groupadd www /usr/sbin/useradd -g www www mkdir -p /var/htdocs/blog chmod +w /var/htdocs/blog chown -R www:www /var/htdocs/blog mkdir -p /var/htdocs/www chmod +w /var/htdocs/www chown -R www:www /var/htdocs/www |
在/usr/local/php/etc/目录中创建php-fpm.conf文件:
rm -f /usr/local/php/etc/php-fpm.conf vi /usr/local/php/etc/php-fpm.conf |
|
ulimit -SHn 51200 /usr/local/php/sbin/php-fpm start |
1、安装Nginx所需的pcre库:
tar zxvf pcre-7.9.tar.gz cd pcre-7.9/ ./configure make && make install cd ../ |
tar zxvf nginx-0.7.61.tar.gz cd nginx-0.7.61/ ./configure --user=www --group=www --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module make && make install cd ../ |
mkdir -p /var/logs chmod +w /var/logs chown -R www:www /var/logs |
①、在/usr/local/nginx/conf/目录中创建nginx.conf文件:
rm -f /usr/local/nginx/conf/nginx.conf vi /usr/local/nginx/conf/nginx.conf |
user www www; worker_processes 8; error_log /data1/logs/nginx_error.log crit; pid /usr/local/nginx/nginx.pid; #Specifies the value for maximum file descriptors that can be opened by this process. worker_rlimit_nofile 51200; events { use epoll; worker_connections 51200; } http { include mime.types; default_type application/octet-stream; #charset gb2312; server_names_hash_bucket_size 128; client_header_buffer_size 32k; large_client_header_buffers 4 32k; client_max_body_size 8m; sendfile on; tcp_nopush on; keepalive_timeout 60; tcp_nodelay on; fastcgi_connect_timeout 300; fastcgi_send_timeout 300; fastcgi_read_timeout 300; fastcgi_buffer_size 64k; fastcgi_buffers 4 64k; fastcgi_busy_buffers_size 128k; fastcgi_temp_file_write_size 128k; 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; #limit_zone crawler $binary_remote_addr 10m; server { listen 80; server_name blog.s135.com; index index.html index.htm index.php; root /data0/htdocs/blog; #limit_conn crawler 20; location ~ .*/.(php|php5)?$ { #fastcgi_pass unix:/tmp/php-cgi.sock; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fcgi.conf; } location ~ .*/.(gif|jpg|jpeg|png|bmp|swf)$ { expires 30d; } location ~ .*/.(js|css)?$ { expires 1h; } log_format access '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" $http_x_forwarded_for'; access_log /data1/logs/access.log access; } server { listen 80; server_name www.s135.com; index index.html index.htm index.php; root /data0/htdocs/www; location ~ .*/.(php|php5)?$ { #fastcgi_pass unix:/tmp/php-cgi.sock; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fcgi.conf; } log_format wwwlogs '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" $http_x_forwarded_for'; access_log /data1/logs/wwwlogs.log wwwlogs; } server { listen 80; server_name status.blog.s135.com; location / { stub_status on; access_log off; } } } |
vi /usr/local/nginx/conf/fcgi.conf |
astcgi_param GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_param REQUEST_URI $request_uri; fastcgi_param DOCUMENT_URI $document_uri; fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param REMOTE_PORT $remote_port; fastcgi_param SERVER_ADDR $server_addr; fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_NAME $server_name; # PHP only, required if PHP was built with --enable-force-cgi-redirect fastcgi_param REDIRECT_STATUS 200; |
ulimit -SHn 51200 /usr/local/nginx/sbin/nginx |
ulimit -SHn 51200 /usr/local/php/sbin/php-fpm start /usr/local/nginx/sbin/nginx |
# Add net.ipv4.tcp_max_syn_backlog = 65536 net.core.netdev_max_backlog = 32768 net.core.somaxconn = 32768 net.core.wmem_default = 8388608 net.core.rmem_default = 8388608 net.core.rmem_max = 16777216 net.core.wmem_max = 16777216 net.ipv4.tcp_timestamps = 0 net.ipv4.tcp_synack_retries = 2 net.ipv4.tcp_syn_retries = 2 net.ipv4.tcp_tw_recycle = 1 #net.ipv4.tcp_tw_len = 1 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_mem = 94500000 915000000 927000000 net.ipv4.tcp_max_orphans = 3276800 #net.ipv4.tcp_fin_timeout = 30 #net.ipv4.tcp_keepalive_time = 120 net.ipv4.ip_local_port_range = 1024 65535 |
/sbin/sysctl -p |
1、修改/usr/local/nginx/conf/nginx.conf配置文件后,请执行以下命令检查配置文件是否正确:
/usr/local/nginx/sbin/nginx -t |
the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
the configuration file /usr/local/nginx/conf/nginx.conf was tested successfully
2、这时,输入以下命令查看Nginx主进程号:
ps -ef | grep "nginx: master process" | grep -v "grep" | awk -F ' ' '{print $2}' |
6302
这时,执行以下命令即可使修改过的Nginx配置文件生效:
或者无需这么麻烦,找到Nginx的Pid文件:
1、创建脚本/usr/local/nginx/sbin/cut_nginx_log.sh
输入以下内容:
# This script run at 00:00
# The Nginx logs path
logs_path="/usr/local/nginx/logs/"
mkdir -p ${logs_path}$(date -d "yesterday" +"%Y")/$(date -d "yesterday" +"%m")/
mv ${logs_path}access.log ${logs_path}$(date -d "yesterday" +"%Y")/$(date -d "yesterday" +"%m")/access_$(date -d "yesterday" +"%Y%m%d").log
kill -USR1 `cat /usr/local/nginx/nginx.pid`
2、设置crontab,每天凌晨00:00切割nginx访问日志
输入以下内容: