(八)使用 jmh 压测 Dubbo
1、JMH简介
JMH即Java Microbenchmark Harness,是Java用来做基准测试的一个工具,该工具由OpenJDK提供并维护,测试结果可信度高。
相对于 Jmeter、ab ,它通过编写代码的方式进行压测,在特定场景下会更能评估某项性能。
本次通过使用JMH来压测Dubbo的性能(官方也是使用JMH压测)
2、使用
只需要引用两个jar即可:
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.29</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.29</version>
</dependency>
通过一系列的注解即可使用JMH。
@State
只能用在类上,有三个取值:
Scope.Thread:默认的State,每个测试线程分配一个实例;
Scope.Benchmark:所有测试线程共享一个实例,用于测试有状态实例在多线程共享下的性能;
Scope.Group:每个线程组共享一个实例;
@OutputTimeUnit
时间单位,如毫秒 TimeUnit.MILLISECONDS、秒 TimeUnit.SECONDS
@Benchmark
声明一个public方法为基准测试方法。该类下的所有被@Benchmark注解的方法都会执行。
相当于类的main方法
@BenchmarkMode
指定测试某个接口的指标,如吞吐量、平均执行时间,一般我都是选择 ALL
Mode有:
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打包插件:
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>2.2</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                    <configuration>
                        <finalName>jmh-demo</finalName>
                        <transformers>
                            <transformer
                                         implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                <mainClass>org.openjdk.jmh.Main</mainClass>
                            </transformer>
                            <transformer
                                         implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                        </transformers>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
如果不想要这种打包方式,打成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/op
  Histogram, 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:12
Benchmark                                           Mode     Cnt     Score      Error  Units
StressTestProvider.string100k                      thrpt       3   759.794 ±   66.300  ops/s
StressTestProvider.string1k                        thrpt       3  6798.005 ± 6992.093  ops/s
StressTestProvider.string100k                       avgt       3     0.042 ±    0.002   s/op
StressTestProvider.string1k                         avgt       3     0.005 ±    0.012   s/op
StressTestProvider.string100k                     sample   22982     0.042 ±    0.001   s/op
StressTestProvider.string100k:string100k·p0.00    sample             0.017              s/op
StressTestProvider.string100k:string100k·p0.50    sample             0.041              s/op
StressTestProvider.string100k:string100k·p0.90    sample             0.048              s/op
StressTestProvider.string100k:string100k·p0.95    sample             0.050              s/op
StressTestProvider.string100k:string100k·p0.99    sample             0.058              s/op
StressTestProvider.string100k:string100k·p0.999   sample             0.075              s/op
StressTestProvider.string100k:string100k·p0.9999  sample             0.088              s/op
StressTestProvider.string100k:string100k·p1.00    sample             0.092              s/op
StressTestProvider.string1k                       sample  186906     0.005 ±    0.001   s/op
StressTestProvider.string1k:string1k·p0.00        sample             0.001              s/op
StressTestProvider.string1k:string1k·p0.50        sample             0.005              s/op
StressTestProvider.string1k:string1k·p0.90        sample             0.007              s/op
StressTestProvider.string1k:string1k·p0.95        sample             0.008              s/op
StressTestProvider.string1k:string1k·p0.99        sample             0.011              s/op
StressTestProvider.string1k:string1k·p0.999       sample             0.030              s/op
StressTestProvider.string1k:string1k·p0.9999      sample             0.035              s/op
StressTestProvider.string1k:string1k·p1.00        sample             0.038              s/op
StressTestProvider.string100k                         ss       3     0.030 ±    0.181   s/op
StressTestProvider.string1k                           ss       3     0.016 ±    0.022   s/op
Benchmark result is saved to result.json
结果分析
简单分析一下:
只需要从第36行开始看,我这里一共压测了2个方法
- StressTestProvider.string100k
 - StressTestProvider.string1k
 
Mode
这一列表示测试的名称,也就是 @BenchmarkMode你选择的测试类型,源码在此:
public enum Mode {
    /**
     * <p>Throughput: operations per unit of time.</p>
     */
    Throughput("thrpt", "Throughput, ops/time"),
    /**
     * <p>Average time: average time per per operation.</p>
     *
     */
    AverageTime("avgt", "Average time, time/op"),
    /**
     * <p>Sample time: samples the time for each operation.</p>
     *
     */
    SampleTime("sample", "Sampling time"),
    /**
     * <p>Single shot time: measures the time for a single operation.</p>
     *
     */
    SingleShotTime("ss", "Single shot invocation time"),
thrpt:吞吐量,也可以理解为tps、ops
avgt:每次请求的平均耗时
sample:请求样本数量,这次压测一共发了多少个请求
ss:除去冷启动,一共执行了多少轮
Cnt、Score、Units
单位
Error
误差
如果你配置了输出文件,比如我上面的 resul.json ,但是你打开是看不懂的,可以借助两个网站把文件上传进行分析:

汇总:
以上对dubbo进行了分别传输1k和100k的数据压测。
provider机器:
2核4g
CentOS 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 | 无 | 无 | 
对比了 jmeter、Apache-Benmark(ab)、jmh 这三个压测工具,个人比较推荐使用jmh,原因有:
- 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电子书
(八)使用 jmh 压测 Dubbo的更多相关文章
- Jmeter实现dubbo接口压测案例
		
当前项目中重构了消息服务,需要对消息服务接口做性能压测,评估消息服务的性能情况 通过和开发对接,目前消息服务是通过dubbo接口对内提供服务,所以才有了这边文章的记录 最初的压测这个dubbo接口有三 ...
 - JMeter分布式压测实战(2020年清明假期学习笔记)
		
一.常用压力测试工具对比 简介:目前用的常用测试工具对比 1.loadrunner 性能稳定,压测结果及颗粒度大,可以自定义脚本进行压测,但是太过于重大,功能比较繁多. 2.Apache ab(单接口 ...
 - 基于Dubbo的压测调优实例
		
不久前参与开发了一个基于dubbo分布式框架的底层账单系统,并实现了其中的一部分业务接口,目前需对这些接口进行压测,以评估生产环境所能承受的最大吞吐量.笔者以其中一个查询接口为例来回顾此次压测的整体流 ...
 - Dubbo接口压测
		
在每年的双十一大促之前,除了全链路压测,还需要各个业务方对自己业务提供的核心接口进行单接口压测,以评判系统的稳定性和承压能力. 一.准备工作 环境准备:确保应用性能环境(perf)正常可用 压测接口梳 ...
 - dubbo接口压测工具stresstester使用
		
dubbo接口压测工具stresstester使用 https://blog.csdn.net/u013822349/article/details/79412719
 - jmeter的dubbo压测,依赖jar包要放到执行机的lib/ext下
		
对于jmeter的dubbo压测场景的master-slave结构: 即master的jmeter进行任务的下发和报告的生成,slave进行任务的执行 因为dubbo压测需要依赖很多三方jar包,那么 ...
 - 消息服务dubbo接口性能压测性能优化案例
		
最近项目中的消息服务做了运营商的改动,导致这个服务做了重新开发 压测脚本如下: 开启200线程压测: tps只有200-300之间,平均耗时在700ms左右 开启500线程压测 500并发压测,发现平 ...
 - 压力测试(八)-多节点JMeter分布式压测实战
		
1.Jmeter4.0分布式压测准备工作 简介:讲解Linux服务器上jmeter进行分布式压测的相关准备工作 1.压测注意事项 the firewalls on the systems are tu ...
 - 第八篇 -- 对数据库mysql进行连接并压测
		
参考链接:https://blog.csdn.net/laofashi2015/article/details/81296929 工具:mysql-8.0.12-winx64,apache-jmete ...
 
随机推荐
- 业务4P分析实践
			
原创不易,求分享.求一键三连 前言 假期继续思考BI未来的方向,其实常规的BI规划也不是不能出,比如公司BI看板建设.数据指标体系建设.业务线UE模型数据监控等,但这种不会超出预期的东西自然也能算技术 ...
 - 「SDWC2018 Day1」网格
			
题目当中有三条限制,我们来逐一考虑.对于第一条限制,每次走动的增加量 \(x_i \le M_x, y_i \le M_y\),可以发现一共走的步数是确定的,那么就相当于解这样两个方程组: \[x_1 ...
 - LDAP概念和原理介绍 (转)
			
相信对于许多的朋友来说,可能听说过LDAP,但是实际中对LDAP的了解和具体的原理可能还比较模糊,今天就从"什么是LDAP"."LDAP的主要产品"." ...
 - 模拟dom结构
			
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
 - sms短信网关对接
			
因为工作的需求,需要对接短信网关,业务上就是一个注册用户时,需要发送手机验证码;可能别的公司都是使用第三方接口,但是大点的公司,为了安全,他们都有自己的短信消息中心(SMSC) 1.业务需求 - 1. ...
 - npm 查看一个包的版本信息
			
有了npm 我们能够简单的一段代码就下载我们需要的包,但是包是不断更新的, 所以我们要关注包的版本信息: 现在,假设我们需要 jquery ,但是jquery现在有很多版本,我们如何通过npm查看呢? ...
 - 为什么后台给前台Date是时间戳,而前台给后台则直接是时间字符串?
			
一.因为时间的格式有很多种,不同的页面可能对不同的时间显示需求不同.比如: 05-8-8 上午9:17 2005-8-8 9:17:42 2005年8月8日 上午09时17分42秒 2005年8月8日 ...
 - MySQL数据库授权与索引
			
MySQL数据库授权与索引 目录 MySQL数据库授权与索引 一.数据库用户授权 1. 授予权限 2. 查看权限 3. 删除权限 4. 全部权限(all privileges) 二.MySQL索引 1 ...
 - 洛谷P1563 [NOIP2016 提高组] 玩具谜题
			
题目链接:https://www.luogu.com.cn/problem/P1563 哈哈哈,这个题拿来一读是不是很吃惊hahaha,我刚开始读的时候吓了我一跳,这么长的题干,这么绕的题意,还有下面 ...
 - 以鶸ice为例,手撸一个解释器(一)明确目标
			
代码地址 # HelloWorld.ice print("hello, world") 前言(废话) 其实从开始学习编译原理到现在已经有快半年的时间了,但是其间常常不能坚持看下去龙 ...