在spring boot中使用jmh進行性能測試

長期處於CRUD工做中的我忽然有一天關心起本身項目的qps了.便用jmeter測試了訪問量最大的接口,發現只有可憐的17r/s左右......看到網絡上比比皆是的幾百萬qps,我無地自容啊.java

因而就準備進行性能優化了,不過在優化前咱們須要進行性能測試,這樣才能知道優化的效果如何.好比我第一步就是用redis加了緩存,結果測試發現竟然比不加以前還要慢???因此性能測試是很是重要的一環,而jmh就是很是適合的性能測試工具了.redis

準備工做

準備工做很是的簡單,引入jmhmaven包就能夠了.spring

<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

很是簡單的註解,日常測試都有的測試前初始化*測試後清理資源**測試方法*.bash

如何與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就處理完了.

相關文章
相關標籤/搜索