并发测试实战指南:从ab、TestNG到JMeter的四种方法
2026/8/26 23:08:16 网站建设 项目流程

1. 从“单打独斗”到“千军万马”:为什么我们需要并发测试?

在软件开发和系统运维的日常里,我们常常会听到这样的对话:“这个接口我本地调通了,没问题!”或者“功能测试都过了,可以上线了。”然而,当这个看似完美的系统,在某个促销活动零点,或者某个业务高峰时段,突然面对成百上千甚至上万的用户同时点击、同时下单、同时查询时,会发生什么?是依然坚如磐石,还是瞬间“雪崩”,出现响应超时、服务宕机、数据错乱,甚至直接给用户返回一个冷冰冰的“500 Internal Server Error”?

这就是并发测试要回答的核心问题。它模拟的不是一个用户在理想环境下的“单打独斗”,而是真实世界中“千军万马”同时发起请求的复杂场景。并发测试的目的,远不止是看系统会不会“挂掉”那么简单。它更深入地探究在高负载下,系统的各项关键指标是否健康:响应时间是否在可接受范围内(比如,95%的请求在2秒内返回)?系统的吞吐量(每秒能处理多少请求)是否达到预期?在持续压力下,内存、CPU、数据库连接等资源是否存在泄漏?多个用户同时操作同一份数据时,业务逻辑是否正确,数据是否一致(比如,会不会出现超卖)?

很多开发团队,尤其是项目初期或中小型团队,容易忽视并发测试,认为这是性能测试专家或者运维团队才需要关心的高级话题。但实际上,并发问题是埋藏在代码深处的“定时炸弹”。一个未经并发考验的HashMap在多线程下可能引发死循环,一个没加锁的库存扣减逻辑会导致商品超卖,数据库连接池配置不当会在流量尖峰时耗尽连接。等到线上真实爆发问题时,往往损失已经造成,排查和修复的成本也急剧上升。因此,将并发测试左移,融入开发阶段,是构建稳健系统的必备实践。

那么,作为开发者、测试工程师或者运维人员,我们手头有哪些“兵器”可以用来开展并发测试呢?从轻量级的命令行工具到功能强大的图形化平台,选择很多。本文将聚焦几种在实践中简单、易上手且非常有效的方法,结合具体场景,带你快速构建起并发测试的能力。我们会从最基础的ab(ApacheBench)开始,到更贴近开发流程的TestNG多线程测试,再到功能强大的Postman集合运行,最后是业界标准的JMeter。每种方法都有其适用的场景和优缺点,掌握它们,你就能像一位经验丰富的将军,在系统上线前,用模拟的“千军万马”检验你的系统防线是否牢固。

2. 初试锋芒:使用 ApacheBench (ab) 进行快速压力探测

当你需要对一个HTTP接口(比如一个查询API、一个登录接口或一个静态页面)进行最快速、最直接的压力摸底时,ApacheBench(简称ab)无疑是首选。它是一个Apache服务器自带的命令行工具,几乎在所有Linux/Unix系统和macOS上都能直接使用(Windows用户可以通过WSL或安装Apache来获取)。它的核心优势就是“简单粗暴”:无需复杂的配置,一条命令,瞬间发起海量并发请求,并给出直观的报告。

2.1ab的核心工作原理与参数解析

ab的工作模式非常直接:它会在本地创建多个线程(由-c参数指定),每个线程模拟一个用户,按照指定的总请求数(-n)或测试时长,持续向目标URL发起请求。它主要测量的是客户端(即ab本身)感受到的延迟和服务器整体的吞吐能力。

一条典型的ab命令看起来是这样的:

ab -n 1000 -c 50 http://api.example.com/v1/user/profile

这条命令的含义是:总共发起1000个请求(-n 1000),并发数为50(-c 50),即同时有50个“虚拟用户”在不停地请求目标URL。

除了-n-c,还有几个关键参数能让你更精确地控制测试:

  • -t:测试持续时间(秒)。例如-t 60表示持续压测60秒,ab会自动计算这段时间内完成的请求数。当你想观察系统在持续负载下的稳定性时,比单纯指定请求数更有效。
  • -k:启用HTTP KeepAlive。这会让ab复用TCP连接,模拟现代浏览器的行为,可以显著减少建立连接的开销,测出的吞吐量会更贴近真实场景。在测试内部API或网关时,强烈建议加上此参数。
  • -H:添加自定义请求头。例如,测试需要认证的接口:-H “Authorization: Bearer xxxxx”
  • -p:指定包含POST数据的文件。例如-p data.txt,文件内容格式为key1=value1&key2=value2
  • -T:设置POST数据的Content-Type,通常与-p联用,如-T ‘application/x-www-form-urlencoded’

注意ab本身是一个单机压测工具,其性能受限于运行ab的客户端机器(我们称之为“压测机”)的网络、CPU和端口资源。如果你用-c 500去压测,意味着压测机要同时维护500个活跃的网络连接和线程,这对压测机本身也是不小的负担。通常,对于几百以上的高并发,建议使用分布式压测工具(如JMeter分布式)或在多台机器上同时运行ab

2.2 解读ab测试报告:从数据中发现问题

执行完命令后,ab会输出一份详细的报告。看懂这份报告,是诊断性能瓶颈的第一步。我们来看一个报告样例的关键部分:

Server Software: nginx/1.18.0 Server Hostname: api.example.com Server Port: 80 Document Path: /v1/user/profile Document Length: 1256 bytes Concurrency Level: 50 Time taken for tests: 2.347 seconds Complete requests: 1000 Failed requests: 0 Total transferred: 1382000 bytes HTML transferred: 1256000 bytes Requests per second: 426.08 [#/sec] (mean) Time per request: 117.350 [ms] (mean) Time per request: 2.347 [ms] (mean, across all concurrent requests) Transfer rate: 575.00 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 5 12 3.8 11 25 Processing: 20 103 35.6 99 245 Waiting: 18 99 34.1 95 240 Total: 28 115 36.8 110 260 Percentage of the requests served within a certain time (ms) 50% 110 66% 125 75% 135 80% 142 90% 165 95% 185 98% 210 99% 225 100% 260 (longest request)
  • Requests per second每秒请求数(QPS/RPS)。这是衡量服务器吞吐量的核心指标。上面的426.08表示服务器平均每秒能处理426个此接口的请求。这个值越高越好,但它受服务器性能、网络带宽和接口逻辑复杂度共同影响。
  • Time per request (mean):这里有两个值,容易混淆。第一个117.350 [ms]每个请求的平均时间(从用户角度看)。可以理解为,在50个并发用户的情况下,单个用户平均等待了117毫秒。第二个2.347 [ms]服务器处理每个请求的平均时间(除以并发数后),这个值更接近服务器处理一个请求的真实耗时。
  • Failed requests:失败的请求数。任何非2xx/3xx的HTTP状态码都会被计入。如果这里不为0,就需要立刻关注,查看服务器日志或ab输出的错误信息,定位是网络问题、服务器错误还是业务逻辑问题。
  • Connection Times:连接时间明细。Connect是建立TCP连接的时间,Processing是服务器处理请求的时间(从发送完请求到接收到第一个响应字节),Waiting可以近似理解为请求在服务器队列中等待处理的时间。如果Waiting时间占比很高,说明服务器并发处理能力不足,请求在排队。
  • Percentage distribution响应时间百分比分布。这是最有价值的诊断数据之一。它告诉我们有多少比例的请求在某个时间内完成。例如,90% 165表示90%的请求在165毫秒内完成。我们常说的“P95响应时间”就是95%对应的值(185ms)。光看平均响应时间(mean)是有欺骗性的,可能因为少数极慢的请求拉高了平均值。而P95、P99(225ms)能更好地反映大多数用户的体验和系统的尾部延迟。如果P99响应时间远高于P50,说明系统存在不稳定的慢请求,需要深入排查。

2.3ab实战技巧与常见坑点

在实际使用中,有几点心得可以分享:

  1. 预热与冷启动:对于运行在JVM(如Java Spring Boot)或带缓存的系统,第一次请求通常很慢(JVM的JIT编译、类加载、缓存未命中)。因此,正式压测前,可以先跑一小批请求(如-n 100 -c 10)进行“预热”,让系统进入稳定状态,再开始正式测试。
  2. 注意“连接被拒绝”:如果大量出现Failed requests并伴随Connect refused错误,很可能是因为服务器或中间件(如Nginx)的并发连接数限制被触发了。你需要检查服务器的ulimit -n(文件描述符限制)、Nginx的worker_connections配置、操作系统的net.core.somaxconn参数等。
  3. 压测机自身成为瓶颈:使用tophtop命令监控压测机本身的CPU和内存使用情况。如果压测机CPU跑满或出现大量内存交换(swap),测试结果将严重失真。此时需要降低并发数-c,或者换用更强大的压测机。
  4. 测试POST接口与JSON数据:对于POST请求,尤其是提交JSON body的接口,ab的原生支持稍显麻烦。你需要将JSON内容写到一个文件(如post_data.json),然后使用-p-T参数:
    ab -n 1000 -c 50 -p post_data.json -T ‘application/json’ http://api.example.com/v1/user/create
    确保你的JSON文件内容是正确的,并且没有尾随换行符等问题。

ab就像一把瑞士军刀,小巧锋利,适合快速验证和基准测试。但当测试场景变得复杂(需要流程、参数化、断言、动态关联)时,我们就需要更强大的工具了。

3. 融入开发流程:使用 TestNG 进行多线程单元/集成测试

如果你是一名Java开发者,你的并发测试完全可以更早地开始,并且更紧密地贴合你的业务代码。TestNG是一个比JUnit更强大的测试框架,它原生支持多线程测试,允许你以并发的形式执行测试方法,这对于验证那些具有并发安全要求的工具类、服务方法或数据库操作是极其有效的。

3.1 为什么要在单元/集成测试层面做并发测试?

很多并发问题,如线程不安全的数据结构、错误的锁使用、原子性被破坏等,在单线程的单元测试中根本无法暴露。它们就像潜伏的幽灵,只在多线程同时访问时现身。通过TestNG的多线程测试,我们可以在代码提交前,甚至在开发过程中,就主动发现并修复这类问题。例如,测试一个自定义的缓存工具类是否线程安全,或者测试一个订单服务在多人同时抢购同一商品时,库存扣减是否正确。

3.2 配置 TestNG 实现并发执行

首先,确保你的项目依赖了TestNG。在Maven的pom.xml中添加:

<dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>7.7.0</version> <!-- 使用当前稳定版本 --> <scope>test</scope> </dependency>

TestNG提供了多种级别的并发控制:方法级别类级别套件级别。最常用的是通过@Test注解的threadPoolSizeinvocationCount属性来实现方法级别的并发。

import org.testng.annotations.Test; import static org.testng.Assert.*; public class ConcurrentServiceTest { private final AtomicInteger counter = new AtomicInteger(0); @Test(threadPoolSize = 5, invocationCount = 100, timeOut = 10000) public void testServiceMethodConcurrently() { // 模拟一个被并发调用的服务方法 int currentValue = counter.incrementAndGet(); System.out.println(Thread.currentThread().getName() + “ - Current count: “ + currentValue); // 这里可以调用你实际的服务类方法 // YourRealService.doSomething(); // 断言:最终计数应该等于调用次数 // 注意:这个断言在并发测试中不会在每个线程内执行,通常是在所有调用结束后进行验证。 // 更常见的做法是在所有测试执行完后,在 @AfterClass 方法中进行最终断言。 } @AfterClass public void verifyCounter() { assertEquals(counter.get(), 100, “Counter should be exactly 100 after 100 invocations.”); } }
  • threadPoolSize = 5:指定用于运行此测试方法的线程池大小为5。
  • invocationCount = 100:指定此测试方法总共被调用100次。
  • timeOut = 10000:设置总超时时间为10秒,防止测试因死锁等问题无限挂起。

当你运行这个测试类时,TestNG会使用一个最多5个线程的线程池,并发地执行testServiceMethodConcurrently方法,总共执行100次。这有效地模拟了5个用户同时反复调用该方法的场景。

3.3 测试并发安全性与数据一致性

上面的例子只是演示了并发执行。真正的并发测试,核心在于验证状态的正确性。我们来看一个更贴近业务的例子:测试一个“库存扣减”服务。

假设有一个InventoryService

public class InventoryService { private Map<String, Integer> stock = new HashMap<>(); // 注意:HashMap非线程安全! public synchronized boolean deductStock(String itemId, int quantity) { // 使用synchronized保证安全 Integer current = stock.get(itemId); if (current != null && current >= quantity) { stock.put(itemId, current - quantity); return true; } return false; } // ... 其他方法,如初始化库存 }

我们可以这样测试它的并发安全性:

public class InventoryServiceConcurrentTest { private InventoryService service = new InventoryService(); private static final String ITEM_ID = “item_001”; private static final int INITIAL_STOCK = 100; // 初始库存100 private static final int THREAD_COUNT = 20; private static final int DEDUCT_PER_THREAD = 5; // 每个线程扣5个 private CountDownLatch startLatch = new CountDownLatch(1); private CountDownLatch endLatch = new CountDownLatch(THREAD_COUNT); private AtomicInteger successCount = new AtomicInteger(0); @BeforeClass public void setup() { service.initStock(ITEM_ID, INITIAL_STOCK); } @Test public void testConcurrentDeduction() throws InterruptedException { for (int i = 0; i < THREAD_COUNT; i++) { new Thread(() -> { try { startLatch.await(); // 所有线程在此等待 boolean success = service.deductStock(ITEM_ID, DEDUCT_PER_THREAD); if (success) { successCount.incrementAndGet(); } } catch (Exception e) { e.printStackTrace(); } finally { endLatch.countDown(); } }).start(); } startLatch.countDown(); // 同时释放所有线程 endLatch.await(); // 等待所有线程执行完毕 // 验证:成功扣减的次数 * 每次扣减数 <= 初始库存 int totalDeducted = successCount.get() * DEDUCT_PER_THREAD; assertTrue(totalDeducted <= INITIAL_STOCK, “Total deducted should not exceed initial stock.“); // 更严格的验证:如果服务是线程安全的,且库存充足,应该全部成功。 // 但这里我们主要验证是否会出现超卖(totalDeducted > INITIAL_STOCK)。 System.out.println(“Initial Stock: “ + INITIAL_STOCK); System.out.println(“Threads attempted: “ + THREAD_COUNT); System.out.println(“Successful deductions: “ + successCount.get()); System.out.println(“Total items deducted: “ + totalDeducted); System.out.println(“Expected remaining stock: “ + (INITIAL_STOCK - totalDeducted)); // 可以在这里再次查询库存,验证与计算剩余库存是否一致 } }

这个测试用例创建了20个线程,模拟20个用户同时抢购商品item_001,每人买5件。初始库存100件。通过CountDownLatch确保所有线程几乎同时发起请求。测试的关键断言是:成功扣减的总数不能超过初始库存(即不能超卖)。如果InventoryServicedeductStock方法没有正确的同步机制(比如去掉synchronized,并且使用HashMap),这个测试有很大概率会失败,或者最终库存出现负数。

3.4 实操心得与注意事项

  1. 资源清理:并发测试可能会创建数据库连接、文件句柄等资源。务必在@AfterClass@AfterMethod中做好清理工作,避免影响后续测试或其他测试套件。
  2. 测试隔离:确保每个并发测试用例是独立的,不共享可变状态。如果必须共享(如上面的库存),要确保其初始状态在每个测试类开始前是确定性的(在@BeforeClass中重置)。
  3. 超时设置:一定要设置合理的timeOut。并发测试容易引发死锁、活锁,没有超时机制可能导致测试线程永远挂起。
  4. 结合Mock:对于涉及外部服务(如支付网关、短信服务)的并发测试,使用Mock框架(如Mockito)来模拟这些外部依赖,使测试更聚焦于核心逻辑的并发安全性。
  5. 它不是性能测试工具TestNG的多线程测试主要用于功能正确性验证,即“在高并发下,逻辑是否正确”。虽然它也能反映出一些性能问题(比如某个方法在并发下变得极慢),但它不提供像abJMeter那样丰富的性能指标(QPS, 响应时间分布等)。它的优势在于能和你的业务代码无缝集成,在CI/CD流水线中自动运行。

通过TestNG,我们将并发测试的防线推进到了代码层面。接下来,我们看一个在API测试领域广泛使用的工具,它如何帮助我们进行更复杂的并发场景测试。

4. 协作与场景化:使用 Postman 运行集合进行并发测试

Postman可能是当今最流行的API调试工具。除了手动调试,它的“集合(Collection)”和“运行器(Collection Runner)”功能,结合 Newman(命令行工具),可以轻松组织一系列API请求,并模拟用户顺序执行。虽然Postman本身是图形界面,但其并发测试能力需要通过运行器或Newman以一定策略来实现。

4.1 构建可测试的请求集合

首先,你需要将你的API测试场景构建成一个集合。例如,一个“用户购物流程”集合可能包含:

  1. POST /login:登录,获取token。
  2. GET /products:浏览商品列表。
  3. GET /product/{id}:查看商品详情。
  4. POST /cart:添加商品到购物车。
  5. POST /order:提交订单。

在Postman中,你可以为集合设置环境变量(如{{base_url}}),为请求编写测试脚本(Tests标签页),用于断言响应结果,并提取响应中的数据(如将登录返回的token保存到环境变量)。这是进行自动化、可重复测试的基础。

4.2 通过 Collection Runner 模拟轻度并发

Postman图形界面的“Collection Runner”允许你设置迭代次数(Iterations)和延迟(Delay)。但它本质上是顺序执行集合中的请求。如何模拟并发呢?一个常见的技巧是:

  1. 将你想要并发测试的单个请求(比如压力测试查询接口GET /api/data)单独放在一个集合中。
  2. 在Collection Runner中,设置一个非常大的迭代次数(比如1000次)。
  3. 将延迟(Delay)设置为0毫秒
  4. 点击运行。

由于计算机处理速度很快,且没有延迟,Postman会以尽可能快的速度顺序发送这1000个请求。这近似于一个“异步”或“高吞吐”的请求流,但它仍然是一个接一个的(排队),并非严格的同时并发(多线程)。因此,这种方法更适合于测试API在快速连续请求下的稳定性和吞吐能力,而不是真正的并发处理能力。

4.3 利用 Newman 和 Shell 脚本实现并发

要实现真正的并发,我们需要借助Postman的命令行工具Newman。你可以用Newman来运行一个集合,并且可以在Shell脚本中,同时启动多个Newman进程。

步骤一:安装Newman并导出集合

npm install -g newman

在Postman中,将你的集合导出为JSON文件(例如my-collection.json),同时如果有环境变量文件也一并导出(my-environment.json)。

步骤二:编写并发执行脚本创建一个Shell脚本run_concurrent.sh

#!/bin/bash # 定义并发数 CONCURRENCY=10 # 定义每个进程的迭代次数 ITERATIONS_PER_PROCESS=100 # 集合文件路径 COLLECTION=“./my-collection.json” ENVIRONMENT=“./my-environment.json” echo “Starting $CONCURRENCY concurrent Newman processes...“ for i in $(seq 1 $CONCURRENCY) do # 后台运行 newman 命令,每个进程执行100次迭代 newman run $COLLECTION -e $ENVIRONMENT -n $ITERATIONS_PER_PROCESS --reporters cli,json --reporter-json-export “report_$i.json” > “output_$i.log” 2>&1 & echo “Started process $i” done echo “Waiting for all processes to finish...“ wait # 等待所有后台进程结束 echo “All concurrent tests completed.“ # 这里可以添加聚合报告的逻辑,例如分析所有 report_*.json 文件

这个脚本会启动10个后台进程,每个进程使用Newman独立运行集合100次。这10个进程是操作系统级别的并发,它们会同时向服务器发送请求,从而模拟10个并发用户的行为。

步骤三:聚合与分析结果每个Newman进程会生成独立的日志(output_$i.log)和JSON报告(report_$i.json)。你可以编写额外的脚本,解析这些JSON报告,汇总总的请求数、失败数、平均响应时间等。Newman本身也支持–reporters htmlextra生成更美观的HTML报告,但多个进程的报告需要手动合并。

4.4 Postman/Newman 并发测试的适用场景与局限

适用场景

  • API集成流程的并发验证:非常适合测试一个完整的、有状态的多步骤业务流程(如登录-浏览-下单)在并发用户下的正确性。你可以验证在并发下单时,订单号是否唯一,库存是否正确扣减等。
  • 依赖已有Postman集合:如果你的团队已经用Postman积累了大量的API测试用例,利用Newman进行并发测试是一个低成本的扩展方案,无需学习新工具。
  • CI/CD集成:Newman可以很方便地集成到Jenkins、GitLab CI等持续集成流水线中,在每次构建后自动运行API并发测试。

局限与注意事项

  1. 性能指标有限:Newman提供的性能指标比较基础,主要是平均响应时间和最小/最大响应时间,缺乏像abJMeter那样详细的百分比分布(P95, P99)、吞吐量曲线等深度性能分析数据。
  2. 资源消耗:每个Newman进程都是一个独立的Node.js进程,启动开销较大。模拟成百上千的并发用户时,压测机本身的资源消耗会非常可观,可能成为瓶颈。
  3. 参数化与数据驱动:虽然Postman支持CSV、JSON数据文件进行参数化,但在上述并发脚本模型中,需要小心处理数据文件的共享与竞争。通常建议每个进程使用独立的数据文件片段,或者使用动态生成数据的方式(在Pre-request Script中)。
  4. 不是专业的压测工具:它的核心定位是API功能测试自动化。对于需要精确控制吞吐量、模拟复杂思考时间、进行大规模分布式压测的场景,JMeter是更专业的选择。

Postman提供了一条从手动调试到自动化测试,再到轻度并发验证的平滑路径。当你需要更强大、更专业的压测能力时,就该JMeter登场了。

5. 专业级压测:使用 JMeter 构建全面并发测试方案

Apache JMeter是功能最全面的开源性能和负载测试工具之一。它不仅能模拟HTTP请求,还支持数据库(JDBC)、FTP、JMS、TCP等多种协议。其图形化界面使得创建复杂测试场景(如事务控制器、逻辑控制器、定时器、断言、监听器等)变得直观,同时它也能以非GUI模式运行,适合集成到CI/CD和进行大规模分布式压测。

5.1 JMeter 核心概念与测试计划结构

启动JMeter后,你会看到一个“测试计划(Test Plan)”。它是所有元素的容器。一个典型的HTTP并发测试计划包含以下层次结构:

  • 线程组(Thread Group):定义虚拟用户(线程)的数量、启动时间、循环次数等。这是并发控制的基石。
  • 采样器(Sampler):向服务器发送请求的组件,如HTTP请求采样器。
  • 逻辑控制器(Logic Controller):控制采样器的执行逻辑,如循环控制器、仅一次控制器、事务控制器等。
  • 配置元件(Config Element):为采样器提供配置信息,如HTTP请求默认值、CSV数据文件设置、HTTP信息头管理器等。
  • 前置处理器/后置处理器(Pre/Post Processor):在发送请求前或收到响应后执行操作,常用于提取数据(如JSON Extractor、正则表达式提取器)或修改请求。
  • 断言(Assertion):验证响应结果是否符合预期。
  • 监听器(Listener):收集测试结果,并以图表、表格、树形或文件的形式展示。如查看结果树、聚合报告、图形结果、用表格查看结果等。

5.2 构建一个完整的 HTTP 接口并发测试计划

让我们一步步创建一个模拟用户登录后查询信息的并发测试场景。

步骤1:添加线程组右键测试计划 -> 添加 -> 线程(用户) -> 线程组。

  • 线程数(Number of Threads):虚拟用户数,例如 100。
  • Ramp-up period(seconds):启动所有线程所需的时间。设为10,表示JMeter会在10秒内逐步启动这100个线程,而不是瞬间启动,这有助于平滑地给服务器施加压力,观察系统在负载逐渐增加时的表现。
  • 循环次数(Loop Count):每个线程执行测试计划的次数。例如 10,意味着100个用户,每个用户执行10轮操作,总共1000次请求。也可以勾选“永远”,配合调度器进行长时间稳定性测试。

步骤2:添加配置元件(可选但推荐)右键线程组 -> 添加 -> 配置元件 -> HTTP请求默认值。 在这里设置所有HTTP请求共享的服务器名称(如api.yourserver.com)和端口。这样,后续的HTTP请求采样器就无需重复填写,只需指定路径。

步骤3:添加事务控制器(模拟用户操作流)右键线程组 -> 添加 -> 逻辑控制器 -> 事务控制器。将其命名为“用户会话”。事务控制器会将其子元件的执行时间合并统计,方便我们分析一个完整业务流程(如登录+查询)的耗时。

步骤4:在事务控制器下添加登录请求(HTTP采样器)右键事务控制器 -> 添加 -> 采样器 -> HTTP请求。

  • 名称:用户登录
  • 方法:POST
  • 路径:/auth/login
  • 在“消息体数据”选项卡中,填入JSON格式的登录信息,如{“username”: “${USERNAME}”, “password”: “${PASSWORD}”}。这里的${USERNAME}是变量,我们稍后通过CSV文件注入。

步骤5:添加后置处理器,提取登录Token右键“用户登录”HTTP请求 -> 添加 -> 后置处理器 -> JSON提取器。

  • 名称:提取Token
  • JSON路径表达式:假设登录返回{“token”: “eyJhbGciOiJ…”},则表达式为$.token
  • 变量名称:AUTH_TOKEN。这样,后续请求就可以用${AUTH_TOKEN}来引用这个token。

步骤6:添加HTTP信息头管理器,管理认证头右键“用户登录”HTTP请求(或其父级事务控制器) -> 添加 -> 配置元件 -> HTTP信息头管理器。 添加一个头:Name: Authorization,Value: Bearer ${AUTH_TOKEN}。这样,该管理器下的所有请求都会自动带上这个认证头。

步骤7:添加查询请求和思考时间在事务控制器下,继续添加第二个HTTP请求采样器,命名为“查询用户信息”,方法GET,路径如/user/profile。 在两个请求之间,可以右键 -> 添加 -> 定时器 -> 固定定时器,设置一个等待时间(如2000毫秒),用来模拟用户操作间的停顿(思考时间)。这对于模拟真实用户行为、测试系统在持续但非爆发压力下的表现至关重要。

步骤8:参数化:使用CSV数据文件配置我们需要为100个虚拟用户准备不同的用户名和密码。创建一个users.csv文件:

USERNAME,PASSWORD user1,pass1 user2,pass2 ... (至少100行)

右键线程组 -> 添加 -> 配置元件 -> CSV数据文件设置。

  • 文件名:指向你的users.csv路径。
  • 变量名称:USERNAME,PASSWORD(与CSV文件表头对应)。
  • 其他设置:Recycle on EOF?设为True(如果线程数多于数据行数,则循环使用数据);Stop thread on EOF?设为False

现在,登录请求中的${USERNAME}${PASSWORD}就会被CSV文件中的每一行数据替换。

步骤9:添加监听器,查看结果右键线程组 -> 添加 -> 监听器 -> 聚合报告。再添加一个“用表格查看结果”。聚合报告会给出全局的统计信息,而表格视图可以看到每个请求的详细情况。

步骤10:运行与调试点击工具栏的绿色开始按钮。首先在“用表格查看结果”中查看是否有失败的请求(红色行)。如果有,结合“查看结果树”监听器(调试时添加,正式压测时建议禁用,因为非常耗内存)检查请求和响应的详细信息,排查问题。

5.3 关键配置与性能调优

  1. JVM 堆内存设置:JMeter本身是Java应用,默认堆内存可能不够。在jmeter.bat(Windows)或jmeter(Linux/macOS)启动脚本中,修改HEAP参数,例如-Xms4g -Xmx4g。如果遇到invalid initial heap size错误,请确保设置的值不超过你机器物理内存的可用量,并且格式正确。

    注意-Xms-Xmx的值必须相同,以避免运行时堆内存调整带来的性能波动。同时,要为操作系统和其他进程留出足够内存。

  2. 禁用不需要的监听器:像“查看结果树”这样的监听器会记录每个请求/响应的详细信息,在高压测试下会迅速消耗大量内存并成为性能瓶颈。正式压测时,只保留“聚合报告”、“汇总报告”等轻量级监听器,或者将结果直接写入文件(如“简单数据写入器”)。

  3. 使用命令行(非GUI)模式执行:图形界面本身会消耗资源。生产环境压测务必使用命令行模式:

    jmeter -n -t your_test_plan.jmx -l result.jtl -e -o ./report
    • -n:非GUI模式。
    • -t:指定测试计划文件。
    • -l:指定结果日志文件(JTL格式)。
    • -e -o:测试结束后生成HTML报告到指定目录。
  4. 分布式压测:当单台压测机无法产生足够压力或成为瓶颈时,需要搭建JMeter分布式环境。你需要一个控制机(Controller)和多台压力机(Agent)。在控制机上运行测试计划,并将任务分发到各个Agent上执行,汇总结果。这需要配置jmeter.properties中的远程主机列表,并在Agent机器上启动jmeter-server

5.4 结果分析与问题定位

运行测试后,重点分析聚合报告:

  • 样本(Samples):总请求数。
  • 平均值、中位数、90%百分位等:关注P90、P95、P99响应时间。如果P99远高于平均值,说明存在一些慢请求拖累了尾部用户体验。
  • 异常%:错误率。任何非零的错误率都需要严肃对待。
  • 吞吐量(Throughput):每秒处理的请求数。这是系统处理能力的直接体现。
  • 接收/发送 KB/sec:网络吞吐量。

如果发现性能不佳或错误率高,需要结合服务器监控(CPU、内存、磁盘I/O、网络)、应用日志、数据库慢查询日志等进行综合排查。JMeter的测试结果是指引你发现系统瓶颈的第一个路标。

从简单的ab到与代码结合的TestNG,再到便于协作的Postman/Newman,最后到功能强大的JMeter,这四种方法覆盖了从快速验证到专业压测、从单元测试到集成流程测试的不同场景。没有一种工具是万能的,在实际工作中,我通常会根据测试目标灵活选择和组合它们。例如,用TestNG确保核心服务类的线程安全,用ab对新接口做快速基准测试,用Postman集合保证API流程的功能正确性,最后用JMeter对核心交易链路进行全链路的压力测试和容量评估。掌握这“四板斧”,你就能为你的系统构建起一道坚实的并发质量防线。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询