PHP多进程模拟多客户端并发
2015-04-19
写于2013-05-14,现迁移到Github blog上
参考了张宴的《PHP多进程并发控制的测试用例》一文,帮助良多,这里记录一下。
因为一台服务器(Server A)被IDC无故断电产生了文件根目录不能写的情况,所以需要切换在这台服务器上跑着的数据和业务到另一台(Server B)上,才能停掉在A上的业务进行维修,在数据迁移之前,需要对B上的数据库做一个压力测试,看看能否同时兼顾现有和附加的数据库的吞吐。
大致思路的伪代码:
Init:
$processLimit = 500 //最大进程数
$executeNumber = 5000 //模拟请求执行次数
$allowedProcessNumber = 500 //当前空闲进程数
process_test.php:
if (当前空闲子进程数够用)
#空闲子进程数减一
#新开一个子进程执行数据库连接&查询脚本,不必等待上一次查询/进程结束
#在子进程中记录查询log
else
#始终询问是否有空闲子进程数可用,如果有则执行if中的过程,否则等待一秒钟后再次询问
mysql_exec.php:
#子进程执行脚本,连接数据库并可以进行一系列增删改查操作
#最后记录下所需时间到log文件中,如果是查询语句记得加上sql_no_cache,不是用mysql语句结果缓存
实际代码demo:
process_test.php:
<?php
/**
* @file /opt/process_test.php
* 目的:利用popen()在后台打开多个子进程连接远程mysql服务器模拟多客户端并发访问操作,进行粗略的压力吞吐测试
* @param inatger $processLimit
* @param intager $executeNumber
* @param intager $allowedProcessNumber
*/
/**
* 启动函数,子进程数够用则打开子进程执行脚本,否则调用work_process方法等待直到有空闲数
*/
function run()
{
global $allowedProcessNumber;
if($allowedProcessNumber <= 0) {
$allowedProcessNumber = work_process($allowedProcessNumber);
}
$allowedProcessNumber--;
$out = popen("/usr/bin/php /opt/mysql_exec.php &", "r");
pclose($out);
}
/**
* 空闲子进程数不够,一直等待直到有其余进程结束
* @param intager $process 当前空闲子进程数
*/
function work_process($process)
{
global $processLimit;
while($process <= 0) {
$cmd = popen("ps -ef | grep \"/opt/mysql_exec.php\" | grep -v grep | wc -l", "r");
$line = fread($cmd, 512);
pclose($cmd);
$process = $processLimit - $line;
if($process <= 0) {
sleep(1);
}
}
return $process;
}
/**
* init,模拟短时间内有大量的请求到达
*/
$processLimit = 500;
$executeNumber = 5000;
$allowedProcessNumber = 500;
for($i = 0;$i < $executeNumber;$i++) {
run();
echo "Idle process number :" . $allowedProcessNumber . "\n";
}
?>
mysql_exec.php:
<?php
/**
* @file /opt/mysql_exec.php
* 目的:子进程执行脚本,进行一些mysql操作记录下log
*/
//模拟访问延时可以增加一个随机秒数的等待
$sleepTime = rand(1,3);
sleep($sleepTime);
$stime = time();
$dbhost = "xx.xx.xx.xx";
$dbuser = "root";
$dbpwd = "xxxx";
$dbname = "test";
$dbcharset = "gbk";
$table = "ucenter_members";
$conn = mysql_connect($dbhost,$dbuser,$dbpwd) or die("can not connect mysql host ".$dbhost);
if($dbcharset){
mysql_query("set names ".$dbcharset);
}
if($dbname){
mysql_select_db($dbname,$conn) or die("can not select db ".$dbname);
}
/**
* 这里可以做一些CRUD的复杂操作,例子很简单。
*/
$table1 = "pre_common_member";
$sql = "select sql_no_cache * from `$table` where uid = 78";
$result = mysql_query($sql,$conn);
$data = @mysql_fetch_array($result);
if($data)
dolog("success",$stime);
else
dolog("fail",$stime);
mysql_close($conn);
/*
*功能:记录log,包括每个sql语句的开始/结束时间/耗费时间(sec),以及是否查询成功的标志
*/
function dolog($input,$stime){
$etime = time();
$duration = $etime - $stime;
file_put_contents("/opt/ptest.log","start time :".date("Y-m-d H:i:s",$stime)." end time :".date("Y-m-d H:i:s",$etime)." ".$input." duration :".$duration.PHP_EOL,FILE_APPEND | LOCK_EX);
}
?>
执行脚本:/usr/bin/php /opt/prcess_test.php
将php脚本作为守护进程的方法:
nohup /usr/bin/php /opt/process_test.php 2>&1 > /dev/null &
(nohup命令可以在用户退出终端后仍然执行程序,“2>&1 > /dev/null”表示不显示标准输出和错误输出,最后的&表示推到后台执行。)