(八)JMH的详细使用,附带压测dubbo案例+代码
1、JMH简介
JMH
即Java Microbenchmark Harness
,是Java
用来做基准测试的一个工具,该工具由OpenJDK
提供并维护,测试结果可信度高。
相对于 Jmeter、ab ,它通过编写代码的方式进行压测,在特定场景下会更能评估某项性能。
本次通过使用JMH来压测Dubbo的性能(官方也是使用JMH压测)
2、使用
只需要引用两个jar即可:
org.openjdk.jmh
jmh-core
1.29
org.openjdk.jmh
jmh-generator-annprocess
1.29
通过一系列的注解即可使用JMH。
@State
只能用在类上,有三个取值:
Scope.Thread
:默认的State,每个测试线程分配一个实例;Scope.Benchmark
:所有测试线程共享一个实例,用于测试有状态实例在多线程共享下的性能;Scope.Group
:每个线程组共享一个实例;@OutputTimeUnit
时间单位,如毫秒 TimeUnit.MILLISECONDS、秒 TimeUnit.SECONDS
@Benchmark
声明一个
public
方法为基准测试方法。该类下的所有被@Benchmark
注解的方法都会执行。相当于类的main方法@BenchmarkMode
指定测试某个接口的指标,如吞吐量、平均执行时间,一般我都是选择 ALL
Mode有:
- 【(八)JMH的详细使用,附带压测dubbo案例+代码】Throughput: 整体吞吐量,例如“1秒内可以执行多少次调用” (thrpt,参加第5点)
- AverageTime: 调用的平均时间,例如“每次调用平均耗时xxx毫秒”。(avgt)
- SampleTime: 随机取样,最后输出取样结果的分布,例如“99%的调用在xxx毫秒以内,99.99%的调用在xxx毫秒以内”(simple)
- SingleShotTime: 以上模式都是默认一次 iteration 是 1s,唯有 SingleShotTime 是只运行一次。往往同时把 warmup 次数设为0,用于测试冷启动时的性能。(ss)
@BenchmarkMode({Mode.Throughput,Mode.All})
public class StressTestProvider {}
@Measurement
用于控制压测的次数
//测量2次,每次测量的持续时间为20秒
@Measurement(iterations = 2, time = 20 , timeUnit = TimeUnit.SECONDS)
@Warmup
预热,预热可以避免首次因为一些其他因素,如CPU波动、类加载耗时这些情况的影响。
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
参数解释同上。
@Fork
@Fork
用于指定fork
出多少个子进程
来执行同一基准测试方法。@Threads
@Threads
注解用于指定使用多少个线程来执行基准测试方法,如果使用@Threads
指定线程数为2
,那么每次测量都会创建两个线程来执行基准测试方法。3、运行
我这里的例子是压测dubbo,源码链接在文末完整例子:
@BenchmarkMode({Mode.All})
@Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS)
//测量次数,每次测量的持续时间
@Measurement(iterations = 3, time = 10, timeUnit = TimeUnit.SECONDS)
@Threads(32)
@Fork(1)
@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.SECONDS)
@Slf4j
public class StressTestProvider {private final AnnotationConfigApplicationContext annotationConfigApplicationContext;
private final StressTestController stressTestController;
public StressTestProvider() {
annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AnnotationConfig.class);
annotationConfigApplicationContext.start();
stressTestController = annotationConfigApplicationContext.getBean("stressTestController", StressTestController.class);
}@TearDown
public void close() throws IOException {
annotationConfigApplicationContext.close();
}@Benchmark
public void string1k() {
stressTestController.string1k();
}@Benchmark
public void string100k() {
stressTestController.string100k();
}public static void main(String[] args) throws RunnerException {log.info("测试开始");
Options opt = new OptionsBuilder()
.include(StressTestProvider.class.getSimpleName())
//可以通过注解注入
//.warmupIterations(3)
//.warmupTime(TimeValue.seconds(10))
//报告输出
.result("result.json")
//报告格式
.resultFormat(ResultFormatType.JSON).build();
new Runner(opt).run();
}
}
有两种运行的方式,一般采用打成jar这种。
3.1、main方法运行
如上,只需要 配置Options,运行main方法即可,注意要使用 run模式启动,不要使用debug模式启动。
否则会报错:
transport error 202: connect failed: Connection refused ERROR
3.2、打成jar运行
有时候需要放在服务器上运行,就需要打成一个jar,需要使用单独的jar打包插件:
org.apache.maven.plugins
maven-shade-plugin
2.2
package
shade
jmh-demo
org.openjdk.jmh.Main
如果不想要这种打包方式,打成jar的时候一定要声明main方法入口对应的类,也就是上面StressTestProvider还有就是,因为我的是springboot项目,我测试了一下想同时打包springboot和 jmh:
文章图片
但是运行 jhm-demo.jar 发现报错:not match main class,还是老老实实通过 profile 节点打包吧。
打完包后,通过以下命令即可运行:
java -jar jmh-demo.jar-rf json -rff result.json
-rf json
是输出 json的格式-rff /data/result.json
是输出文件位置和名称4、结果 执行后,会生成一个汇总结果:
Result "com.dubbo.benchmark.StressTestProvider.string1k":
N = 3
mean =0.016 ±(99.9%) 0.022 s/opHistogram, s/op:
[0.014, 0.014) = 0
[0.014, 0.015) = 0
[0.015, 0.015) = 0
[0.015, 0.015) = 1
[0.015, 0.015) = 1
[0.015, 0.016) = 0
[0.016, 0.016) = 0
[0.016, 0.016) = 0
[0.016, 0.016) = 0
[0.016, 0.017) = 0
[0.017, 0.017) = 0
[0.017, 0.017) = 0
[0.017, 0.017) = 1
[0.017, 0.018) = 0
[0.018, 0.018) = 0
[0.018, 0.018) = 0 Percentiles, s/op:
p(0.0000) =0.015 s/op
p(50.0000) =0.015 s/op
p(90.0000) =0.017 s/op
p(95.0000) =0.017 s/op
p(99.0000) =0.017 s/op
p(99.9000) =0.017 s/op
p(99.9900) =0.017 s/op
p(99.9990) =0.017 s/op
p(99.9999) =0.017 s/op
p(100.0000) =0.017 s/op# 第36行
# Run complete. Total time: 00:05:12BenchmarkModeCntScoreErrorUnits
StressTestProvider.string100kthrpt3759.794 ±66.300ops/s
StressTestProvider.string1kthrpt36798.005 ± 6992.093ops/s
StressTestProvider.string100kavgt30.042 ±0.002s/op
StressTestProvider.string1kavgt30.005 ±0.012s/op
StressTestProvider.string100ksample229820.042 ±0.001s/op
StressTestProvider.string100k:string100k·p0.00sample0.017s/op
StressTestProvider.string100k:string100k·p0.50sample0.041s/op
StressTestProvider.string100k:string100k·p0.90sample0.048s/op
StressTestProvider.string100k:string100k·p0.95sample0.050s/op
StressTestProvider.string100k:string100k·p0.99sample0.058s/op
StressTestProvider.string100k:string100k·p0.999sample0.075s/op
StressTestProvider.string100k:string100k·p0.9999sample0.088s/op
StressTestProvider.string100k:string100k·p1.00sample0.092s/opStressTestProvider.string1ksample1869060.005 ±0.001s/op
StressTestProvider.string1k:string1k·p0.00sample0.001s/op
StressTestProvider.string1k:string1k·p0.50sample0.005s/op
StressTestProvider.string1k:string1k·p0.90sample0.007s/op
StressTestProvider.string1k:string1k·p0.95sample0.008s/op
StressTestProvider.string1k:string1k·p0.99sample0.011s/op
StressTestProvider.string1k:string1k·p0.999sample0.030s/op
StressTestProvider.string1k:string1k·p0.9999sample0.035s/op
StressTestProvider.string1k:string1k·p1.00sample0.038s/op
StressTestProvider.string100kss30.030 ±0.181s/op
StressTestProvider.string1kss30.016 ±0.022s/opBenchmark result is saved to result.json
结果分析
简单分析一下:
只需要从第36行开始看,我这里一共压测了2个方法
- StressTestProvider.string100k
- StressTestProvider.string1k
@BenchmarkMode
你选择的测试类型,源码在此:public enum Mode {
/**
* Throughput: operations per unit of time.
*/
Throughput("thrpt", "Throughput, ops/time"),/**
* Average time: average time per per operation.
*
*/
AverageTime("avgt", "Average time, time/op"),/**
* Sample time: samples the time for each operation.
*
*/
SampleTime("sample", "Sampling time"),/**
* Single shot time: measures the time for a single operation.
*
*/
SingleShotTime("ss", "Single shot invocation time"),
thrpt:吞吐量,也可以理解为tps、ops
avgt:每次请求的平均耗时
sample:请求样本数量,这次压测一共发了多少个请求
ss:除去冷启动,一共执行了多少轮
Cnt、Score、Units 单位
Error 误差
如果你配置了输出文件,比如我上面的 resul.json ,但是你打开是看不懂的,可以借助两个网站把文件上传进行分析:
- https://jmh.morethan.io
- http://deepoove.com/jmh-visual-chart
文章图片
汇总:
以上对dubbo进行了分别传输1k和100k的数据压测。
provider机器:
2核4gCentOS release 6.4 (Final)
model name: QEMU Virtual CPU version 2.5+
stepping: 3
cpu MHz: 2099.998
cache size: 4096 KB
JVM:
jdk1.8
-server -Xmx2g -Xms2g -XX:+UseG1GC
dubbo:
版本:2.7.3
序列化:hessian2
使用默认dubbo线程数
压测参数:
32并发
结果:
1k | 100k | |
---|---|---|
TPS | 6700 | 760 |
RTT | 95% 8ms | 95% 50ms |
AVGTime/OP | 5ms | 42ms |
OOM | 无 | 无 |
- jmh压测简单,只需要引入依赖,声明注解
- 准确性高,目前大多数性能压测都是使用jmh
- 缺点就是代码入侵
- dubbo压测的官方代码:https://github.com/apache/dubbo-benchmark
- 主流RPC框架压测代码:https://github.com/hank-whu/rpc-benchmark
- 压测dubbo的源码已上传到github:https://github.com/DogerRain/dubbo-samples-test
- Java资源分享:Java学习路线思维导图+Java学习视频+简历模板+Java电子书
推荐阅读
- Android 编译优化
- 基于WEB快速开发平台的轻量ERP
- 【北亚数据恢复】IBM DS系列存储服务器硬盘故障、映射出错的数据恢复
- Vben-Admin的useForm实现思路详解,以及实现element版的useForm
- 普通二本计算机考研推荐学校江苏,计算机考研,本科内蒙二本,在偏北方有没有推荐的学校(...)
- 程序员|普通二本的辛酸Java面试之路,实战篇
- 二本浙大计算机考研,二本考研逆袭浙大(宝贵经历送给即将上路的你!)
- 程序员|【深度思考】普通二本的辛酸Java面试之路,含答案解析
- 大厂面试系列|我的秋招 | 大厂上岸经验一(末流二本)
- 二本毕业,我是如何逆袭成为BAT年薪40W的Java工程师的()