站点在流量上来的时候开始成片返回502,PHP-FPM进程还在,数据库也不忙,这种情况多半不是后端程序崩了。502的本意是网关连不上后端,或者连上之后连接被提前断开,问题出在nginx和PHP-FPM之间那一段。
502和504的分界在谁先超时
nginx的错误日志里有两句话要分开看。upstream prematurely closed connection说明连接是后端主动关掉的,upstream timed out才是nginx自己等不下去。两句对应的处置方向相反,一句要往FPM那边调,一句要往nginx这头调,混在一起改只会越改越乱。
PHP-FPM有两个值决定单个请求能跑多久。request_terminate_timeout默认是0,含义是完全不限制;php.ini里的max_execution_time默认30秒,是PHP脚本自己的上限。max_execution_time比nginx那头短的时候,PHP会在第30秒自己中止脚本,连接随之关闭,nginx收到的就是一句prematurely closed。
nginx这头的对应值是fastcgi_read_timeout,默认60秒。这个值计的不是整条请求的总时长,而是两次读之间的最长间隔,所以一个持续往外吐字节的脚本可以在里面跑很久都不触发。把两边的数摆在一起看,谁小谁先放弃,结果也就确定了:FPM先放弃是502,nginx先放弃是504。
常见错法是反过来调,看到502就去把fastcgi_read_timeout从60秒加到300秒,FPM那边还是30秒就掐,加完照旧报502,只是每次故障都多等四分钟。正确的顺序是先把两边数值摆平,再决定让谁先放弃。通常让nginx稍早一点,用户拿到的是504,FPM在几秒后收尾,不会留下空跑的worker。
进程池打满时连接会先排进队列
另一路502来自进程池打满。pm.max_children决定同时能有多少个worker,并发超过这个数,新连接会排进listen queue,队列满了就在内核层被丢掉,nginx看到的同样是连接异常。FPM的状态页里listen queue和max children reached两个数能直接说明问题,前者持续大于0说明在排队,后者一直在涨说明池子不够。往上调之前先算内存,单个worker的实际占用在状态页的process部分能看到,用可用内存除以它,再留两三成余量。另一组参数管的是出故障之后留不留线索,request_slowlog_timeout设上之后,跑超时的请求会把当时的PHP调用栈打进slowlog,不用等故障复现就能知道卡在哪一行。被request_terminate_timeout掐掉的那些请求,在这里往往能看到同一个函数反复出现,改代码比调参数管用。
调完要拿真实流量验一遍,用ab或者wrk压一小段带慢接口的页面,同时在另一个终端tail -f盯着两个日志,看502出现时FPM有没有对应的worker退出记录,nginx那句prematurely closed的时间戳和FPM的terminate时间戳差多少。差值稳定在一个固定秒数上,说明还是两边超时没对齐;差值忽大忽小,问题在别处,优先看内存和swap有没有在换页。压完把压测参数撤掉,池子的max_children写在pool.d下的conf文件里,改完reload才生效,restart会把正在处理的请求一起丢掉。
申请创业报道,分享创业好点子。点击此处,共同探讨创业新机遇!
