讲师中心 微信公众号
AI工具推荐 视频效率加速

PHP负载测试工具开发 用PHP实现简单压力测试系统的完整教程

酷伟同学_9931

酷伟同学_9931

发布时间:2025-08-01 20:14:01

|

582人浏览过

|

来源于php中文网

原创

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

PHP负载测试工具开发 用PHP实现简单压力测试系统的完整教程

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

PHP负载测试工具开发 用PHP实现简单压力测试系统的完整教程

用PHP构建一个简单的压力测试系统,说实话,这事儿挺有意思的,也挺能锻炼人对HTTP协议和并发编程的理解。对我来说,它不是为了替代JMeter或Locust这种专业工具,更多的是一种深入理解和快速验证的手段。

最基本的实现思路,就是在一个循环里不断地发送HTTP请求。但这样效率太低,因为PHP默认的

file_get_contents
curl_exec
都是阻塞的。要实现并发,
curl_multi_exec
是绕不开的关键。

立即学习PHP免费学习笔记(深入)”;

PHP负载测试工具开发 用PHP实现简单压力测试系统的完整教程

大致的流程会是这样:

  1. 配置请求参数: 目标URL、请求方法(GET/POST)、请求头、POST数据等。
  2. 初始化
    curl_multi
    句柄:
    这是PHP实现并发请求的基石。
  3. 添加
    curl
    句柄到
    multi
    句柄:
    循环创建多个独立的
    curl
    句柄,每个代表一个待发送的请求,然后把它们都加到
    curl_multi
    中。
  4. 执行并发请求: 调用
    curl_multi_exec
    ,它会非阻塞地执行所有添加的请求。
  5. 轮询和处理结果: 使用
    curl_multi_select
    等待活动句柄,然后循环检查
    curl_multi_exec
    的状态,直到所有请求完成。每当一个请求完成,就获取其响应数据、状态码、耗时等信息。
  6. 统计和分析: 收集所有请求的响应时间、成功率、错误信息等,计算平均响应时间、吞吐量(QPS)等关键指标。
  7. 报告输出: 将统计结果以易读的方式展示出来,比如命令行输出或简单的HTML报告。
<?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";
    }
}

?>

为什么选择自建PHP负载测试工具?

说实话,市面上那么多专业的负载测试工具,像JMeter、Locust、ApacheBench(ab),功能都比我们用PHP手搓的强大得多。那为什么还要自己动手搞一个呢?我觉得吧,主要有几个原因。

PHP负载测试工具开发 用PHP实现简单压力测试系统的完整教程

首先,学习和理解。对我来说,亲手写一个压力测试工具,能让我更深入地理解HTTP协议、并发模型以及PHP的

curl_multi
扩展的工作原理。这可比看文档或者直接用别人的工具来得真切。你会遇到各种细节问题,比如句柄的管理、错误的处理、非阻塞I/O的逻辑,这些都是宝贵的经验。

其次,定制化和集成。有时候,我们的测试场景非常特殊,可能需要模拟特定的用户行为流,或者需要与现有的PHP代码库、框架紧密集成。用PHP自己写,灵活性就非常高。我可以轻松地在请求前或请求后加入自定义的逻辑,比如动态生成请求参数、处理session、或者根据响应内容进行后续操作。这在通用工具里实现起来可能会比较麻烦,甚至需要编写复杂的脚本插件。

再者,快速验证和调试。对于一些简单的API接口测试,或者在开发阶段需要快速验证某个功能在高并发下的表现,启动一个JMeter可能有点“杀鸡用牛刀”的感觉。一个轻量级的PHP脚本,几行代码就能跑起来,非常方便快捷。而且,如果被测系统本身就是PHP写的,那么用PHP来测试,在某些方面(比如字符编码、数据序列化)可能会更“亲和”,减少一些不必要的兼容性问题。

最后,可能还有一点点“控制欲”吧。当我能完全掌控测试工具的每一个细节时,那种感觉是不一样的。我可以精确地知道每个请求是如何发出的,数据是如何收集的,以及结果是如何计算的。这种透明度,对于诊断一些复杂的性能问题,其实是很有帮助的。当然,这并不是说要取代专业工具,而是作为它们的一个有益补充。

PHP
PHP

编写健壮的PHP代码,规避类型转换陷阱、数组怪癖及常见安全漏洞。

下载

PHP实现并发请求的关键技术点是什么?

在PHP里要实现并发请求,最核心、最关键的技术点,毫无疑问就是

curl_multi_exec
函数以及它背后的一整套
curl_multi
家族函数。这玩意儿,说白了,就是PHP对libcurl库中多句柄接口的封装。

传统的

curl_exec
是阻塞的,也就是说,你发一个请求,它就得等着服务器响应了,才能继续发下一个。这显然不符合压力测试对并发的需求。而
curl_multi_exec
的出现,就彻底改变了这种局面。

它的基本逻辑是这样的:

  1. 创建主句柄: 你得先用
    curl_multi_init()
    创建一个“多句柄”,你可以把它想象成一个任务管理器。
  2. 添加子句柄: 然后,你为每一个独立的HTTP请求,都创建一个普通的cURL句柄(
    curl_init()
    ),设置好它的URL、参数、超时等等。接着,用
    curl_multi_add_handle()
    把这些独立的请求句柄“挂”到那个任务管理器上。
  3. 非阻塞执行: 调用
    curl_multi_exec()
    。注意,这个函数是非阻塞的!它会尽可能快地发送所有已添加的请求,并不会等待任何一个请求完成。它会返回当前还有多少个请求正在进行中。
  4. 事件循环与轮询: 这是最精妙也最容易让人搞糊涂的地方。因为
    curl_multi_exec
    是非阻塞的,你不能指望它一次性把所有结果都给你。你需要一个循环,不断地调用
    curl_multi_exec
    ,并结合
    curl_multi_select()
    curl_multi_select()
    的作用是等待cURL句柄上的I/O活动(比如数据来了,或者连接断了)。它会阻塞,直到有活动发生,或者达到设定的超时时间。
  5. 处理完成请求: 在循环中,你需要用
    curl_multi_info_read()
    来检查哪些请求已经完成了。每当一个请求完成,你就可以通过
    curl_getinfo()
    获取它的详细信息(如响应时间、HTTP状态码),通过
    curl_multi_getcontent()
    获取响应体。处理完之后,记得用
    curl_multi_remove_handle()
    把这个句柄从多句柄中移除,并用
    curl_close()
    关闭它,释放资源。
  6. 循环结束: 整个循环会一直持续,直到
    curl_multi_exec
    返回的活动句柄数为0,这意味着所有请求都处理完了。

这个过程,其实就是模拟了一个简单的事件驱动模型。你把任务提交给cURL,然后PHP线程就可以去做别的事情,直到cURL通知你“嘿,有个请求完成了!”。这种模式对于I/O密集型的任务(比如网络请求)非常高效,因为PHP不需要傻傻地等待网络响应,可以同时管理和调度大量的请求。当然,这里面也有一些坑,比如句柄泄露、错误处理不当等等,都需要在使用时仔细考虑。

如何有效收集和分析负载测试结果?

收集和分析负载测试结果,这部分其实是整个压力测试的“灵魂”所在。光是能发请求没用,得知道这些请求到底表现怎么样,服务器有没有扛住,哪里是瓶颈。

我通常会关注以下几个核心指标:

  1. 总请求数 (Total Requests): 简单明了,总共发了多少次请求。
  2. 成功请求数 (Successful Requests) 与 失败请求数 (Failed Requests): 这直接反映了服务的可用性。失败的请求要详细记录原因,是HTTP状态码错误(比如5xx),还是网络错误(如超时、连接拒绝)。
  3. 总耗时 (Total Execution Time): 整个测试过程从开始到结束一共花了多长时间。
  4. 平均响应时间 (Average Response Time): 所有成功请求的响应时间总和除以成功请求数。这是最直观的性能指标之一。
  5. 最高响应时间 (Max Response Time) 与 最低响应时间 (Min Response Time): 这两个能帮助你了解响应时间的波动范围。如果最高响应时间远超平均值,说明在高并发下,偶尔会出现响应非常慢的情况,这可能是潜在的性能问题。
  6. 吞吐量 (Throughput / QPS - Queries Per Second): 总请求数除以总耗时。这个指标衡量了服务器在单位时间内能处理多少个请求,是衡量系统处理能力的关键指标。

在数据收集层面,我会在每个请求完成时,立即获取

curl_getinfo()
返回的详细信息,特别是
total_time
(总耗时)、
http_code
(HTTP状态码)、
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速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

Loomy
Loomy Hot

一款AI工具,主要用于科大讯飞发布的桌面级 AI 助理,比 OpenClaw 更易用、更安全!,适合需要提升相关任务效率的用户。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

相关专题

更多
session失效的原因
session失效的原因

session失效的原因有会话超时、会话数量限制、会话完整性检查、服务器重启、浏览器或设备问题等等。详细介绍:1、会话超时:服务器为Session设置了一个默认的超时时间,当用户在一段时间内没有与服务器交互时,Session将自动失效;2、会话数量限制:服务器为每个用户的Session数量设置了一个限制,当用户创建的Session数量超过这个限制时,最新的会覆盖最早的等等。

580

2023.10.17

session失效解决方法
session失效解决方法

session失效通常是由于 session 的生存时间过期或者服务器关闭导致的。其解决办法:1、延长session的生存时间;2、使用持久化存储;3、使用cookie;4、异步更新session;5、使用会话管理中间件。

856

2023.10.18

cookie与session的区别
cookie与session的区别

本专题整合了cookie与session的区别和使用方法等相关内容,阅读专题下面的文章了解更详细的内容。

1726

2025.08.19

curl_exec
curl_exec

curl_exec函数是PHP cURL函数列表中的一种,它的功能是执行一个cURL会话。给大家总结了一下php curl_exec函数的一些用法实例,这个函数应该在初始化一个cURL会话并且全部的选项都被设置后被调用。他的返回值成功时返回TRUE, 或者在失败时返回FALSE。

3323

2023.06.14

linux常见下载安装工具
linux常见下载安装工具

linux常见下载安装工具有APT、YUM、DNF、Snapcraft、Flatpak、AppImage、Wget、Curl等。想了解更多linux常见下载安装工具相关内容,可以阅读本专题下面的文章。

715

2023.10.30

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

3028

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

4229

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

3649

2025.12.29

Buffalo框架数据库开发全教程
Buffalo框架数据库开发全教程

本专题围绕Buffalo框架数据库开发,讲解database.yml多环境配置、soda与fizz迁移生成回滚、模型结构体标签、增删改查与条件查询、一对多与多对多关联、数据校验、回调钩子、事务处理及原生SQL执行能力。

0

2026.09.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
墨刀帮助中心
墨刀帮助中心

共0课时 | 0人学习

MyEclipse学习中心
MyEclipse学习中心

共0课时 | 0人学习

Apache Subversion 官方手册
Apache Subversion 官方手册

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn