Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

在spring boot中使用jmh进行性能测试 #18

Open
Yuicon opened this issue Nov 28, 2019 · 0 comments
Open

在spring boot中使用jmh进行性能测试 #18

Yuicon opened this issue Nov 28, 2019 · 0 comments

Comments

@Yuicon
Copy link
Owner

Yuicon commented Nov 28, 2019

长期处于CRUD工作中的我突然有一天关心起自己项目的qps了.便用jmeter测试了访问量最大的接口,发现只有可怜的17r/s左右......看到网络上比比皆是的几百万qps,我无地自容啊.

于是就准备进行性能优化了,不过在优化前我们需要进行性能测试,这样才能知道优化的效果如何.比如我第一步就是用redis加了缓存,结果测试发现居然比不加之前还要慢???所以性能测试是非常重要的一环,而jmh就是非常适合的性能测试工具了.

准备工作

准备工作非常的简单,引入jmhmaven包就可以了.

        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-core</artifactId>
            <version>1.22</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-generator-annprocess</artifactId>
            <version>1.22</version>
            <scope>provided</scope>
        </dependency>

第一个例子

package jmh;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

/**
 * Benchmark
 *
 * @author wangpenglei
 * @since 2019/11/27 13:54
 */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class Benchmark {

    public static void main(String[] args) throws Exception {
        // 使用一个单独进程执行测试,执行5遍warmup,然后执行5遍测试
        Options opt = new OptionsBuilder().include(Benchmark.class.getSimpleName()).forks(1).warmupIterations(5)
                .measurementIterations(5).build();
        new Runner(opt).run();
    }

    @Setup
    public void init() {
   
    }

    @TearDown
    public void down() {

    }

    @org.openjdk.jmh.annotations.Benchmark
    public void test() {

    }

}

@BenchmarkMode

这个注解决定了测试模式,具体的内容网络上非常多,我这里采用的是计算平均运行时间

@OutputTimeUnit(TimeUnit.MILLISECONDS)

这个注解是最后输出结果时的单位.因为测试的是接口,所以我采用的是毫秒.如果是测试本地redis或者本地方法这种可以换更小的单位.

@State(Scope.Benchmark)

这个注解定义了给定类实例的可用范围,因为spring里的bean默认是单例,所以我这里采用的是运行相同测试的所有线程将共享实例。可以用来测试状态对象的多线程性能(或者仅标记该范围的基准).

@setup @teardown @benchmark

非常简单的注解,平常测试都有的测试前初始化*测试后清理资源**测试方法*.

如何与spring共同使用

因为我们需要spring的环境才能测试容器里的bean,所以需要在初始化方法中手动创建一个.我查了一下资料没发现什么更好的方法,就先自己手动创建吧.

    private ConfigurableApplicationContext context;
    private AppGoodsController controller;

    @Setup
    public void init() {
        // 这里的WebApplication.class是项目里的spring boot启动类
        context = SpringApplication.run(WebApplication.class);
        // 获取需要测试的bean
        this.controller = context.getBean(AppGoodsController.class);
    }

    @TearDown
    public void down() {
        context.close();
    }

开始测试

写好测试方法后启动main方法就开始测试了,现在会报一些奇奇怪怪的错误,不过不影响结果我就没管了.运行完成后会输出结果,这时候可以对比下优化的效果.

Result "jmh.Benchmark.testGetGoodsList":
  65.969 ±(99.9%) 10.683 ms/op [Average]
  (min, avg, max) = (63.087, 65.969, 69.996), stdev = 2.774
  CI (99.9%): [55.286, 76.652] (assumes normal distribution)


# Run complete. Total time: 00:02:48

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.

Benchmark                   Mode  Cnt   Score    Error  Units
Benchmark.testGetGoodsList  avgt    5  65.969 ± 10.683  ms/op

Process finished with exit code 0

开头的负优化

在文章开头我讲了一个负优化的例子,我用redis加了缓存后居然比直接数据库查询还要慢!其实原因很简单,我在本地电脑上测试,连接的redis却部署在服务器上.这样来回公网的网络延迟就已经很大了.不过数据库也是通过公网的,也不会比redis快才对.最后的原因是发现部署redis的服务器带宽只有1m也就是100kb/s,很容易就被占满了.最后优化是redis加缓存与使用内网连接redis.

优化结果

优化前速度:

Result "jmh.Benchmark.testGetGoodsList":
  102.419 ±(99.9%) 153.083 ms/op [Average]
  (min, avg, max) = (65.047, 102.419, 162.409), stdev = 39.755
  CI (99.9%): [≈ 0, 255.502] (assumes normal distribution)


# Run complete. Total time: 00:03:03

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.

Benchmark                   Mode  Cnt    Score     Error  Units
Benchmark.testGetGoodsList  avgt    5  102.419 ± 153.083  ms/op

Process finished with exit code 0

优化后速度(为了模拟内网redis速度,连的是本地redis):

Result "jmh.Benchmark.testGetGoodsList":
  29.210 ±(99.9%) 2.947 ms/op [Average]
  (min, avg, max) = (28.479, 29.210, 30.380), stdev = 0.765
  CI (99.9%): [26.263, 32.157] (assumes normal distribution)


# Run complete. Total time: 00:02:49

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.

Benchmark                   Mode  Cnt   Score   Error  Units
Benchmark.testGetGoodsList  avgt    5  29.210 ± 2.947  ms/op

Process finished with exit code 0

可以看到大约快了3.5倍,其实还有优化空间,全部数据库操作都通过redis缓存的话,大概1ms就处理完了.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant