1. 程式人生 > 程式設計 >在spring boot中使用jmh進行效能測試

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

長期處於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,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,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,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,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就處理完了.