原来一直用动态IP(Oray.com 或 Dnspod.com )在一台老机上(赛扬1.2G)使用 Ubuntu 6.06 + LAMP 运行 Uchome 和 Doku Wiki ,因为流量在200~300个PV,而且好像 Uchome 的缓存设计可能比较好,所以运行一年多也没管。前几天网站换回自己原来的个人的 WordPress 博客,用 Apache2 简直就维持不下去了。听说,Nginx 服务器适合低配置机器,于是,这几天把 WEB 服务器换成了 Nginx + PHP5-CGI。下面简要的小结一下。
上面说用 Apache2 维持不下去并不是说换成 WordPress 博客后流量就神奇般的增加了。问题可能应该主要是因为那些各种搜索引擎看到我网站的内容似乎发生了大的变化,于是像开会似的纷纷来到网站上爬,我在一天晚上看了一下日志,那些著名的搜索引擎基本上就到齐了,尤其是晚上到第二天早上,搞得整晚负载高达20以上,根本无法用。听说 WordPress 缓存插件有用,把 W3 Total Cache 和 WP Super Cache 都试了一下,没用。好像还更糟糕,因为缓存技术可能在新的访问时会向硬盘上写静态文件,结果弄得 io 等待 (top 命令的 wa)高达80%左右,这时我看 CPU 也就 10% 左右,瓶颈应该就是磁盘子系统了。如果再频繁的写缓存文件,好像是雪上加霜。所以我觉得缓存是不行了。不过,缓存对提高用户体验还是很不错的,网页一点就打开的感觉真好 🙂 但是我还没有计划买空间来放这个博客。于是考虑用 Nginx 代替 Apache2 来试试。
因为 Ubuntu 6.06 Server 没有现成的 Nginx 和 启动 php5-cgi 的 spawn-cgi ,所以都是从官网下载稳定版来编译的。分别是 nginx-0.8.53 和 spawn-fcgi-1.6.3。用默认的方法编译完二者后,再 sudo apt-get install php5-cgi ,要用的运行环境就 OK 了。
把正在 Apache2 下运行的网站的主要部分复制了一份在 /usr/local/nginx/html 下,把数据库复制了一个。边学边弄,直到调试成功,再停掉 Apache2,把 Nginx 的主目录指向原来的网站,启动 Nginx 和 php5-cgi 就 OK 了。经过 50 个小时左右的运行,一切正常,只有约半个小时时间的负载在20左右,其余基本都在1以下。当然,还有待长期观察。
下面总结一下转换过程中的一些设置要点。
Nginx 的设置文件主要就是那个 /usr/local/nginx/conf/nginx.conf ,在默认配置的基础上,我主要改动了下面一些地方:
全局:
改变运行用户:
user www-data;
http {} 区块内的:
启用页面 gzip 压缩传输:
gzip on;
gzip_min_length 1000;
gzip_buffers 4 8k;
gzip_types text/plain application/x-javascript text/css application/xml;
各个 server {} 区块内,如:
listen 80;
server_name www.learndiary.com;
各个 location 区块内定义的重定向等,如:
location / {
root /var/www/site;
index index.php index.html index.htm;
include wordpress_params_regular;
}
里面的那个 "include wordpress_params_regular;" 是为 wordpress 写的重定向规则。是我从网上抄来修改的,后面我会贴出来的。这之类的东西网上都有,大家碰到需要的时候,一搜一大把。
location ~ \.php$ {} 让我花了点时间解决,说“no input file specified",把默认的内容中的 “fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;”改成 “fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;”就OK 了。
因为我的网站文件与原来的 Apache2 服务器下的基本未动,去掉下面这个区块的注释保证用户不能直接访问里面的 .htaccess 文件。万一以后回到 Apache 下面有用的。
location ~ /\.ht {
deny all;
}
其中最主要的时间花在把原来用 .htaccess 定义的重定向规则等变成 Nginx 下的配置文件。所有的配置文件都放在和 nginx.conf 一起,在定义相应的区块(location)时,把针对这个区块的配置文件包含进去(include)就行了。比如:针对 WordPress 2.8.1 网址伪静态化的重写规则(顶层目录),参考: Nginx+FastCGI 运行 WordPress 和 WP Super Cache:http://www.vpsee.com/2009/06/run-wordpress-wpsupercache-with-nginx-fastcgi/ ,下面评论中那个针对 wp super cache 的静态化重写规则也是参考此页面:
if (-f $request_filename) {
expires max;
break;
}
if (-d $request_filename) {
break;
}
rewrite ^(.+)$ /index.php?q=$1 last;
error_page 404 = /index.php?q=$uri;
针对DokuWiki Release 2009-02-14b (目录 /good/中):
rewrite ^/good/_media/(.*) /good/lib/exe/fetch.php?media=$1 last;
rewrite ^/good/_detail/(.*) /good/lib/exe/detail.php?media=$1 last;
rewrite ^/good/_export/([^/]+)/(.*) /good/doku.php?do=export_$1&id=$2 last;
if (!-f $request_filename) {
rewrite ^/good/(.*) /good/doku.php?id=$1&$args last;
}
另外,如果需要对某目录设置访问密码,可以在 nginx.conf 中其相应的区块中加入如:
location / {
root /var/www/secret;
index index.php index.html index.htm;
auth_basic "Members Area";
auth_basic_user_file /path/passwordfile;
}
这个密码文件 passwordfile 可以用 Apache 下的 htpasswd 产生,如:
htpasswd -c passwordfile username
下面是设置 nginx 和 php5-cgi 的控制代码(分别源自: http://articles.slicehost.com/2007/10/17/ubuntu-lts-adding-an-nginx-init-script 和: http://www.unixaid.info/index.php/unixtecspt/31-botndinst/737-ubuntunginx-fastcgi-php)
/etc/init.d/nginx
#!/bin/sh
### BEGIN INIT INFO
# Provides: nginx
# Required-Start: $all
# Required-Stop: $all
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts the nginx web server
# Description: starts nginx using start-stop-daemon
### END INIT INFO
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/local/nginx/sbin/nginx
NAME=nginx
DESC=nginx
test -x $DAEMON || exit 0
set -e
case "$1" in
start)
echo -n "Starting $DESC: "
start-stop-daemon --start --quiet --pidfile /usr/local/nginx/logs/$NAME.pid \
--exec $DAEMON -- $DAEMON_OPTS
echo "$NAME."
;;
stop)
echo -n "Stopping $DESC: "
start-stop-daemon --stop --quiet --pidfile /usr/local/nginx/logs/$NAME.pid \
--exec $DAEMON
echo "$NAME."
;;
restart|force-reload)
echo -n "Restarting $DESC: "
start-stop-daemon --stop --quiet --pidfile \
/usr/local/nginx/logs/$NAME.pid --exec $DAEMON
sleep 1
start-stop-daemon --start --quiet --pidfile \
/usr/local/nginx/logs/$NAME.pid --exec $DAEMON -- $DAEMON_OPTS
echo "$NAME."
;;
reload)
echo -n "Reloading $DESC configuration: "
start-stop-daemon --stop --signal HUP --quiet --pidfile /usr/local/nginx/logs/$NAME.pid \
--exec $DAEMON
echo "$NAME."
;;
*)
N=/etc/init.d/$NAME
echo "Usage: $N {start|stop|restart|force-reload}" >&2
exit 1
;;
esac
exit 0
/etc/init.d/spawn-fcgi
#!/bin/bash
### BEGIN INIT INFO
# /etc/init.d/spawn-fcgi
# Provides: hto
# Required-Start: $local_fs $remote_fs $network $syslog
# Required-Stop: $local_fs $remote_fs $network $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts the fastcgi server
# Description: starts fastcgi using start-stop-daemon
### END INIT INFO
set -e
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DESC="spawn-fcgi daemon"
NAME=spawn-fcgi
DAEMON=/usr/local/bin/$NAME
# Gracefully exit if the package has been removed.
test -x $DAEMON || exit 0
d_start() {
$DAEMON -a 127.0.0.1 -p 9000 -u www-data -g www-data -f /usr/bin/php5-cgi -C 4 > /dev/null 2>&1
$DAEMON -a 127.0.0.1 -p 9000 -u www-data -g www-data -f /usr/bin/php5-cgi -C 4 > /dev/null 2>&1 || echo -n " already running"
}
d_stop() {
/usr/bin/killall -9 php5-cgi > /dev/null 2>&1 || echo -n " not running"
}
case "$1" in
start)
echo -n "Starting $DESC: $NAME"
d_start
echo "."
;;
stop)
echo -n "Stopping $DESC: $NAME"
d_stop
echo "."
;;
restart)
echo -n "Restarting $DESC: $NAME"
d_stop
sleep 1
d_start
echo "."
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|restart}" >&2
exit 3
;;
esac
exit 0
然后,再用 update-rc.d nginx(或spawn-fcgi) defaults 将服务更新到各个相应的运行级别中去。
另外,我现在用的是 dnspod.com 的动态IP服务,是自己根据他们提供的 VERSION 2.5 API 用 bash 写的,供需要的朋友参考(今天去看已经2.7版本了,但试了一下根据2.5版写的API还能用,我看到2.7版的API有一个专门针对更新动态 DNS 记录的接口地址: https://www.dnspod.com/API/Record.Ddns ):
#!/bin/bash
# curlddns.sh 用于在 Linux 环境下,当 ADSL IP 发生改变时,发送通知邮件和更新 DNSPOD.COM 上的 DNS 记录。
# DNSPod 用户 API 文档版本:V2.5,Ubuntu 6.06 上测试通过
# Author: littlebat dashing.meng@gmail.com http://www.learndiary.com/ http://www.openlong.com/
# Version: 0.0.1.1
login_email="yourmail@mail.com"
login_password="youpassword"
format="json"
# 取得域名ID的命令:
# curl -k -A "Curl DDNS Client/0.0.1.1 (yourmail@mail.com)" -d \
# "login_email=yourmail@mail.com&login_password=yourpassword&format=json&domain=yourdomain.com" \
# https://www.dnspod.com/API/Domain.Id
domain_id="xxx"
# 取得记录列表的命令:
# curl -k -A "Curl DDNS Client/0.0.1.1 (yourmail@mail.com)" -d \
# "login_email=yourmail@mail.com&login_password=yourpassword&format=json&domain_id=yourdomain_id&offset=M&length=N" \
# https://www.dnspod.com/API/Record.List
record_id="xxx"
sub_domain="www"
record_type="A"
record_line="默认"
value=""
mx="0"
ttl="600"
# ipconf 用于存储旧 IP 的值,只有当 IP 改变时才发送通知邮件和更新 DNSPOD 上的记录
ipconf="curlddns.conf"
logfile="curlddns.log"
while [ 1 ]
do
#从路由器管理界面取得公网IP,因为有时需要取一次以上才能取到,所以加了判断。有的路由器不支持从管理界面取IP。也可以用其它方法取得 IP。
i=0
value="0"
value2="1"
while [[ -z "$value" || "$value" == "0.0.0.0" || "$value" != "$value2" ]]
do
let "i+=1"
value=`curl -u admin:admin http://192.168.1.1/st.htm | grep wanIP=".*"\ \; | sed -e "s/wanIP=\"//" -e "s/\"\ \;//g"`
value2=`echo $value | grep -o -E '([0-9]{1,3}\.){3}[0-9]{1,3}'`
# echo "`date`: the $i time: value=$value, value2=$value2" >> $logfile
done
echo "`date`: current ip is: $value" >> $logfile
oldvalue=`cat $ipconf`
echo "The old ip is: $oldvalue" >> $logfile
#如果 IP 发生改变,则发送邮件通知用户,并更新 Dnspod 上的记录
if [ "$value" != "$oldvalue" ]
then
echo "ip has changed, send info mail and update record..." >> $logfile
#发送通知IP改变的邮件
while [ 1 ]
do
info="`date`, Ip has changed from $oldvalue to $value."
echo $info | /usr/bin/mail -s "IP changed" yourmail@mail.com
status=$?
if [ $status = 0 ]
then
echo "`date`: sent IP changed mail ok. " >> $logfile
break
fi
echo "`date`: sent IP changed mail failed." >> $logfile
sleep 10s
done
returncode="0"
agent="Curl DDNS Client/0.0.1 (yourmail@mail.com)"
poststr="login_email=$login_email&login_password=$login_password&format=$format&domain_id=$domain_id&record_id=$record_id&sub_domain=$sub_domain&record_type=$record_type&record_line=$record_line&value=$value&mx=$mx&ttl=$ttl"
apiurl="https://www.dnspod.com/API/Record.Modify"
# echo "The command is: curl -k -A \"$agent\" -d \"$poststr\" $apiurl" >> $logfile
#更新 DNSPOD 上的记录,如果不成功则每隔一分钟执行一次更新操作,直到更新成功。
while [ "$returncode" != "1" ]
do
returnstr=`curl -k -A "$agent" -d "$poststr" $apiurl`
returncode=`echo $returnstr | grep -o -E '"code":".{1,2}"' | grep -o -E '"[-,0-9]{1,2}"' | grep -o -E '[-,0-9]{1,2}'`
echo "`date`, the return string of dnspod when update record is: $returnstr." >> $logfile
echo "the return code of update record is: $returncode." >> $logfile
if [ "$returncode" == "1" ]
then
# 将新的 IP 值写入文件
echo $value > $ipconf
# 发送更新 DNSPOD 更新记录成功邮件
echo "`date`, update dnspod record ok and send info mail..." >> $logfile
while [ 1 ]
do
info="`date`, update dnspod record ok."
echo $info | /usr/bin/mail -s "update dnspod record ok" yourmail@mail.com
status=$?
if [ $status = 0 ]
then
echo "`date`: sent update dnspod record ok mail ok. " >> $logfile
break
fi
echo "`date`: sent update dnspod record ok mail failed." >> $logfile
sleep 10s
done
else
echo "`date`, update dnspod record failed, sleep 1 minutes..." >> $logfile
sleep 1m
fi
done
fi
sleep 30s
done
exit 0
最后是我的两个没有解决的问题:
1、怎样在 nginx 配置文件里写把如:rss.php?uid=1 这样带查询字符串的URL 301重定向到一个新的网址?请教过网友( http://cafeneko.info/2010/10/nginx_rewrite_note/ )但还没有去试验。我不想试了,新建了一个 rss.php 文件,在里面用 php 处理了重定向,以后有需要时再试验一下。
2、如何分别为不同的目录配置不同的 auto_prepend_file ?机器上的全部网站共用 /etc/php5/cgi/php.ini 中的配置我知道如:
auto_prepend_file = " /var/www/commonprepend.php"
关键是分别配置不知道,Apache2 可以在不同目录下的 .htaccess 文件中分别配置如:
php_value auto_prepend_file /var/www/commonprepend.php
一些关于缓存的想法:
前面提过缓存插件的问题,不过缓存对提高用户浏览体验不错,用得恰当对服务器的负载是应该有减轻的好处的,如果以后有必要,我想再在下面两个缓存(或叫加速?)试一下:
1、听说 php 有个 APC 模块可以缓存 PHP 中间码不错((参见:php加速 PHP APC 浅析,我已经加载了zend优化器);
2、用 curl 自动访问网页访问统计帐号里面的统计数据,用脚本手工生成和更新访问最多的前100个页面的静态 html 文件。这样就不是访问时才生成,而是有控制的主动预先生成和更新,应该不会出现前面估计是缓存插件造成的磁盘io负载太高的情况。
好了,暂时总结这么些。前面的东西有不少是源自网络,在此向他们表示谢谢。欢迎交流。
更新:2010.12.17:补充现在的完整环境: 用 ADSL 动态 IP 搭建的服务器:四川电信 ADSL,Dnspod.com 动态解析,赛扬1.2G, 810E, 384M, 15G, Ubuntu 6.06 LTS, Nginx, Mysql, PHP, eAccelerator , WordPress, Wp Super Cache 静态化缓存插件, Dokuwiki
上面的 wp super cache 可以有更具体的设置,比如:在搜索引擎访问时禁止生成缓存可以解决无效缓存占用服务器资源的情况。参见:WordPress优化缓存插件WP Super Cache安装与设置 http://www.zhukun.net/archives/669
而关于带查询字符串的 URL 重定向可以这样:
location = /home/rss.php {
root /var/www/main;
if ($args = "uid=1"){
rewrite ^ http://www.learndiary.com/feed/? permanent;
}
}
今天安装了一个叫作 eaccelerator 的 php 加速器,试了一下,相比 Zend ZendOptimizer 的效果在我的老机上好像有些许的提高,简单的作了一下 ab 压力测试:
eaccelerator的结果:
Requests per second: 0.49 [#/sec] (mean)
Time per request: 10202.990 [ms] (mean)
Time per request: 2040.598 [ms] (mean, across all concurrent requests)
Transfer rate: 32.01 [Kbytes/sec] received
Zend ZendOptimizer的结果:
Requests per second: 0.34 [#/sec] (mean)
Time per request: 14687.243 [ms] (mean)
Time per request: 2937.449 [ms] (mean, across all concurrent requests)
Transfer rate: 22.12 [Kbytes/sec] received
另外,在 Ubuntu 6.06 上编译安装最新的 eaccelerator-0.9.6.1 好像通不过,换成 eaccelerator-0.9.6 就行了。
上面对缓存插件的结论下早了,昨晚重新安装了wp super cache,效果明显,虽然博客的流量也用不着缓存,但是快总比慢好吧。相对于静态化带来的弊端,其速度的提升不是一个数量级(静态html 文件对 php),下面是 ab 测试结果,可以看出,服器的开销可以忽略不计了,上面在用 Zend Optimizer 和 eAccelerator 时作ab测试时,服务器负载4.3左右,而现在只有零点几。同样的命令:
ab -c5 -n3000 http://www.learndiary.com/ ,而且上面的测试因为太慢,中途过了300就取消掉,这次是执行完毕的,共 114.064 秒。下面是结果摘要:
Requests per second: 26.30 [#/sec] (mean)
Time per request: 190.107 [ms] (mean)
Time per request: 38.021 [ms] (mean, across all concurrent requests)
Transfer rate: 1721.25 [Kbytes/sec] received
这时,瓶颈就不是服务器能力了,而是带宽了(三次测试我均在局域网内做的)。
下面是我设置 wp super cache 的一些要点:
1、我使用的是 2.8.1 的 wordpress,可支持的最新的 wp super cache 是0.9.9.3;
2、WP Super Cache 状态选中:启动 启用 WP Cache 与 Super Cache 与 Don’t cache pages for known users. (Logged in users and those that comment);
3、我的博客访问量少,使用了预加载,点勾作者推荐的:Preload mode (garbage collection only on half-on cache files. Recommended.) todo: 不懂 half-on 是什么文件,现在对这个选项的意思理解为垃圾回收机制对一般的预加载文件不起作用。
手动执行了预加载,在 ./wp-content/cache/supercache/www.learndiary.com/ 的下面目录中生成了全部日记和独立页面及首页的静态文件;没有选择定时生成,有必要时再手动刷新一次;
4、启用 Super Cache 压缩
5、过期时限:86400秒(24小时)
6、不缓存:
存档 (is_archive)
标签 (is_tag)
分类 (is_category)
搜索页 (is_search)
相应的 URL 重定向配置文件:
# if the requested file exists, return it immediately
if (-f $request_filename) {
expires 30d;
break;
}
set $supercache_file '';
set $supercache_uri $request_uri;
if ($request_method = POST) {
set $supercache_uri '';
}
# Using pretty permalinks, so bypass the cache for any query string
if ($query_string) {
set $supercache_uri '';
}
if ($http_cookie ~* "comment_author_|wordpress|wp-postpass_" ) {
set $supercache_uri '';
}
# if we haven't bypassed the cache, specify our supercache file
if ($supercache_uri ~ ^(.+)$) {
set $supercache_file /wp-content/cache/supercache/$http_host/$1index.html;
}
# only rewrite to the supercache file if it actually exists
if (-f $document_root$supercache_file) {
rewrite ^(.*)$ $supercache_file break;
}
# all other requests go to WordPress
if (!-e $request_filename) {
rewrite . /index.php last;
}
启用这个配置文件,就要把日记中 wordpress 的rewrite 配置文件去掉,如果把这个配置放在了那个只针对 wordpress 伪静态化的规则后面,例如日记的 URL 将会首先被伪静态的规则处理,而不会被定向到 wp super cache 插件的静态化页面。
另外,我在日记中好像错怪那些搜索引擎造成服务器整晚的负载高达20以上,因为 wp super cache 默认是禁止掉了响应这些搜索引擎的请求。又或者是当时只是试的 w3 total cache ?我忘了。但是不管怎么说,把 Apache 换成 Nginx + php5-cgi + eAccelerator + wp super cache 后,确实效果明显。推荐碰到类似问题的朋友可以一试。不过,还在用这么老的服务器的朋友不多吧 🙂
todo: 关于三种环境下的下载速度差异的原理:
同样是 “wget http://www.learndiary.com/文件名” 网站上一个 100M 的测试文件,以下三种环境速度不是一个级别的差距,原理不是很明白。
1)、在服务器上,在 /etc/hosts 中添加本机 IP 的记录,如:“ 192.168.1.2 http://www.learndiary.com ”时,下载速度是约 11M/S;
2)、在服务器上,去掉 /etc/hosts 文件中的记录时,下载速度约是 2M/S。在局域中的其它机器上下载也差不多;
3)、在广域网中,有时下载速度只有不到 10KB/S,好的时候也不过大概 40~50KB/S。
我估计的原因是第一种是在本机内部传输数据,第二种是通过路由器的局域网中,第三种是通过广域网传输。但是原理不清楚,记在此处供以后有必要时进一步学习。
todo: Zend Optimizer 和 eAccelerator 加速器是否可以并存?是否应该并存?现在是只加载 eAccelerator。
大概就是这样,转移到 nginx 服务器的学习就暂时到此为一阶段了,以后有需要再研究。新的发现或错误纠正多会在这个日记的评论中发布,小的改动就直接修改日记或评论。这也是我的习惯。