последовательность
Я давно работаю в CRUD и вдруг однажды мне небезразличен мой проект.qps
готовые к использованиюjmeter
Протестировали самый посещаемый интерфейс и обнаружили, что только плохой17r/s
Слева и справа... смотри, миллионы в сетиqps
, мне стыдно.
Итак, мы готовы выполнить оптимизацию производительности, но нам нужно выполнить тестирование производительности перед оптимизацией, чтобы узнать, каков эффект оптимизации.Например, мой первый шаг — использоватьredis
После добавления кеша результат теста показал, что он на самом деле медленнее, чем до того, как его не добавили??? Так что тест производительности - очень важная часть, иjmh
Это очень удобный инструмент для тестирования производительности.
Готов к работе
Подготовительная работа очень проста, введениеjmh
изmaven
Просто пакет.
<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
среду, чтобы протестировать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
Это сделано.