KimmyKuang +

PHP多进程模拟多客户端并发

写于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”表示不显示标准输出和错误输出,最后的&表示推到后台执行。)

Blog

Articles