几种性能测试工具的总结
wptr33 2025-01-13 18:26 18 浏览
我们经常会谈论性能、并发等问题,但是衡量性能不是说写段代码循环几百次这么简单。最近从项目上的同事了解到了代码化的测试性能测试工具 k6,以及结合之前用过的Java 微基准测试 (JMH)、AB (Apache Benchmark) 测试、Jmeter 做一下总结。
谈性能,实际上结合实际的业务背景、网络条件、测试数据的选择等因素影响非常大,单纯的谈 QPS 等数据意义不大。
这里介绍的几个工具刚好能满足平时开发工作中不同场景下衡量性能的需求,因此整理出来。
- Java 微基准测试 (JMH) 可以用于衡量一段 Java 代码到底性能如何,例如我们平时总是谈 StringBuilder 比 new String() 快很多。我们有一个很好地量化方法,就可以很直观的展示出一段代码的性能优劣。
- AB (Apache Benchmark) 测试是 Apache 服务器内置的一个 http web 压测工具,非常简单易用。Mac 预装了 Apache,因此可以随手使用来测试一个页面或者 API 的性能。贵在简单易用,无需额外安装。
- k6 一款使用 go 语言编写,支持用户编写测试脚本的测试套件。弥补了 ab 测试功能不足,以及 jemeter 不容易代码化的缺点。也是项目上需要使用,从同事那里了解到的。
- Jmeter 老牌的性能测试工具,有大量专门讲 jmeter 的资料,本文不再赘述。
那我们从 JMH 开始从来看下这几个工具的特点和使用吧。
Java 微基准测试
StringBuilder 到底比 new String() 快多少呢?
我们可以使用 JMH 来测试一下。JMH 是一个用于构建、运行和分析 Java 方法运行性能工具,可以做到 nano/micro/mili/macro 时间粒度。JMH 不仅可以分析 Java 语言,基于 JVM 的语言都可以使用。
JMH 由 OpenJDK 团队开发,由一次下载 OpenJDK 时注意到官网还有这么一个东西。
OpenJdk 官方运行 JMH 测试推的方法是使用 Maven 构建一个单独的项目,然后把需要测试的项目作为 Jar 包引入。这样能排除项目代码的干扰,得到比较可靠地测试效果。当然也可以使用 IDE 或者 Gradle 配置到自己项目中,便于和已有项目集成,代价是配置比较麻烦并且结果没那么可靠。
使用 Maven 构建基准测试
根据官网的例子,我们可以使用官网的一个模板项目。
mvn archetype:generate \
-DinteractiveMode=false \
-DarchetypeGroupId=org.openjdk.jmh \
-DarchetypeArtifactId=jmh-java-benchmark-archetype \
-DgroupId=org.sample \
-DartifactId=test \
-Dversion=1.0
创建一个项目,导入 IDE,Maven 会帮我们生成一个测试类,但是这个测试类没有任何内容,这个测试也是可以运行的。
先编译成 jar
mvn clean install
然后使用 javar -jar 来运行测试
java -jar target/benchmarks.jar
运行后可以看到输出信息中包含 JDK、JVM 等信息,以及一些用于测试的配置信息。
#JMH version: 1.22
# VM version: JDK 1.8.0_181, Java HotSpot(TM) 64-Bit Server VM, 25.181-b13
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/bin/java
# VM options:
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: org.sample.MyBenchmark.testSimpleString
下面是一些配置信息说明
- Warmup 因为 JVM 即时编译的存在,所以为了更加准确有一个预热环节,这里是预热 5,每轮 10s。
- Measurement 是真实的性能测量参数,这里是 5轮,每轮10s。
- Timeout 每轮测试,JMH 会进行 GC 然后暂停一段时间,默认是 10 分钟。
- Threads 使用多少个线程来运行,一个线程会同步阻塞执行。
- Benchmark mode 输出的运行模式,常用的有下面几个。Throughput 吞吐量,即每单位运行多少次操作。AverageTime 调用的平均时间,每次调用耗费多少时间。SingleShotTime 运行一次的时间,如果把预热关闭可以测试代码冷启动时间
- Benchmark 测试的目标类
实际上还有很多配置,可以通过 -h 参数查看
java -jar target/benchmarks.jar -h
由于默认的配置停顿的时间太长,我们通过注解修改配置,并增加了 Java 中最基本的字符串操作性能对比。
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 3)
@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS)
@Threads(8)
@Fork(1)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class MyBenchmark {
@Benchmark
public void testSimpleString() {
String s = "Hello world!";
for (int i = 0; i < 10; i++) {
s += s;
}
}
@Benchmark
public void testStringBuilder() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10; i++) {
sb.append(i);
}
}
}
在控制台可以看到输出的测试报告,我们直接看最后一部分即可。
Benchmark Mode Cnt Score Error Units
MyBenchmark.testSimpleString thrpt 10 226.930 ± 16.621 ops/ms
MyBenchmark.testStringBuilder thrpt 10 80369.037 ± 3058.280 ops/ms
Score 这列的意思是每毫秒完成了多少次操作,可见 StringBuilder 确实比普通的 String 构造器性能高很多。
更多有趣的测试
实际上平时 Java 开发中一些细节对性能有明显的影响,虽然对系统整体来说影响比较小,但是注意这些细节可以低成本的避免性能问题堆积。
其中一个非常有意思细节是自动包装类型的使用,即使是一个简单的 for 循环,如果不小心讲 int 使用成 Integer 也会造成性能浪费。
我们来编写一个简单的基准测试
@Benchmark
public void primaryDataType() {
int sum = 0;
for (int i = 0; i < 10; i++) {
sum += i;
}
}
@Benchmark
public void boxDataType() {
int sum = 0;
for (Integer i = 0; i < 10; i++) {
sum += i;
}
}
运行测试后,得到下面的测试结果
AutoBoxBenchmark.boxDataType thrpt 5 312779.633 ± 26761.457 ops/ms
AutoBoxBenchmark.primaryDataType thrpt 5 8522641.543 ± 2500518.440 ops/ms
基本类型的性能高出了一个数量级。当然你可能会说基本类型这种性能问题比较微小,但是性能往往就是这种从细微处提高的。另外编写 JMH 测试也会让团队看待性能问题更为直观。
一份直观的 Java 基础性能报告
下面是我写的常见场景的性能测试,例如 StringBuilder 比 new String() 速度快几个数量级。
代码仓库和持续更新的基准测试可以看下面的仓库。
https://github.com/linksgo2011/jmh-reports
Apache Benchmark 测试
我想用命令行快速简单的压测一下网站该怎么办呢?
Apache Benchmark (简称 ab,不同于产品领域的 A/B 测试) 是 Apache web 服务器自带的性能测试工具,在 windows 或者 linux 上安装了 Apache 服务器就可以在其安装位置的 bin 目录中找到 ab 这个程序。
ab 使用起来非常简单,一般只需要 -n 参数指明发出请求的总数,以及 -c 参数指明测试期间的并发数。
例如对 ThoughtWorks 官网首页发出 100 个请求,模拟并发数为 10:
ab -n 100 -c 10 https://thoughtworks.com/
需要注意的是 ab 工具接收一个 url 作为参数,仅仅是一个域名是不合法的,需要增加 / 表示首页。稍等片刻后就可以看到测试报告:
Server Software: nginx/1.15.6
Server Hostname: thoughtworks.com
Server Port: 443
SSL/TLS Protocol: TLSv1.2,ECDHE-RSA-AES256-GCM-SHA384,2048,256
Server Temp Key: ECDH P-256 256 bits
TLS Server Name: thoughtworks.com
Document Path: /
Document Length: 162 bytes
Concurrency Level: 10
Time taken for tests: 42.079 seconds
Complete requests: 100
Failed requests: 0
Non-2xx responses: 100
Total transferred: 42500 bytes
HTML transferred: 16200 bytes
Requests per second: 2.38 [#/sec] (mean)
Time per request: 4207.888 [ms] (mean)
Time per request: 420.789 [ms] (mean, across all concurrent requests)
Transfer rate: 0.99 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 1056 2474 3006.1 1144 23032
Processing: 349 740 1003.5 379 8461
Waiting: 349 461 290.9 377 2265
Total: 1411 3214 3273.9 1674 23424
Percentage of the requests served within a certain time (ms)
50% 1674
66% 2954
75% 3951
80% 4397
90% 6713
95% 9400
98% 14973
99% 23424
100% 23424 (longest request)
从这个报告中可以看到服务器的一些基本信息,以及请求的统计信息。比较重要的指标是 Requests per second 每秒钟完成的请求数量,不严格的说也就是我们的平时说的 QPS。
ab 测试是专为 http 请求设计的,因此 ab 的其他参数和 curl 的参数比较类似,也可以指定 http method 以及 cookies 等参数。
K6 测试套件
我需要编写复杂的测试脚本,并保留压测的脚本、参数、数据,以及版本化该怎么做呢?
k6 是一个压力测试套件,使用 golang 编写。主要特性有:
- 提供了友好的 CLI 工具
- 使用 JavaScript 代码编写测试用例
- 可以根据性能条件设置阈值,表明成功还是失败
k6 没有使用 nodejs 而是 golang 程序,通过包裹了一个 JavaScript 运行时来运行 JavaScript 脚本,因此不能直接使用 npm 包以及 Nodejs 提供的一些 API。
同时,k6 在运行测试时,没有启动浏览器,主要用于测试页面以及 API 加载速度。k6 提供了通过网络请求(HAR)生成测试脚本的方法,实现更简便的测试脚本编写,以及 session 的维护。
使用
在 Mac 上比较简单,直接使用 HomeBrew 即可安装:
brew install k6
其他平台官网也提供了相应的安装方式,比较特别的是提供了 Docker 的方式运行。
直接使用 k6 的命令运行测试,官网提供了一个例子:
k6 run github.com/loadimpact/k6/samples/http_get.js
也可以编写自己的测试脚本:
import http from "k6/http";
import { sleep } from "k6";
export default function() {
http.get("https://www.thoughtworks.com/");
sleep(1);
};
保存文件 script.js 后运行 k6 命令
k6 run script.js
然后可以看到 http 请求的各项指标
/\ | ̄ ̄| / ̄ ̄/ / ̄/
/\ / \ | |_/ / / /
/ \/ \ | | /  ̄ ̄\
/ \ | | ̄\ \ | (_) |
/ __________ \ |__| \__\ \___/ .io
execution: local
output: -
script: k6.js
duration: -, iterations: 1
vus: 1, max: 1
done [==========================================================] 1 / 1
data_received..............: 108 kB 27 kB/s
data_sent..................: 1.0 kB 252 B/s
http_req_blocked...........: avg=2.35s min=2.35s med=2.35s max=2.35s p(90)=2.35s p(95)=2.35s
http_req_connecting........: avg=79.18ms min=79.18ms med=79.18ms max=79.18ms p(90)=79.18ms p(95)=79.18ms
http_req_duration..........: avg=639.03ms min=639.03ms med=639.03ms max=639.03ms p(90)=639.03ms p(95)=639.03ms
http_req_receiving.........: avg=358.12ms min=358.12ms med=358.12ms max=358.12ms p(90)=358.12ms p(95)=358.12ms
http_req_sending...........: avg=1.79ms min=1.79ms med=1.79ms max=1.79ms p(90)=1.79ms p(95)=1.79ms
http_req_tls_handshaking...: avg=701.46ms min=701.46ms med=701.46ms max=701.46ms p(90)=701.46ms p(95)=701.46ms
http_req_waiting...........: avg=279.12ms min=279.12ms med=279.12ms max=279.12ms p(90)=279.12ms p(95)=279.12ms
http_reqs..................: 1 0.249921/s
iteration_duration.........: avg=4s min=4s med=4s max=4s p(90)=4s p(95)=4s
iterations.................: 1 0.249921/s
vus........................: 1 min=1 max=1
vus_max....................: 1 min=1 max=1
k6 提供的性能指标相对 ab 工具多很多,也可以通过脚本自己计算性能指标。和 ab 工具中表明每秒钟处理完的请求数是 http_reqs,上面的测试默认只有一个用户的一次请求,如果通过参数增加更多请求,可以看到和 ab 工具得到的结果比较接近。
运行压力测试时,需要增加更多的虚拟用户(VU),vus 参数和持续时间的参数:
k6 run --vus 10 --duration 30s script.js
编写测试脚本的一些规则
default 方法是用于给每个 VU 以及每次迭代重复运行的,因此需要把真正的测试代码放到这个方法中,例如访问某个页面。
为了保证测试的准确性,一些初始化的代码不应该放到 default 方法中。尤其是文件的读取等依赖环境上下文的操作不能放到 default 方法中执行,这样做也会丢失 k6 分布式运行的能力。
前面提到的命令行参数,例如指定虚拟用户数量 --vus 10 ,这些参数也可以放到脚本代码中。通过暴露一个 options 对象即可。
export let options = {
vus: 10,
duration: "30s"
};
为了更为真实的模拟用户访问的场景,k6 提供了在整个测试期间让用户数量和访问时间呈阶段性变化的能力。只需要在 options 中增加 stages 参数即可:
export let options = {
stages: [
{ duration: "30s", target: 20 },
{ duration: "1m30s", target: 10 },
{ duration: "20s", target: 0 },
]
};
在测试过程中需要检查网络请求是否成功,返回的状态码是否正确,以及响应时间是否符合某个阈值。在脚本中可以通过调用 check() 方法编写检查语句,以便 k6 能收集到报告。
import http from "k6/http";
import { check, sleep } from "k6";
export let options = {
vus: 10,
duration: "30s"
};
export default function() {
let res = http.get("https://www.thoughtworks.com/");
check(res, {
"status was 200": (r) => r.status == 200,
"transaction time OK": (r) => r.timings.duration < 200
});
sleep(1);
};
报告输出
k6 默认将报告输出到 stdout 控制台,同时也提供了多种格式报告输出,包括:
- JSON
- CSV
- InfluxDB
- Apache Kafka
- StatsD
- Datadog
- Load Impact cloud platform
当然,我们在编写测试的时候不可能只有一个用例,对多个场景可以在脚本中通过group 进行分组,分组后输出的报告会按照分组排列。同时,也可以使用对一个组整体性能衡量的指标 group_duration。
import { group } from "k6";
export default function() {
group("user flow: returning user", function() {
group("visit homepage", function() {
// load homepage resources
});
group("login", function() {
// perform login
});
});
};
InfluxDB 等外部数据收集平台时,还可以打上标签,供过滤和检索使用。k6 提供了一些内置的标签,并允许用户自定义标签。
总结
实际上用于性能测试的工具还有很多,也有一些专门的工具针对网络质量(iperf) 、数据库(sysbench)、前端页面(PageSpeed)等专门方面进行性能测试。
写本文的初衷是想说评价性能,以及做性能优化的第一步应该是寻找到合适工具做一次基准测试,这样的优化往往才有意义。我在使用 JMH 后不仅在工作中使用它对一些代码片段进行测试以及优化,同时更重要的是,在codereview 中对某些操作关于性能的讨论不再基于经验,而是事实。
- 上一篇:一篇文章让你学会Elasticsearch中的查询
- 下一篇:数据库负载均衡
相关推荐
- 威信Chronosonic XVX全新旗舰全球首发 设计特点彻底公开
-
第一眼看到WilsonAudio新推出的ChronosonicXVX音箱,相信大家都会直觉认为它是两年前超级旗舰WAMMMasterChronosonic的缩小版,不过这个推测并不完全正确。C...
- C#高精度Timer和Delay以及时间测量
-
在PCHMI7.0后在工具箱里会多一个MsTimer,以及Delay和Microsecond两个类。...
- python教程从基础到精通,第9课—日期与时间
-
Hello,小伙伴们,祝大家五.一玩得快乐!刚学习完了七大数据类型,今天咱们来学习日期与时间的表示方法。Python标准库中提供了时间和日期的支持:calendar:日历相关;time、datetim...
- 软件测试|教你轻松玩转Python日期时间
-
Python基础之日期时间处理...
- Go语言中互斥锁与读写锁,你知多少?
-
简述Golang中的锁机制主要包含互斥锁和读写锁互斥锁互斥锁是传统并发程序对共享资源进行控制访问的主要手段。在Go中主要使用sync.Mutex的结构体表示。一个简单的示例:funcmutex()...
- 变形金刚动画大电影——经典台词赏析
-
YOURDAYSARENUMBEREDNOW,DECEPTI-CREEPS你们活不了多久了,霸天虎小子。-{铁皮说的话,体现了铁皮的嫉恶如仇,可是后来铁皮在飞船上遇袭身亡,可谓是出师未捷身先...
- Python时间日期模块使用教程(python3日期)
-
1.时间日期处理概述在日常编程中,时间日期处理是非常常见的需求,比如:记录日志时间...
- 亚马逊介绍AWS“无服务器”云服务改进:数据库可线上扩充容量等
-
IT之家11月29日消息,在今天于美国拉斯维加斯展开的亚马逊“AWSre:Invent2023”活动中,亚马逊计算部门资深副总裁PeterDeSantis,介绍了旗下三款云端服务,IT...
- 2.日期格式 datetime(日期时间显示格式)
-
fromdatetimeimportdatetime1.获取当前日期和时间now=datetime.now()#2025-05-3110:56:01.4687822.格式化日期...
- 【科普】时间单位大盘点(时间单位都有哪些?)
-
时间单位,是7种基本单位之一,长度、时间、质量、物质的量、光照度、电流和(热力学)温度是七种基本单位。本词条中时间单位以时间从大到小列。今天我们来盘点下时间的单位换算...
- 基于PHP的Laravel框架,盘点Github高星Web管理后台,效率为王!
-
在Web开发工作中,选择一个高效、稳定的后台管理系统是提高开发效率的关键。虽然PHP在近些年中的热度有所减退,但其上手简单、开源、灵活且被广泛应用的特点,仍然使其在编程语言排行榜中保持前十的位置。这表...
- 如何使用PHP编写一个简单的留言板?
-
留言板是一个常见的Web应用程序,允许用户在网站上发布和查看留言。在本文中,我们将使用PHP编写一个简单的留言板,介绍构建过程中的关键步骤和技巧。一、准备工作在开始编写留言板之前,我们需要准备好以下工...
- 产品经理提需求时要考虑的 15 个隐性需求
-
虽然世界充满未知的变化,但是有一些大的方向还是可以把握的,本文跟大家谈谈产品经理提需求时要考虑的15个隐性需求,enjoy~俗话说,计划赶不上变化快,无论需求文档做得如何细致,考虑得如何周全,总会...
- 关于 PHP 启动 MongoDb 找不到指定模块问题
-
前言:最近有一个小demo,需要通过PHP将用户行为记录储存到MongoDB,再用Spark做协同过滤。由于以前处理跨语言交互是通过消息中间件,这次本地使用MongoDB却弄出了几个问...
- PHP程序员老鸟面试经历(php程序员怎么样)
-
在任何时代找任何工作都有面试这么一说的。特别是高端技术类的工种对技术理论和技术实操能力要求很严格。大部分公司招收技术员工的要求也越来愈高。至于PHP程序员也是如此,我估计大多数PHP老鸟已经不在意所...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
因果推断Matching方式实现代码 因果推断模型
-
git pull命令使用实例 git pull--rebase
-
面试官:git pull是哪两个指令的组合?
-
git 执行pull错误如何撤销 git pull fail
-
git pull 和git fetch 命令分别有什么作用?二者有什么区别?
-
git fetch 和git pull 的异同 git中fetch和pull的区别
-
git pull 之后本地代码被覆盖 解决方案
-
还可以这样玩?Git基本原理及各种骚操作,涨知识了
-
git命令之pull git.pull
-
- 最近发表
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)