基于PHP-FPM进程池探秘
PHP支持多进程而不支持多线程;PHP-FPM在进程池中运行多个子进程并发处理所有连接请求。通过ps查看PHP-FPM进程池(pm.start_servers=2)状态如下:
root@d856fd02d2fe:~#psaux-L USERPIDLWP%CPUNLWP%MEMVSZRSSTTYSTATSTARTTIMECOMMAND root110.010.04504692?Ss13:100:00/bin/sh/usr/local/php/bin/php-fpmstart root770.010.417607619304?Ss13:100:00php-fpm:masterprocess(/usr/local/php/etc/php-fpm.conf) www-data880.010.21760768132?S13:100:00php-fpm:poolwww www-data990.010.21760768132?S13:100:00php-fpm:poolwww root10100.010.0183763476?Ss14:110:00bash root66660.010.0344202920?R+15:130:00psaux-L
从列表中可以看出,进程池www中有两个尚处于空闲状态的子进程PID8和PID9。注:NLWP指轻量级进程数量,即线程数量。
PHP-FPM(FastCGIProcessManager)是什么?PHP-FPM为PHP-CGI提供进程管理方式,可以有效控制内存和进程,可以平滑重载PHP配置,其masterprocess是常驻内存的。FastCGI是语言无关的、可伸缩架构的CGI开放扩展,其主要行为是将CGI解释器进程保持在内存中更长时间,不是fork-and-execute,并因此获得较高的性能。FastCGI支持分布式部署,可以部署在WEB服务器以外的多个主机上。
探秘手段:模拟多线程并发执行
1.什么是线程:线程有时又称轻量级进程(LightweightProcess,LWP),通常由线程ID、当前指令指针(PC)、寄存器集合和堆栈组成,是进程中的一个实体,是被系统独立调度的基本单位;线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,与同属一个进程的其它线程共享进程所拥有的全部资源。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。由于进程是资源拥有者,创建、撤消与切换开销过大,在对称多处理机(SMP)上同时运行多个线程(Threads)才是更合适的选择。线程的实体包括程序、数据和线程控制块(ThreadControlBlock,TCB),TCB包括以下信息:
(1)线程状态;
(2)当线程不运行时,被保存的现场资源;
(3)一组执行堆栈;
(4)存放每个线程的局部变量主存;
(5)访问同一个进程中的主存和其它资源。
但使用多个进程会使得应用程序在出现进程池内的进程崩溃或被攻击的情况下变得更加健壮。
2.模拟多线程:
thread=microtime(true); } /** *通过socket发送一个新的HTTP连接请求到本机, *此时当前模拟线程既是服务端又是模拟客户端 * *当前(程序)子进程sleep(1)后会延迟1s才继续执行,但其持有的连接是继续有效的, *不能处理新的连接请求,故这种做法会降低进程池处理并发连接请求的能力, *类似延迟处理还有time_nanosleep()、time_sleep_until()、usleep()。 *而且sleep(1)这种做法并不安全,nginx依然可能出现如下错误: *“epoll_wait()reportedthatclientprematurelyclosedconnection, *soupstreamconnectionisclosedtoowhileconnectingtoupstream” * *@returnvoid */ publicfunctionsimulate() { $run=$_GET['run']??0; if($run++<9){//最多模拟10个线程 $fp=fsockopen($this->host,$this->port); fputs($fp,"GET{$_SERVER['PHP_SELF']}?run={$run}\r\n\r\n"); sleep(1);//usleep(500) fclose($fp); } $this->log(); } /** *日志记录当前模拟线程运行时间 * *@returnvoid */ privatefunctionlog() { $fp=fopen('simulated.thread','a'); fputs($fp,"Logthread{$this->thread}at".microtime(true)."(s)\r\n"); fclose($fp); } } $thread=newSimulatedThread(); $thread->simulate(); echo"Startedtosimulatethreads...";
探秘汇总:本人通过运行上述脚本后,发现一些可预料但却不是我曾想到的结果
1.PHP-FPM配置项pm.max_children=5,simulated.thread记录如下:
Logthread1508054181.4236at1508054182.4244(s) Logthread1508054181.4248at1508054182.4254(s) Logthread1508054181.426at1508054182.428(s) Logthread1508054181.6095at1508054182.6104(s) Logthread1508054182.4254at1508054183.4262(s) Logthread1508054183.4272at1508054183.4272(s) Logthread1508054182.4269at1508054183.4275(s) Logthread1508054182.4289at1508054183.43(s) Logthread1508054182.6085at1508054183.6091(s) Logthread1508054182.611at1508054183.6118(s)
最新生成的(模拟)线程登记出现在红色标示条目位置是因为进程池的并发连接处理能力上限为5,因此它只可能出现在第六条以后的位置。
Logthread1508058075.042at1508058076.0428(s) Logthread1508058075.0432at1508058076.0439(s) Logthread1508058075.0443at1508058076.045(s) Logthread1508058075.6623at1508058076.6634(s) Logthread1508058076.0447at1508058077.0455(s) Logthread1508058076.046at1508058077.0466(s) Logthread1508058077.0465at1508058077.0466(s) Logthread1508058076.0469at1508058077.0474(s) Logthread1508058076.6647at1508058077.6659(s) Logthread1508058076.6664at1508058077.6671(s)
有意思的是绿色条目代表的(模拟)线程和红色条目代表的(模拟)线程的登记时间是一样的,说明两个(模拟)线程是并发执行的。
2.PHP-FPM配置项pm.max_children=10,simulated.thread记录如下:
Logthread1508061169.7956at1508061170.7963(s) Logthread1508061169.7966at1508061170.7976(s) Logthread1508061169.7978at1508061170.7988(s) Logthread1508061170.2896at1508061171.2901(s) Logthread1508061170.7972at1508061171.7978(s) Logthread1508061171.7984at1508061171.7985(s) Logthread1508061170.7982at1508061171.7986(s) Logthread1508061170.7994at1508061171.8(s) Logthread1508061171.2907at1508061172.2912(s) Logthread1508061171.2912at1508061172.2915(s)
由于服务端并发连接处理能力上限达到10,因此最新生成的(模拟)线程登记可出现在任何位置。
3.执行usleep(500)延迟,simulated.thread记录如下:
Logthread1508059270.3195at1508059270.3206(s) Logthread1508059270.3208at1508059270.3219(s) Logthread1508059270.322at1508059270.323(s) Logthread1508059270.323at1508059270.324(s) Logthread1508059270.3244at1508059270.3261(s) Logthread1508059270.3256at1508059270.3271(s) Logthread1508059270.3275at1508059270.3286(s) Logthread1508059270.3288at1508059270.3299(s) Logthread1508059270.3299at1508059270.331(s) Logthread1508059270.3313at1508059270.3314(s)
可见日志记录顺序与(模拟)线程生成的顺序一致。usleep延迟的基本单位是微妙(us,1s=1000000us)。
从以上的记录可以看出:
1)这些(模拟)线程是第一次请求执行脚本后就自动生成的,一个(模拟)线程紧接着创建了另一个(模拟)线程;
2)这些(模拟)线程中有的是在同一个子进程空间中产生并运行的;
3)前后相邻(模拟)线程生成时间间隔很小,几乎是同时产生,或后一个(模拟)线程在前一个(模拟)线程尚未执行结束并退出之前产生;
4)多个(模拟)线程之间可以并发执行。
所以,上述模拟多线程并发的实现是成功的。PHP-FPM进程池中同一个子进程可先后处理多个连接请求,但同一时间只能处理一个连接请求,未处理连接请求将进入队列等待处理。换句话,同一个子进程不具有并发处理连接请求的能力。
PHP-FPMPool配置:它允许定义多个池,每个池可定义不同的配置项。以下只是列举了我在探秘过程中还关注过的其他部分配置项
1、listen:TheaddressonwhichtoacceptFastCGIrequests.它支持TCPSocket和unixsocket两种通讯协议。可设置listen=[::]:9000。
2、listen.allowed_clients:Listofaddresses(IPv4/IPv6)ofFastCGIclientswhichareallowedtoconnect.该配置项为逗号分隔的列表,如listen.allowed_clients=127.0.0.1,172.17.0.5。
3、pm:Choosehowtheprocessmanagerwillcontrolthenumberofchildprocesses.该配置项设置FPM管理进程池的方式,包括static、dynamic、ondemand三种。
4、pm.max_requests:Thenumberofrequestseachchildprocessshouldexecutebeforerespawning.Thiscanbeusefultoworkaroundmemoryleaksin3rdpartylibraries.设置每个子进程处理请求数的上限,对于处理第三方库中的内存泄漏很有用。
5、pm.status_path:TheURItoviewtheFPMstatuspage.
以上这篇基于PHP-FPM进程池探秘就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。