PHP 模拟并发请求

1,086 阅读1分钟

网站出现恶意并发攻击,解决问题第一步,自己先模拟并发,进行测试

注意:
	session 登录状态,我们需要设置 cookie

	api 登录状态,我们一般需要设置 Bearer token


1.并发工具:
	postman - 网上各种说 postman 并发测试,那是迭代请求,并非并发,错误

	AB(Apache Bench) - 这个是经常听说的一款,很简单
		参考:
			ab(Apache Bench)命令详解以及压力测试模拟
				https://blog.csdn.net/jiajiren11/article/details/79486967

	JMeter - Apache JMeter是Apache组织开发的基于Java的压力测试工具
		参考:
			使用 JMeter 进行压力测试
				https://www.cnblogs.com/stulzq/p/8971531.html

	其他工具,没看,参考:
		10大主流压力测试工具
			https://blog.csdn.net/langzitianya/article/details/81479422

		九款Web服务器性能压力测试工具
			https://blog.csdn.net/qq_33440246/article/details/80591070

2.自己使用 PHP 来模拟并发
	1>使用 guzzlehttp 的并发请求

		use GuzzleHttp\Client;
		use GuzzleHttp\Promise;
		use Psr\Http\Message\ResponseInterface;
		use GuzzleHttp\Exception\RequestException;
		use GuzzleHttp\Exception\ServerException;

	    /**
	     * 模拟发红包 - 异步并发
	     */
	    public function mockSendRedPacketConcurrent($token)
	    {
	        $client = new Client([
	            'base_uri' => 'http://xxx/api/',
	        ]);

	        $promises = [];
	        for($i = 0; $i < 20; $i++){
	            $promise = $client->postAsync('red/sent', [
	                'headers' => [
	                    'Accept' => 'application/json',

	                    // 登录 token
	                    'Authorization' => 'Bearer ' . $token,

	                    // 模拟不同的虚拟IP
	                    'X-Forwarded-For' => '168.1.1.' . $i,
	                ],
	                'query' => [
	                    'red_money' => 100,
	                    'red_type' => 1,
	                    'red_group' => 184,
	                    'red_num' => 1,
	                    'red_title' => '恭喜发财',
	                ],
	            ]);
	            $promises[] = $promise;
	        }

	        $results = Promise\unwrap($promises);

	        foreach($results as $result){
	            $body = $result->getBody();
	            $content = $body->getContents();
	            dump($content);
	        }
	    }

	2>使用 curl 并发请求
		https://www.cnblogs.com/52fhy/p/8908315.html
		https://www.oschina.net/question/54100_58279

	    /**
	     * 模拟发红包 - Rolling cURL并发
	     */
	    public function mockSendRedPacketCurl()
	    {
	    	$token = '';

	        $urls = [];
	        for($i = 0; $i < 600; $i++){
	            $urls[] = 'http://xxx/api/red/sent';
	        }

	        $responses = $this->rolling_curl($urls, $token, 30);
	        dd($responses);
	    }

	    /**
	     * Rolling cURL并发
	     */
	    private function rolling_curl($urls, $token, $delay) {
	        $queue = curl_multi_init();
	        $map = array();

	        foreach ($urls as $i => $url) {
	            $ch = curl_init();

	            $headers = [
                    'Accept' => 'application/json',

                    // 登录 token
                    'Authorization' => 'Bearer ' . $token,

                    // 模拟不同的虚拟IP
                    'X-Forwarded-For' => '168.1.1.' . $i,
	            ];
	            $params = [
	                'red_money' => 1,
	                'red_type' => 2,
	                'red_group' => '201910270025455db473899d72f',
	                'red_num' => 1,
	                'red_title' => '并发测试',
	            ];
	            // $params = [
	            //     'red_money' => 100,
	            //     'red_type' => 1,
	            //     'red_group' => 184,
	            //     'red_num' => 1,
	            //     'red_title' => '恭喜发财',
	            // ];
	            curl_setopt($ch, CURLOPT_URL, $url);
	            curl_setopt($ch, CURLOPT_POST, 1);
	            curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
	            curl_setopt($ch, CURLOPT_TIMEOUT, 100);
	            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	            curl_setopt($ch, CURLOPT_HEADER, 0);
	            curl_setopt($ch, CURLOPT_NOSIGNAL, true);
	            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

	            curl_multi_add_handle($queue, $ch);
	            $map[(string) $ch] = $url;
	        }

	        $responses = array();
	        do {
	            while (($code = curl_multi_exec($queue, $active)) == CURLM_CALL_MULTI_PERFORM) ;

	            if ($code != CURLM_OK) { break; }

	            // a request was just completed -- find out which one
	            while ($done = curl_multi_info_read($queue)) {

	                // get the info and content returned on the request
	                $info = curl_getinfo($done['handle']);
	                $error = curl_error($done['handle']);
	                $content = curl_multi_getcontent($done['handle']);
	                // $results = callback(curl_multi_getcontent($done['handle']), $delay);
	                // $responses[$map[(string) $done['handle']]] = compact('info', 'error', 'results');
	                $responses[] = [
	                    'info' => $info,
	                    'error' => $error,
	                    'content' => $content,
	                ];

	                // remove the curl handle that just completed
	                curl_multi_remove_handle($queue, $done['handle']);
	                curl_close($done['handle']);
	            }

	            // Block for data in / output; error handling is done by curl_multi_exec
	            if ($active > 0) {
	                curl_multi_select($queue, 0.5);
	            }

	        } while ($active);

	        curl_multi_close($queue);
	        return $responses;
	    }

	参考文章:
		模拟 ip 是可以的
			https://blog.csdn.net/intel80586/article/details/8906779
			不过 laravel 的 ip() 好像检测比较准确 

		使用 guzzlehttp 来实现并发请求:
			https://segmentfault.com/q/1010000007683839
				使用guzzle发送异步请求返回的promise 调用 then 方法不会立即执行, 还必须要调用wait去等待所有的请求结束,才会执行

		curl 并发:
			https://www.cnblogs.com/52fhy/p/8908315.html
			https://www.oschina.net/question/54100_58279