前两天碰到一个挺典型的故障,一台四核八G的机器上放了七个站,其中一个是刚上线的商城。上午十点左右,商城被爬虫从头到尾翻了一遍,紧接着另外六个站同时开始报502。运维第一反应是内存不够,结果free一看还剩四G,负载只有三,CPU才百分之二十。机器看着一点都不忙,但网站就是打不开。
问题其实在PHP-FPM进程池。php-fpm启动时会按配置文件里的pool划分工作组,每个pool有一组worker进程,pm.max_children就是这个组的上限。多个站如果没有各自独立的pool配置,就全挤在默认的www池里,共用同一批worker。商城那波请求把worker占满之后,另外六个站的请求再进来,已经没有空闲进程可用了,只能排在监听队列里等。nginx的fastcgi_read_timeout一到点就断开,前端返回502。CPU不高、内存不紧,因为机器根本没在干活,是在等人腾出进程。
想确认也简单,先看一条命令:ps aux | grep 'php-fpm: pool www' | grep -v grep | wc -l。这个数如果贴着pm.max_children,基本就说明池已经满了。想看得更细,可以把status打开,在pool配置里加pm.status_path = /status,nginx里配一个location指向它,访问后能看到active、idle、listen queue三个数。listen queue不为零,就说明请求已经在门口排队了。
改法就是拆池,给每个站点建一份独立的pool配置,放在php-fpm.d目录下,文件名能认出站点就行,比如shop.conf、blog.conf。每份里写清listen、user、group、pm、pm.max_children、pm.start_servers、pm.min_spare_servers、pm.max_spare_servers这几个参数,listen要用独立的socket路径,别和默认的撞上。
socket文件要放在有权限的目录。/dev/shm是内存盘,读写快,重启后自动清空,适合放socket;但要注意这个目录权限是1777,别的用户也能建同名文件,所以配置里把listen.owner和listen.group设成nginx用户,listen.mode给0660,能挡住大部分误用。写完后nginx那边把fastcgi_pass从原来的127.0.0.1:9000或者统一socket,改成指向各站自己的socket路径。
max_children怎么分,按内存算比拍脑袋靠谱。单个PHP进程的实际占用可以用ps -o rss看,通常三十到六十兆,取一个站压力测试峰值再乘一点五。四核八G的机器,给商城留二十个、其余六个站各五个,加起来五十个,按每进程四十兆算正好两G,剩下的留给系统和数据库。不要把所有池的上限加起来超过物理内存,否则一个站被刷的时候,所有池一起涨,机器直接进swap,那比502更糟。
拆完还有一处容易漏:原来所有站共用一个池的时候,opcache也是共用的,一份缓存装所有站的脚本。拆池之后每个池有自己的opcache,内存占用会翻几倍,opcache.memory_consumption要按站点数量重新估。另外各池的slowlog路径也要分开改,不然日志混在一起,出问题还是分不清是哪个站拖的。改完重启php-fpm,再跑一次压力测试,用一个站打满,观察其他站的响应时间有没有变化,这一步才算验完。
如果用的是宝塔面板,站点设置里有个PHP-FPM独立进程池的开关,打开就是给这个站建独立池,参数可以自己填。1Panel的做法是在运行环境里给每个站点单独建PHP容器,思路一样,隔离粒度更细。
A5创业网 版权所有