使用php实现压力测试工具的核心是利用curl_multi_exec实现并发请求,通过非阻塞方式发送大量http请求并收集响应数据;2. 关键步骤包括配置请求参数、初始化curl_multi句柄、添加多个curl请求句柄、调用curl_multi_exec执行并发请求、使用curl_multi_select轮询状态、处理完成的请求并统计响应时间、状态码等信息;3. 数据收集需记录每个请求的总耗时、http状态码、错误信息,并计算总请求数、成功与失败请求数、平均/最高/最低响应时间、吞吐量(qps)等指标;4. 分析结果时可通过统计错误率、响应时间分布及百分位(如p90、p95)来评估服务性能;5. 最终报告可通过命令行或html形式输出,便于快速验证接口在高并发下的表现。该工具虽无法替代专业软件,但具有学习价值、高定制性、易集成和快速调试的优势。

用PHP实现一个简单的压力测试工具,核心在于模拟大量并发请求,然后收集并分析服务器的响应数据。这通常涉及HTTP请求的发送、并发控制、以及对响应时间、错误率等指标的统计。

用PHP构建一个简单的压力测试系统,说实话,这事儿挺有意思的,也挺能锻炼人对HTTP协议和并发编程的理解。对我来说,它不是为了替代JMeter或Locust这种专业工具,更多的是一种深入理解和快速验证的手段。
最基本的实现思路,就是在一个循环里不断地发送HTTP请求。但这样效率太低,因为PHP默认的
file_get_contents
curl_exec
curl_multi_exec
立即学习“PHP免费学习笔记(深入)”;

大致的流程会是这样:
curl_multi
curl
multi
curl
curl_multi
curl_multi_exec
curl_multi_select
curl_multi_exec
<?php
// 简单配置
$targetUrl = 'http://localhost/test_api.php'; // 你的目标URL
$concurrency = 50; // 并发数
$totalRequests = 500; // 总请求数
$requestCount = 0;
$activeHandles = 0;
$mh = curl_multi_init();
$handles = [];
$results = [
'total_requests' => 0,
'successful_requests' => 0,
'failed_requests' => 0,
'total_time' => 0,
'max_time' => 0,
'min_time' => PHP_INT_MAX,
'errors' => [],
];
echo "开始压力测试...\n";
// 填充初始并发请求
for ($i = 0; $i < min($concurrency, $totalRequests); $i++) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $targetUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_NOBODY, false); // 获取响应体
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 5000); // 5秒超时
// curl_setopt($ch, CURLOPT_POST, true); // 如果是POST请求
// curl_setopt($ch, CURLOPT_POSTFIELDS, ['key' => 'value']); // POST数据
curl_multi_add_handle($mh, $ch);
$handles[(string)$ch] = $ch; // 用句柄的字符串表示作为键
$requestCount++;
}
$startTime = microtime(true);
do {
// 执行批处理cURL请求
curl_multi_exec($mh, $activeHandles);
// 等待活动句柄
// 如果没有活动句柄,或者没有新的请求可以添加,则不需要select
if ($activeHandles > 0 || $requestCount < $totalRequests) {
curl_multi_select($mh, 0.1); // 等待最多0.1秒
}
// 检查已完成的请求
while (($info = curl_multi_info_read($mh)) !== false) {
$ch = $info['handle'];
$requestInfo = curl_getinfo($ch);
$response = curl_multi_getcontent($ch);
$results['total_requests']++;
$requestTime = $requestInfo['total_time'];
$results['total_time'] += $requestTime;
$results['max_time'] = max($results['max_time'], $requestTime);
$results['min_time'] = min($results['min_time'], $requestTime);
if ($info['result'] === CURLE_OK && $requestInfo['http_code'] >= 200 && $requestInfo['http_code'] < 300) {
$results['successful_requests']++;
} else {
$results['failed_requests']++;
$error = curl_error($ch) ?: "HTTP Code: " . $requestInfo['http_code'];
$results['errors'][] = [
'url' => $requestInfo['url'],
'error' => $error,
'time' => $requestTime,
'http_code' => $requestInfo['http_code']
];
}
// 移除已完成的句柄
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
unset($handles[(string)$ch]);
// 如果还有请求需要发送,就添加新的句柄
if ($requestCount < $totalRequests) {
$newCh = curl_init();
curl_setopt($newCh, CURLOPT_URL, $targetUrl);
curl_setopt($newCh, CURLOPT_RETURNTRANSFER, true);
curl_setopt($newCh, CURLOPT_HEADER, false);
curl_setopt($newCh, CURLOPT_NOBODY, false);
curl_setopt($newCh, CURLOPT_TIMEOUT_MS, 5000);
curl_multi_add_handle($mh, $newCh);
$handles[(string)$newCh] = $newCh;
$requestCount++;
}
}
} while ($activeHandles > 0 || $requestCount < $totalRequests); // 循环直到所有请求完成
curl_multi_close($mh);
$endTime = microtime(true);
$totalExecutionTime = $endTime - $startTime;
// 输出报告
echo "\n--- 压力测试报告 ---\n";
echo "总请求数: " . $results['total_requests'] . "\n";
echo "成功请求数: " . $results['successful_requests'] . "\n";
echo "失败请求数: " . $results['failed_requests'] . "\n";
echo "总耗时: " . sprintf("%.2f", $totalExecutionTime) . " 秒\n";
echo "平均响应时间: " . sprintf("%.4f", $results['total_time'] / $results['successful_requests']) . " 秒\n";
echo "最高响应时间: " . sprintf("%.4f", $results['max_time']) . " 秒\n";
echo "最低响应时间: " . sprintf("%.4f", $results['min_time']) . " 秒\n";
echo "吞吐量 (QPS): " . sprintf("%.2f", $results['total_requests'] / $totalExecutionTime) . " 请求/秒\n";
if (!empty($results['errors'])) {
echo "\n--- 错误详情 ---\n";
foreach ($results['errors'] as $error) {
echo "URL: " . $error['url'] . ", 错误: " . $error['error'] . ", HTTP状态码: " . $error['http_code'] . ", 耗时: " . sprintf("%.4f", $error['time']) . "秒\n";
}
}
?>说实话,市面上那么多专业的负载测试工具,像JMeter、Locust、ApacheBench(ab),功能都比我们用PHP手搓的强大得多。那为什么还要自己动手搞一个呢?我觉得吧,主要有几个原因。

首先,学习和理解。对我来说,亲手写一个压力测试工具,能让我更深入地理解HTTP协议、并发模型以及PHP的
curl_multi
其次,定制化和集成。有时候,我们的测试场景非常特殊,可能需要模拟特定的用户行为流,或者需要与现有的PHP代码库、框架紧密集成。用PHP自己写,灵活性就非常高。我可以轻松地在请求前或请求后加入自定义的逻辑,比如动态生成请求参数、处理session、或者根据响应内容进行后续操作。这在通用工具里实现起来可能会比较麻烦,甚至需要编写复杂的脚本插件。
再者,快速验证和调试。对于一些简单的API接口测试,或者在开发阶段需要快速验证某个功能在高并发下的表现,启动一个JMeter可能有点“杀鸡用牛刀”的感觉。一个轻量级的PHP脚本,几行代码就能跑起来,非常方便快捷。而且,如果被测系统本身就是PHP写的,那么用PHP来测试,在某些方面(比如字符编码、数据序列化)可能会更“亲和”,减少一些不必要的兼容性问题。
最后,可能还有一点点“控制欲”吧。当我能完全掌控测试工具的每一个细节时,那种感觉是不一样的。我可以精确地知道每个请求是如何发出的,数据是如何收集的,以及结果是如何计算的。这种透明度,对于诊断一些复杂的性能问题,其实是很有帮助的。当然,这并不是说要取代专业工具,而是作为它们的一个有益补充。
在PHP里要实现并发请求,最核心、最关键的技术点,毫无疑问就是curl_multi_exec
curl_multi
传统的
curl_exec
curl_multi_exec
它的基本逻辑是这样的:
curl_multi_init()
curl_init()
curl_multi_add_handle()
curl_multi_exec()
curl_multi_exec
curl_multi_exec
curl_multi_select()
curl_multi_select()
curl_multi_info_read()
curl_getinfo()
curl_multi_getcontent()
curl_multi_remove_handle()
curl_close()
curl_multi_exec
这个过程,其实就是模拟了一个简单的事件驱动模型。你把任务提交给cURL,然后PHP线程就可以去做别的事情,直到cURL通知你“嘿,有个请求完成了!”。这种模式对于I/O密集型的任务(比如网络请求)非常高效,因为PHP不需要傻傻地等待网络响应,可以同时管理和调度大量的请求。当然,这里面也有一些坑,比如句柄泄露、错误处理不当等等,都需要在使用时仔细考虑。
收集和分析负载测试结果,这部分其实是整个压力测试的“灵魂”所在。光是能发请求没用,得知道这些请求到底表现怎么样,服务器有没有扛住,哪里是瓶颈。
我通常会关注以下几个核心指标:
在数据收集层面,我会在每个请求完成时,立即获取
curl_getinfo()
total_time
http_code
url
curl_error()
// 假设 $results 数组结构如下
$results = [
'total_requests' => 0,
'successful_requests' => 0,
'failed_requests' => 0,
'total_time' => 0, // 累加所有请求的total_time
'max_time' => 0,
'min_time' => PHP_INT_MAX,
'errors' => [], // 记录详细的错误信息
// 还可以考虑增加:
// 'http_codes' => [], // 统计各个HTTP状态码出现的次数
// 'latency_distribution' => [], // 响应时间分布,比如落在0-100ms, 100-200ms的请求数
];
// 在处理每个完成的cURL句柄时
// ...
$requestInfo = curl_getinfo($ch);
$requestTime = $requestInfo['total_time'];
$results['total_requests']++;
$results['total_time'] += $requestTime;
$results['max_time'] = max($results['max_time'], $requestTime);
$results['min_time'] = min($results['min_time'], $requestTime);
if ($info['result'] === CURLE_OK && $requestInfo['http_code'] >= 200 && $requestInfo['http_code'] < 300) {
$results['successful_requests']++;
} else {
$results['failed_requests']++;
$error = curl_error($ch) ?: "HTTP Code: " . $requestInfo['http_code'];
$results['errors'][] = [
'url' => $requestInfo['url'],
'error' => $error,
'time' => $requestTime,
'http_code' => $requestInfo['http_code']
];
}
// ...分析阶段,就是对这些原始数据进行计算和汇总。我会用PHP的数学函数(
sum
max
min
avg
如果想要更高级的分析,比如响应时间的百分位(P90、P95、P99),这能更好地反映用户体验,因为平均值可能会被极端值拉偏。实现这个就需要把所有成功请求的响应时间都存起来,然后进行排序,再计算对应的百分位。
// 假设 $successfulTimes 数组存储了所有成功请求的响应时间 // array_push($successfulTimes, $requestTime); // 计算P90, P95, P99 // sort($successfulTimes); // $count = count($successfulTimes); // $p90Index = (int)($count * 0.90) - 1; // $p95Index = (int)($count * 0.95) - 1; // $p99Index = (int)($count * 0.99) - 1; // $p90 = $successfulTimes[$p90Index] ?? 0; // $p95 = $successfulTimes[$p95Index] ?? 0; // $p99 = $successfulTimes[$p99Index] ?? 0;
最后,报告的输出也很重要。我倾向于直接在命令行打印出这些关键指标,清晰、简洁。如果需要更友好的界面,可以考虑生成一个简单的HTML页面,用表格展示数据,或者结合一些JavaScript库(比如Chart.js)绘制图表,那就更直观了。但对于快速验证,命令行输出已经足够。关键是,数据要真实、可信,能反映出系统在高负载下的真实表现。
以上就是PHP负载测试工具开发 用PHP实现简单压力测试系统的完整教程的详细内容,更多请关注php中文网其它相关文章!
PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号