2013年5月

公司的nginx+php以及memcached & redis的服务器一直服务正常,但是今天突然有两次出现系统响应极度延时的情况。 分析系统看,php处理每个请求非常延时,超过1s的时间。而且系统的load受到雪崩的拖累,从平时的4~10,提升到了100+。
  用strace分析的时候,发现连接memcache服务器的时候,经常出现EADDRNOTAVAIL (Cannot assign requested address)的错误,按理说这应该是单进程local port耗尽的表现。然后机器的netstat -ant查看的TIME_WAIT非常大,集中在访问memecache和redis的端口。
  所以一度怀疑是网络硬件出现的问题,因为ping测试的时候发现少量波动。然后在机房重启了路由器,发现似乎缓解了。 但是后来发现不是这个原因,于是这个问题在晚上服务高峰期再次爆发。
   因为系统内核参数已经设置了TCP连接的回收和复用:
net.ipv4.tcp_tw_recycle=1 
net.ipv4.tcp_tw_reuse = 1 
所以考虑trop掉过多的TIME_WAIT,保证服务器的正常运行, debian默认的tcp_max_tw_buckets是262144。而系统故障时,php前端机的netstat -ant|wc -l的数量也是在这个范围, 所以这个才是问题原因所在。
    因此准备改这个设置:sudo -w net.ipv4.tcp_max_tw_buckets=3000,
先丢掉一次,然后再设置为一个较低的数字:
sudo -w net.ipv4.tcp_max_tw_buckets=30000
     这样看起来系统的服务响应速度恢复了, load也降到了正常的水平。 但是,造成这个问题的原因应该还是在php-fpm或者相关的模块和代码上,还得接着排查。

PS: 后续分析原因是,有人把net.ipv4.tcp_timestamps的参数改为0了。所以导致recycle和reuse全部失效,没法回收多余的time wait连接。改回这个就好了,其他配置可以回滚。

PHP配置值通过 php_value 或者 php_flag 设置,并且会覆盖以前的值。请注意 disable_functions 或者 disable_classes 在 php.ini 之中定义的值不会被覆盖掉,但是会将新的设置附加在原有值的后面。
      使用 php_admin_value 或者 php_admin_flag 定义的值,不能被 PHP 代码中的 ini_set() 覆盖。
      比如memory_limit配置,可以在fpm/php.ini中设置 memory_limit = 128M, 也可以在fpm/pool.d/www.conf或者fpm/php-fpm.conf中定义 php_admin_value[memory_limit] = 64M, 后者不能被ini_set覆盖。

在 nginx.conf 中设定 PHP:

set $php_value "pcre.backtrack_limit=424242"; 
set $php_value "$php_value \n pcre.recursion_limit=99999";
fastcgi_param PHP_VALUE $php_value; 
fastcgi_param PHP_ADMIN_VALUE "open_basedir=/var/www/htdocs";

$cat /etc/php5/fpm/pool.d/www.conf
[www]
user = www-data
group = www-data
;listen = /var/run/php5-fpm.sock
listen = 127.0.0.1:9000

listen.backlog = 4096

pm = dynamic
pm.max_children = 10
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 2

; pm.process_idle_timeout = 10s;

php_flag[display_errors] = off
php_admin_value[memory_limit] = 64M
rlimit_files = 2000
request_terminate_timeout = 300
chdir = /
pm.status_path = /status.php

    以前的squeeze没有自带php-fpm,然后觉得要更新testing的php5-fpm和php5.4要升级一大堆软件和依赖,太不方便和混乱,于是使用spawn-fcgi实现fastcgi。现在要替换spawn-fcgi了,就把以前的启动脚本备份一下,算是留个记录。
./spwan-php.sh

#!/bin/sh
USER=www-data
GROUP=www-data
PATH=/sbin:/bin:/usr/sbin:/usr/bin


SSD="/sbin/start-stop-daemon"
PHP_FCGI_CHILDREN=2
PHP_FCGI_MAX_REQUESTS=200
RETVAL=0

FCGI_DAEMON="/usr/bin/spawn-fcgi"
FCGI_PROGRAM="/usr/bin/php-cgi"
FCGI_PORT="9000"
FCGI_SOCKET="/tmp/php-fastcgi.sock"
FCGI_PIDFILE="/var/run/spawn-fcgi.pid"

test -x $FCGI_DAEMON || exit0
set -e
exportPHP_FCGI_CHILDREN PHP_FCGI_MAX_REQUESTS
. /lib/lsb/init-functions

case "$1" in
 start)
       log_daemon_msg "Starting spawn-fcgi"
       if ! $FCGI_DAEMON -a 127.0.0.1 -p $FCGI_PORT -f $FCGI_PROGRAM -u $USER -g $GROUP -C $PHP_FCGI_CHILDREN -P $FCGI_PIDFILE; then
           log_end_msg 1
       else
           log_end_msg 0
       fi
       RETVAL=$?
 ;;
 stop)
       log_daemon_msg "Killing all spawn-fcgi processes"
       ifkillall --signal 2 php-cgi > /dev/null 2> /dev/null; then
           #log_end_msg 0
           echo
       else
           #log_end_msg 1
           echo
       fi
       RETVAL=$?
 ;;
reload|restart|force-reload)
       $0 stop
       sleep 3
       $0 start
 ;;
 *)
       echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
       exit1
 ;;
esac

exit $RETVAL


debian 7正式发布以后。我把所有服务器的apt source从testing切换到了stable了。
    然后,想到我的vps的配置还是squeeze和testing混用的,于是统一改成stable了。接着执行sudo apt-get dist-upgrade, 发现几乎平滑升级完毕。mysql从5.1换到了5.5, php从5.3升级到5.4. 这个blog在upgrade完毕以后,没有任何配置修改的情况下下正常访问。看来debian的版本升级还是一如既往的可靠和稳定。
    准备用php5-fpm替换现在使用的spawn-fcgi + php, 然后就finish了。

$cat /etc/issue


Debian GNU/Linux 7.0 \n \l