小内存vps上wordpress优化

wordpress网站优化

由于图便宜,买了一个就128M内存的vps。所以非常关注性能的问题。

性能主要是2点,php和mysql的部分。

mysql的部分网上很多都有所有说到了,不启动innodb,限制binlog cache, 限制sort buffer等等。具体可以参见如下

[mysql]
no-auto-rehash

[mysqld]
user = mysql
port = 3306
open_files_limit = 600
back_log = 20
max_connections = 100
max_connect_errors = 200
table_cache = 60
external-locking = FALSE
max_allowed_packet = 16M
sort_buffer_size = 128K
join_buffer_size = 128K
thread_cache_size = 10
thread_concurrency = 8
query_cache_size = 0M
query_cache_limit = 2M
query_cache_min_res_unit = 2k
default_table_type = MyISAM
thread_stack = 192K
transaction_isolation = READ-UNCOMMITTED
tmp_table_size = 512K
max_heap_table_size = 32M
long_query_time = 1
log_long_format
server-id = 1
binlog_cache_size = 2M
max_binlog_cache_size = 4M
max_binlog_size = 512M
expire_logs_days = 7
key_buffer_size = 4M
read_buffer_size = 1M
read_rnd_buffer_size = 2M
bulk_insert_buffer_size = 2M
myisam_sort_buffer_size = 4M
myisam_max_sort_file_size = 10G
myisam_max_extra_sort_file_size = 10G
myisam_repair_threads = 1
myisam_recover
skip-innodb

[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

php的部分。
使用fastcgi, 前端使用nginx来进行fastcgi代理。 fastcgi使用的php-fpm。

使用php-fpm要特别注意如下设置:

; Choose how the process manager will control the number of child processes.
; Possible Values:
; static – a fixed number (pm.max_children) of child processes;
; dynamic – the number of child processes are set dynamically based on the
; following directives:
; pm.max_children – the maximum number of children that can
; be alive at the same time.
; pm.start_servers – the number of children created on startup.
; pm.min_spare_servers – the minimum number of children in ‘idle’
; state (waiting to process). If the number
; of ‘idle’ processes is less than this
; number then some children will be created.
; pm.max_spare_servers – the maximum number of children in ‘idle’
; state (waiting to process). If the number
; of ‘idle’ processes is greater than this
; number then some children will be killed.
; Note: This value is mandatory.
pm = dynamic

; The number of child processes to be created when pm is set to ‘static’ and the
; maximum number of child processes to be created when pm is set to ‘dynamic’.
; This value sets the limit on the number of simultaneous requests that will be
; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP
; CGI.
; Note: Used when pm is set to either ‘static’ or ‘dynamic’
; Note: This value is mandatory.
pm.max_children = 50

; The number of child processes created on startup.
; Note: Used only when pm is set to ‘dynamic’
; Default Value: min_spare_servers + (max_spare_servers – min_spare_servers) / 2
pm.start_servers = 2

; The desired minimum number of idle server processes.
; Note: Used only when pm is set to ‘dynamic’
; Note: Mandatory when pm is set to ‘dynamic’
pm.min_spare_servers = 1

; The desired maximum number of idle server processes.
; Note: Used only when pm is set to ‘dynamic’
; Note: Mandatory when pm is set to ‘dynamic’
pm.max_spare_servers = 2

不然我的小内存vps很快就会挂的。

listen的部分使用sock的方式,毕竟都是本机,而且sock的速度要比tcp要来的快的多。

listen = /var/run/phpfpm.sock

以上就是php的部分。

下面是nginx的部分

开启fastcgi cache

fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_buffer_size 32k;
fastcgi_buffers 4 32k;
fastcgi_busy_buffers_size 64k;
fastcgi_temp_file_write_size 64k;
fastcgi_cache_path /opt/server/nginx/fastcgi_cache_dir levels=1:2 keys_zone=cache_fastcgi:128m inactive=1d max_size=1g;

fastcgi_cache cache_fastcgi;
fastcgi_cache_valid 200 302 301 1h;
fastcgi_cache_valid any 1m;
fastcgi_cache_min_uses 1;
fastcgi_cache_use_stale error timeout invalid_header http_500;
fastcgi_cache_key $request_method://$host$request_uri;

静态文件过期时间

location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
expires max;
}

gzip压缩
由于这个vps不光内存少,1个月的带宽也少,那能减少传输就减少一点。

gzip on;
gzip_http_version 1.1;
gzip_vary on;
gzip_comp_level 6;
gzip_proxied any;
gzip_types text/plain text/xml text/css application/x-javascript;

wordpress本身的优化

使用wp-super-cache插件,这个插件可以把动态的php转换成静态的html代码,这样减少了很多fastcgi的压力,其实这个是最根本的解决方法。而且这些html又被缓存到了nginx中,这样访问的速度就会非常非常的快了。

###########################################

Best regards
Timo Seven
Linux System Admin & MySQL DBA

nginx调优

因为跟后端app的统计时间对不上,所以有点怀疑是不是nginx proxy这边出了问题。 一开始用的strace来进行检查,发现是 recvfrom()这个系统函数比较慢,但是要更深入的解析,那strace就没有办法了。


strace -s 5000 -rp 1991 2>&1 | awk '$1 > 0.01'

google了下下发现agentzh大神写了很多关于systemtap的小工具,于是就直接用上了进行了分析。 主要是两个部分,一个nginx的用户空间的调用,另外一个是内核空间的调用。 要调试内核空间的话一定要安装kernel-debuginfo包才行,大家根据自己的内核版本来安装吧。

https://github.com/agentzh/nginx-systemtap-toolkit

由于系统默认的systemtap版本比较低,所以要自己手动编译安装2.1以上的版本,我就装了比较新的2.2.1版本,安装这个需要先安装好kernel-devel包才行。

wget http://sourceware.org/systemtap/ftp/releases/systemtap-2.2.1.tar.gz
yum install kernel-devel-2.6.32-358.el6.x86_64
wget http://debuginfo.centos.org/6/x86_64/kernel-debuginfo-2.6.32-358.14.1.el6.x86_64.rpm
wget http://debuginfo.centos.org/6/x86_64/kernel-debuginfo-common-x86_64-2.6.32-358.14.1.el6.x86_64.rpm
rpm -ivh *.rpm

在都安装完systemtap和debuginfo包之后执行下面这个进行测试看看:

stap -v -e ‘probe vfs.read {printf(“read performedn”); exit()}’

具体的用法在https://github.com/agentzh/nginx-systemtap-toolkit都有介绍的,下面是简单演示了一下其中几个的用法

[root@11 nginx-systemtap-toolkit]# ./ngx-active-reqs -p 23187
ERROR: MAXACTION exceeded near keyword at <input type="text" />:32:13
Tracing 23187 (/usr/local/nginx/sbin/nginx)...

req "POST /bid?", r=0x11c4510, keepalive=1, spdy=0, host=www.sina.com, status=0, time=0.036s, buffered=0, conn: ssl=0, from=110.75.20.114, reqs=11, err=0, fd=272, buffered=0, sending request to upstream
req "POST /bid?", r=0x10a0fe0, keepalive=1, spdy=0, host=www.sina.com, status=0, time=0.003s, buffered=0, conn: ssl=0, from=110.75.36.5, reqs=6, err=0, fd=56, buffered=0, sending request to upstream
req "POST /bid?", r=0x11c4e50, keepalive=1, spdy=0, host=www.sina.com, status=0, time=0.011s, buffered=0, conn: ssl=0, from=110.75.20.115, reqs=23, err=0, fd=269, buffered=0, sending request to upstream
req "POST /?", r=0x10a14e0, keepalive=1, spdy=0, host=www.sina.com, status=0, time=0.001s, buffered=0, conn: ssl=0, from=101.226.62.84, reqs=26585, err=0, fd=220, buffered=0, sending request to upstream
req "POST /bid?", r=0x11af4f0, keepalive=1, spdy=0, host=www.sina.com, status=0, time=0.014s, buffered=0, conn: ssl=0, from=110.75.36.5, reqs=1, err=0, fd=316, buffered=0, sending request to upstream
req "POST /bid?", r=0x11a2ef0, keepalive=1, spdy=0, host=www.sina.com, status=0, time=0.036s, buffered=0, conn: ssl=0, from=110.75.36.3, reqs=11, err=0, fd=184, buffered=0, sending request to upstream
req "POST /bid?", r=0x10e9f30, keepalive=1, spdy=0, host=www.sina.com, status=0, time=0.017s, buffered=0, conn: ssl=0, from=110.75.36.5, reqs=2, err=0, fd=134, buffered=0, sending request to upstream
req "POST /bid?", r=0x10bac20, keepalive=1, spdy=0, host=www.sina.com, status=0, time=0.019s, buffered=0, conn: ssl=0, from=110.75.20.112, reqs=3, err=0, fd=150, buffered=0, sending request to upstream
req "POST /bid?", r=0x1180a10, keepalive=1, spdy=0, host=www.sina.com, status=0, time=0.006s, buffered=0, conn: ssl=0, from=110.75.36.4, reqs=6, err=0, fd=147, buffered=0, sending request to upstream
WARNING: Number of errors: 1, skipped probes: 0
WARNING: /usr/bin/staprun exited with status: 1
Pass 5: run failed.  [man error::pass5]

上面这段信息并没有任何问题,所以需要更详细的分析了,只好看更深入的了。这里主要是通过火焰图来显示。要生成火焰图就还要再下载一个程序。

git clone git://github.com/brendangregg/FlameGraph

生成火焰图

./ngx-sample-bt -p 30763 -t 5 -k > c.bt
./stackcollapse-stap.pl ../nginx-systemtap-toolkit/b.bt > b.cbt
./flamegraph.pl b.cbt > b.svg

生成的svg图片要用浏览器打开,可以看到每个方块调用的方法名和具体时间信息。 比如我这个几乎每个系统调用的时间都非常的短。 下面这个图就是展示了每个内核调用的时间,基本上大部分都是iptables的部分。但是每个时间还是相对比较短的。 看来要在nginx这边再优化的工作比较少了,只能在后端app上找原因了。

nginx自定义返回状态

领导要求在响应失败的时候也返回200以保证不影响客户网站,这个在F5中很容易的实现,只要一句话就可以了,但是nginx中稍微麻烦了点,要答案的直接看下面这个语句就可以了,加到每个server中就可以了。

proxy_intercept_errors on;
 error_page  404 400 500 503 502 =200 @handler;
 location @handler {
 default_type text/plain;
 return 200;
 }

至于为什么,还有没有别的方法且看下面。

本来想用 if语句来实现,但是发现根本不生效,因为无论server_status还是upstream_status出来的时候都已经返回给用户端了。所以下面的代码根本没有效果

if ($upstream_status ~ (400|404|500|503|502)){
   returen 200; break;
}
if ($server_status ~ (400|404|500|503|502)){
   returen 200; break;
}

上面两种代码都是没有效果的。那error_page还有别的实现方式吗? 当然有。

proxy_intercept_errors on;
 error_page  404 400 500 503 502 =200 /200.html;

可这样不是每个server都要建立一个本地主机,因为我前端都是proxy,每个server都建立也挺麻烦。要么每个后端都要加一个200.html,可后端本来都无法响应了,你加来又有何用呢。

最上面的那个代码为什么要加default_type text/plain; 这句呢。因为当用户默认输错,我们无法判断会有用户输入什么,当不是html或者图片啥的,那就会提示用户下载,那也无法体现我们的需求,所以必须强制加一句这个。

nginx的问题(续)

昨天说了nginx的几个问题,

由于我现在支持的主要是通用CDN,也就是小图片和CSS以及JS这些东西,并没有做大文件的cache。而问了我们视频部门他们也根本不用nginx作为cache使用,所以有些问题还是没考虑周到。

一个问题是之前发现的,在做tmpfs的时候,当你设置的cache大小为tmpfs的90%的时候,一旦要缓存的内容大于这个90%的时候,nginx会自己崩溃掉。这个我明天做个测试再重现一下,现在有点忘记原因了,好像是tmpfs会占用到实际的磁盘才导致的。

另外一个其实是upstream的问题,这个是一个新浪的老兄提的,基本现在是无解。问题是:比如平均一个文件10M,你在磁盘上存3000个文件,然后模拟多个客户端去访问,因为shm一开始是空的,所以都会去后端拽文件,我们放慢这个拽的过程,比如说网络不好吧 呵呵,那么第一个请求发现不在lookup失败,就会去upstream取数据,边取边向downsteam发,并且存在一个临时文件中,这时第二个请求来了也请求这个文件,因为第一个请求没有处理完,所以第二个请求找不到目标文件只好继续去后端拽,又生成临时文件,并发量大的时候,一个大文件会对应N多的临时文件,最终这些文件rename到一个目标文件,大大浪费了磁盘IO。这个rename是内核做的,nginx调完之后就返回了。这种问题往往会导致内部网络流量非常大,而实际却没有根本没有大。

当有多个连接来获取同一个文件的时候,squid中却没有跟nginx cache一样的问题。我们看看squid是怎么解决的吧。我们在squid有这样一个参数collapsed_forwarding on(http://wiki.squid-cache.org/Features/CollapsedForwarding),一旦设置了参数了,当发现有连接请求同一个文件的时候,squid会合并多个连接,但是它会以最慢速的那个连接去取,然后同步地传送给客户端。这样就不会重复请求后端了。但是squid也有问题,因为首先它是同步的。当用户请求的是动态的内容的话会导致客户端错误。而且当一个用户连接速度很快,一个用户连接速度很慢,那就会以最慢的速度进行获取和传输。所以在squid3.0以后取消了这个参数。

这个问题的解决办法暂时看起来只能在nginx cache前充一下热点的大文件。还好一般内网都是千兆连接,这个速度往往大于用户的连接速度。只有当文件特别大,并且热点文件特别多的情况下才会发生。而且nginx这种处理方式也有一定的好处。

nginx upstream流程我们可以参考http://bollaxu.javaeye.com/blog/900424http://bollaxu.javaeye.com/blog/900357这2篇文章。基本是由于nginx upstream为了防止锁所以进程间是不共享的。

###########################################

Best regards
Timo Seven
blog:http://www.timoseven.com
twitter: http://twitter.com/zauc
Linux System Admin  & MySQL DBA

nginx cache现有的几个问题

在我这边现实环境中主要是有三个问题。

第一个问题是当cache过期的时候,nginx会把cache状态设置为updating,但是这个updating却没有设置一个超时,所以导致的情况当一个cache过期的时候好多nginx会连接到源站取数据,而如果源站并发量有限的话,会导致源站突发负载过高,而这个时候导致nginx一直处于updating过程中,如果导致源站挂掉的话,那完蛋了。当然我们也可以设置proxy_cache_use_stale updating;但是这样会导致cache的内容不准确。

解决的方法有2种,一个是我们现在更改nginx源码,使之可以updating设置有超时时间,这样不会导致长时间连接源站造成拥塞。另外一种就是nginx cache分层,分为父节点和子节点,只有父节点才会去源站更新,子节点只从父节点上更新。

第二个问题是upstream中server的状态,我们知道upstream中可以设置proxy_next_upstream当一个服务器挂掉后自动转到其它的服务器上,但是这里有个问题就是让用户访问的时候proxy会每次都会先访问下这个坏的服务器,连接不通了再转到其它服务器上,这每次都要测试会浪费大量的时间。我们需要得到的是proxy会独立一个线程去检查后端服务器,一旦发现后端服务器挂掉的话,那直接从upstream中剔除掉这个服务器就可。而等检测后端服务器又存活了再加进来。

这个现在有ngx_http_healthcheck_module这个第三方模块。具体可以参考http://wiki.nginx.org/HttpHealthcheckModule

第三个问题是nginx proxy连接nginx cache的时候居然使用的是http1.0的,而不是http1.1,就没法使用keepalive连接后端,导致我们小文件的cache服务器连接数过高,而没法进行复用链接。

这个暂时还没有解决,不过看到http://wiki.nginx.org/HttpUpstreamKeepaliveModule这个第三方模块,但是实际还没有测试过,没法做什么结论。

其实大家可以完全查看http://wiki.nginx.org/3rdPartyModules 来看看是否针对自己的应用,有些还是我们国人自己开发的,但是针对第三方模块还是要谨慎使用,上面的healthcheck模块还是经过我们自己的开发重新修改后并进行压力测试后再上线的。开源就是这点好啊。看来我要得学习学习C语言编程来,以后自己也改改增加增加功能啥的。

###########################################

Best regards
Timo Seven
blog:http://www.timoseven.com
twitter: http://twitter.com/zauc
Linux System Admin  & MySQL DBA

nginx的if判断语句

使用nginx cache也有很长一段时间了,原来没有想到用nginx cache会有那么多问题存在,还是在实际运用中才发现如下问题的。

首先是要禁止IP访问。我这里直接给它返回403

if ($host ~ "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}") {return 403;}

这个是为了监控需要才加上的。

if ($http_user_agent ~* "monitor") {break;}

这个是当用户直接访问域名下某个文件时候,那就直接退出

if ($request_uri !~* \/$ ){
break;
}

下面这些是由于这个网站是多语言网站,当用户语言是中文的时候就返回www.timoseven.com,但是当用户是手动选择其它语言的时候就不作判断

当用户访问的不是www.timoseven.com的时候就退出,防止出现循环错误。

if ($host !~* www.timoseven.com) {
break;
}

当用户的referer是timoseven.com的时候就退出,这个为了用户手动选择语言的时候不作判断

if ($http_referer ~ timoseven.com){
break;
}

当用户浏览器语言是法语的时候就转到french.timoseven.com等等。这里的$http_accept_language就是可以获取用户的浏览器语言,当然还可以$http_accept_encoding等等,只要是用户请求头部分的都是可以用$http_开头的进行获取并进行匹配。

if ($http_accept_language ~* fr) {
rewrite ^/(.*) http://french.timoseven.com redirect;
break;
}
if ($http_accept_language ~* de) {
rewrite ^/(.*) http://german.timoseven.com redirect;
break;
}
if ($http_accept_language !~* zh) {
rewrite ^/(.*) http://english.timoseven.com redirect;
break;
}

这个是为了有时候需要用到purge进行推送的使用的方法,具体推送的可以参考之前的文章。

if ($request_method = PURGE) {rewrite ^(.*)$ /purge$1$is_args$args;}

###########################################

Best regards
Timo Seven
blog:http://www.timoseven.com/
twitter: http://twitter.com/zauc
Linux System Admin & MySQL DBA

nginx日志定制

由于工作需要,需要用到nginx的很多特性,但是每个层次需要做不同的功能。虽然下面这个日志配置文件可以很好的运行。可我就跟郭欣在《构建高性能web站点》中说的,我还没弄清楚这些日志配置之后的原理。

测试环境

1. 10.1.41.81 client
2. 10.10.72.148 proxy
3. 10.10.72.219 cache
4. 10.10.72.221 real server

日志设置的需求

1. 日志设置的结果需要在所有节点上知道它的上一级IP和用户IP,这样可以方便查找问题 2. 日志设置的第二个目的是在proxy和cache都加了response time和request time 3. 在cache层的日志增加了cache的命中结果,有MISS,EXPIRED,HIT等5种不同状态 4. 在源站加入了gzip的压缩比
5. msec是表示用毫秒表示当前写入日志的时间

意外发现

1. 客户端连接nginx是Http1.1,而proxy连接后端却是http1.0,暂时nginx还不支持连接后端使用http1.1

nginx获取realip编译

必须加入这个模块

--with-http_realip_module

nginx前段proxy日志格式

全部通过|线进行分割,方便以后进行分割统计

log_format proxy  '$remote_addr|$upstream_addr|$connection|$upstream_status|$time_local|$request|'
'$status|$body_bytes_sent|$bytes_sent|$http_referer|'
'$http_user_agent|$upstream_response_time|$msec|$request_time';

同时在server部分要这样进行设置

proxy_set_header real_ip_header X-Real-IP;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;

显示的结果如下:我们可以看到客户请求IP和后端被转发服务器的IP和端口

10.1.41.81|10.10.72.219:80|230|200|03/Sep/2010:17:33:03 +0800|GET /index.html HTTP/1.1|200|176|498|-|Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid) Firefox/3.6.8|0.000|1283506383.661|0.000

nginx cache的日志设置

也是通过竖线进行分割

log_format cache  '$remote_addr|$http_x_forwarded_for|$upstream_addr|$connection|$upstream_status|$time_local|$request|$status|'<br />
'$body_bytes_sent|$bytes_sent|$http_referer|'
'$upstream_response_time|$msec|$request_time|$upstream_cache_status';

server部分需要进行如下设置

proxy_set_header real_ip_header X-Real-IP;
proxy_set_header Host $http_host;
proxy_set_header Forwarded-For $remote_addr;

日志显示的结果如下: 当cache要从后端取数据的时候我们可以看到上一级IP,用户IP,以及需要被转发的IP。而假如是直接HIT本地的话那就不会有被转发IP了。

10.10.72.148|10.1.41.81|10.10.72.221:80|1|200|03/Sep/2010:17:55:13 +0800|GET /index.html HTTP/1.0|200|165|454|-|0.003|1283507713.029|0.003|EXPIRED
10.10.72.148|10.1.41.81|-|3|-|03/Sep/2010:17:55:13 +0800|GET /index.html HTTP/1.0|200|165|454|-|-|1283507713.358|0.000|HIT

nginx real server日志设置

log_format real '$remote_addr|$http_x_forwarded_for|$time_local|$request|'
'$status|$body_bytes_sent|$bytes_sent|$http_referer|'
'$http_user_agent|$msec|$request_time|$gzip_ratio';

得到的结果如下:我们可以看到客户端IP,和上一级CACHE的IP

10.10.72.219|10.1.41.81|03/Sep/2010:17:50:23 +0800|GET /index.html HTTP/1.0|200|165|424|-|Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid) Firefox/3.6.8|1283507423.583|0.000|18.83

###########################################

Best regards
Timo Seven
blog: http://www.timoseven.com
twitter: http://twitter.com/zauc
Linux System Admin & MySQL DBA