善用 PHP-FPM 的 slow log 分析问题

节前公司站点出现了莫名的 502 错误,在服务器配置上拆腾未果,重新开始怀疑程序问题。

关于 502 错误,具体可以参考以下两篇文章:
《自动检测 PHP-FPM 的错误并重启的 PHP 脚本》
《NGINX + PHP-FPM 502 相关事》

根据错误提示(11: Resource temporarily unavailable) ,排除掉服务器配置的问题,自然而然就怀疑是资源被程序占用光了。

这些资源包括数据库连接、文件数、锁等等,如果一个个去猜解调试甚至是走读代码,未免太费时间,也未必能发现问题所在。

好在 PHP-FPM 提供了慢执行日志,可以将执行比较慢的脚本的调用过程 dump 到日志中。

配置比较简单,PHP 5.3.3 之前设置如下:

The timeout (in seconds) for serving of single request after which a php backtrace will be dumped to slow.log file
'0s' means 'off'
<value name="request_slowlog_timeout">1s</value>

The log file for slow requests
<value name="slowlog">logs/slow.logs</value>

PHP 5.3.3 之后设置以下如下:

; The timeout for serving a single request after which a PHP backtrace will be
; dumped to the 'slowlog' file. A value of '0s' means 'off'.
; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
; Default Value: 0
request_slowlog_timeout = 1s

; The log file for slow requests
; Default Value: /usr/local/php/log/php-fpm.log.slow
slowlog = /usr/local/php/log/php-fpm.log.slow

还可以将执行时间太长的进程直接终止,设置下执行超时时间即可。
PHP 5.3.3 之前版本:

The timeout (in seconds) for serving a single request after which the worker process will be terminated
Should be used when 'max_execution_time' ini option does not stop script execution for some reason
'0s' means 'off'
<value name="request_terminate_timeout">10s</value>

PHP 5.3.3+:

; The timeout for serving a single request after which the worker process will
; be killed. This option should be used when the 'max_execution_time' ini option
; does not stop script execution for some reason. A value of '0' means 'off'.
; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
; Default Value: 0
request_terminate_timeout = 10s

加上慢执行日志后,我们可以很容易从慢执行日志中看出问题所在,比如:

Feb 07 19:00:30.378095 pid 27012 (pool default)
script_filename = /www/adshow/a.php
[0x000000000115ea08] flock() /www/backend/parser/logs.class.php:260
[0x0000000001159810] lock_stats() /www/adshow/a.php:126

Feb 07 19:00:31.033073 pid 27043 (pool default)
script_filename = /www/adshow/a.php
[0x00000000012686e8] flock() /www/backend/parser/logs.class.php:260
[0x00000000012634f0] lock_stats() /www/adshow/a.php:126

Feb 07 19:00:31.163995 pid 26979 (pool default)
script_filename = /www/adshow/a.php
[0x000000000115bda8] flock() /www/backend/parser/logs.class.php:260
[0x0000000001156bb0] lock_stats() /www/adshow/a.php:126

Feb 07 19:00:31.164102 pid 27033 (pool default)
script_filename = /www/adshow/a.php
[0x00000000013f3fa8] flock() /www/backend/parser/logs.class.php:260
[0x00000000013eedb0] lock_stats() /www/adshow/a.php:126

Feb 07 19:00:31.297313 pid 27448 (pool default)
script_filename = /www/adshow/a.php
[0x0000000001180218] flock() /www/backend/parser/logs.class.php:260
[0x000000000117b020] lock_stats() /www/adshow/a.php:126

很明显是程序中产生了死锁,导致各个 PHP-CGI 进程互相等待资源而锁死。
据此,再进行进一步的程序分析,就更具方向性了。

— EOF —

NGINX + PHP-FPM 502 相关事

NGINX + PHP-FPM 报 502 错误,我想大部分 SA 都遇到过吧。
根据报错的频率,可以分为两种情况,间歇性的502和连续性的502。
这里只讨论第一种情况——间歇性的502。

502,是后端 PHP-FPM 不可用造成的,间歇性的502一般认为是由于 PHP-FPM 进程重启造成的。

在 PHP-FPM 的配置中存在这么一项:

How much requests each process should execute before respawn.
Useful to work around memory leaks in 3rd party libraries.
For endless request processing please specify 0
Equivalent to PHP_FCGI_MAX_REQUESTS
<value name=”max_requests”>500</value>

这段配置的意思是,当一个 PHP-CGI 进程处理的请求数累积到 500 个后,自动重启该进程。

但是为什么要重启进程呢?

一般在项目中,我们多多少少都会用到一些 PHP 的第三方库,这些第三方库经常存在内存泄漏问题,如果不定期重启 PHP-CGI 进程,势必造成内存使用量不断增长。因此 PHP-FPM 作为 PHP-CGI 的管理器,提供了这么一项监控功能,对请求达到指定次数的 PHP-CGI 进程进行重启,保证内存使用量不增长。

正是因为这个机制,在高并发的站点中,经常导致 502 错误,我猜测原因是 PHP-FPM 对从 NGINX 过来的请求队列没处理好。不过我目前用的还是 PHP 5.3.2,不知道在 PHP 5.3.3 中是否还存在这个问题。

目前我们的解决方法是,把这个值尽量设置大些,尽可能减少 PHP-CGI 重新 SPAWN 的次数,同时也能提高总体性能。在我们自己实际的生产环境中发现,内存泄漏并不明显,因此我们将这个值设置得非常大(204800)。大家要根据自己的实际情况设置这个值,不能盲目地加大。

话说回来,这套机制目的只为保证 PHP-CGI 不过分地占用内存,为何不通过检测内存的方式来处理呢?我非常认同高春辉所说的,通过设置进程的峰值内在占用量来重启 PHP-CGI 进程,会是更好的一个解决方案。

期待他的建议能在今后的 PHP-FPM 版本中实现。

— EOF —

自动检测 PHP-FPM 的错误并重启的 PHP 脚本

公司的 WEB 生产服务器使用 NGINX+PHP-FPM 构建。

近日 NGINX 频报 (110: Connection timed out) 以及 (11: Resource temporarily unavailable) 的错误,出错后后端的 PHP-FPM 几乎全部挂死,重启 PHP-FPM 后又能正常工作。

初步认定是 PHP-FPM 或系统参数配置有问题,优化了系统参数并重启服务器后,目前尚未发现问题。

但是依然比较担心意外情况发生,没人想在春节期间再来照看这些服务器,索性用 PHP 写了个脚本监控错误日志,监测到错误后自动重启 PHP-FPM。

脚本下载:
phpfpm_guarder

配置说明:
$bin_tail_path tail命令路径
$bin_cp_path cp命令路径
$log_path 错误日志文件路径
$error_logs 要检测的错误类型,可以检测多种
$restart_cmd 重启 PHP-FPM 的命令
$guide_period 检测周期,单位为秒
$max_error_cnt 在检测周期里面发现多少次错误后重启服务器

没几天就要过年了,祝各位新快乐,万事如意!

— EOF —

Nginx + PHP-FPM (11: Resource temporarily unavailable)

今天在测试服务器上搭了 Nginx + PHP-FPM 的环境,结果发现 PHP 页面频繁出现 502 Bad Gateway 错误。
Nginx 版本:nginx/0.7.61
PHP-FPM:php-5.3.0-fpm-0.5.12
Nginx 错误日志:

connect() to unix:/tmp/php-fpm.socket failed (11: Resource temporarily unavailable) while connecting to upstream

看起来似乎是 PHP-FPM 的问题,看老外的讨论:
http://forum.nginx.org/read.php?3,31467,31467

原因是 PHP-FPM 在 backlog 设置为 -1 的情况下,并没有使用系统的 backlog 设置。
所以我们需要显式指定 backlog 参数。

把 PHP-FPM 配置文件中的:

<value name=”backlog”>-1</value>

改成:

<value name=”backlog”>1024</value>

问题消除!

— EOF —

Gentoo 下搭建 Nginx+ MySQL + PHP (fastcgi) 环境

安装 Nginx

* 一条命令搞定:

USE=fastcgi emerge nginx

* 新建用户和组:

groupadd www

useradd www -g www

Nginx 安装好后默认会添加 nginx 组和 nginx 用户,不过我本身还是习惯新建个 www 组和 www 用户来做 HTTP 服务用户。若今后 HTTP 服务器更换为 apache 或是 lighttpd 时,用户名和用户组可以不变。

安装 MySQL

在装 PHP 前必须先装 MySQL,因为 PHP 里的 MySQL 操作函数需要 MySQL 头文件和库的支持。

emerge dev-db/mysql

* 初始化数据库:

我不习惯把数据库安装在默认路径 /var/lib/mysql 中,我把它放在 /work/db/3306/data 中。

mkdir -p /work/db/3306/data

mysql_install_db –basedir=/usr –datadir=/work/db/3306/data –user=mysql

* 修改配置文件:

vim /etc/mysql/my.cnf

将 datadir 修改为:

datadir = /work/db/3306/data

* 启动 MySQL:

/etc/init.d/mysql start

* 修改 root 密码:

mysqladmin -uroot password hily

* 测试数据库:

mysql -uroot -p

显示:

gentoo setup # mysql -uroot -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.0.84-log Gentoo Linux mysql-5.0.84-r1

Type ‘help;’ or ‘\h’ for help. Type ‘\c’ to clear the current input statement.

mysql>

测试成功!

安装 PHP

以 fastcgi 方式来运行 PHP,需要安装 PHP-FPM

目前最后一个需要以 patch 形式安装 PHP-FPM 的 PHP 版本是 5.3.0,PHP 5.3.2 版本中将可能直接集成 PHP-FPM。

这里我就使用 PHP 5.3.0 来安装。

因为 Gentoo 中目录还没有集成 PHP-FPM 的 Portage,所以下面直接通过源码编译形式进行安装。

* 下载 PHP 5.3.0:

wget http://cn.php.net/distributions/php-5.3.0.tar.bz2

* 下载 PHP-FPM 补丁:

wget http://php-fpm.org/downloads/php-5.3.0-fpm-0.5.12.diff.gz

* 解压 PHP 并打 FPM 补丁:

tar jxf php-5.3.0.tar.bz2

gzip -cd php-5.3.0-fpm-0.5.12.diff.gz | patch -d php-5.3.0 -p1

* 安装 PHP 需要的库(根据自身需要):

emerge libpng

emerge jpeg

emerge freetype

USE=”png jpeg truetype” emerge gd

或者直接:

USE=”png jpeg truetype” emerge gd

* 配置并编译 PHP(根据自身需要):

cd php-5.3.0

./configure –prefix=/usr/local/php –with-config-file-path=/usr/local/php/etc –with-mysql=/usr –with-mysqli=/usr/bin/mysql_config –enable-fpm –enable-sockets –enable-pdo –with-pdo-mysql=/usr –with-gd –with-jpeg-dir –with-png-dir –with-freetype-dir –with-zlib

make && make install

* PHP 配置文件:

cp php.ini-production /usr/local/php/etc/php.ini

* PHP-FPM 配置文件:

vim /usr/local/php/etc/php-fpm.conf

修改 listen_address 为 socket 地址(socket 比 IP:Port 高效):

<value name=”listen_address”>/tmp/php-fpm.sock</value>

修改用户组和用户名:

Unix user of processes
<value name=”user”>www</value>

Unix group of processes
<value name=”group”>www</value>

修改 PHP-FPM 运行模式为 Apache-Like 模式:

<value name=”style”>apache-like</value>
<value name=”StartServers”>1</value>
<value name=”MinSpareServers”>1</value>
<value name=”MaxSpareServers”>5</value>

StartServers、MinSpareServers 和 MaxSpareServers 根据实际需要设置,我这里是虚拟机,没必要太大。

* PHP-FPM 启动脚本:

cp /usr/local/php/sbin/php-fpm /etc/init.d/php-fpm

* 启动 PHP-FPM

/etc/init.d/php-fpm start

添加启动服务

rc-update add nginx default

rc-update add mysql default

rc-update add php-fpm default

测试 Nginx+PHP

* 添加测试站点目录:

mkdir -p /work/www/test

echo “<?php phpinfo(); ?>” > /work/www/test/index.php

* 添加测试站点的 Nginx 配置:

vim /etc/nginx/nginx.conf

注释掉 server 段,在 http 段尾部加上:

include sites/*.enable;

之后每个站点的配置文件都以一个独立的文件保存在 /etc/nginx/sites 目录下,方便管理和维护。

mkdir /etc/nginx/sites

vim /etc/nginx/test.enable

test.enable 配置如下:

server {
listen       80;
server_name  test.local;

access_log  /work/www/logs/test.access.log  main;
error_log  /work/www/logs/test.error.log;

location / {
root   /work/www/test;
index  index.html index.htm index.php;
}

location ~ \.php$ {
root           /work/www/test;
fastcgi_index  index.php;
fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
include        fastcgi_params;
fastcgi_pass   unix:/tmp/php-fpm.sock;
}
}

* 新建存储日志目录:

mkdir /work/www/logs

* 本地 hosts 中添加记录:

192.168.1.10    test.local

192.168.1.10 是我这台 Gentoo 机器的 IP。

* 重新加载 Nginx 配置

/etc/init.d/nginx reload

* 访问:

http://test.local/

显示正常的 phpinfo 信息,则安装完成!

— EOF —