Статьи

Микросервисы для разработчиков Java: тестирование производительности и нагрузки

1. Введение

В наши дни многочисленные фреймворки и библиотеки позволяют довольно легко перейти от буквально ничего к полноценному запущенному приложению или услуге за считанные часы. Это действительно удивительно, и вы можете полностью сойти с этого, но чаще всего решения, которые фреймворки принимают от вашего имени (часто называемые «чувствительными значениями по умолчанию»), далеки от оптимальных (или даже достаточных) в контексте конкретного приложения. или услуга (и, по правде говоря, вряд ли можно придумать решение « один размер подходит всем» ).

В этом разделе руководства мы поговорим о тестировании производительности и нагрузке, сосредоточив внимание на инструментах, которые помогут вам в достижении ваших целей, а также выделят типичные области настройки приложения. Стоит отметить, что некоторые методы могут применяться к одному отдельному микросервису, но в большинстве случаев акцент должен быть направлен на весь ансамбль архитектуры микросервисов .

Достаточно часто термины «производительность» и «нагрузочное тестирование» используются взаимозаменяемо, однако это ошибочное мнение. Это правда, что эти методы тестирования часто встречаются вместе, но каждый из них ставит разные цели. Тестирование производительности помогает оценить скорость тестируемой системы, а нагрузочное тестирование — понять пределы тестируемой системы. Эти ответы очень чувствительны к контексту, в котором работает система, поэтому всегда рекомендуется разрабатывать моделирование как можно ближе к условиям производства.

Методология тестирования производительности и нагрузки не включена в эту часть руководства, поскольку в ней слишком много всего, чтобы ее охватить. Я настоятельно рекомендую книгу Брендана Грегга « Производительность систем: предприятие и облако », чтобы глубоко понять аспекты производительности и масштабируемости всего программного стека.

2. Подружитесь с JVM и GC

Успех Java во многом обязан его времени выполнения ( JVM ) и автоматическому управлению памятью ( GC ). За эти годы JVM превратилась в очень сложную технологию, на которой строится множество вещей. Вот почему его часто называют «платформой JVM».

Существует две основные реализации JVM с открытым исходным кодом: HotSpot и Eclipse OpenJ9 . Честно говоря, HotSpot занимает доминирующее положение, но Eclipse OpenJ9 выглядит весьма перспективным для определенных видов приложений. Картина была бы неполной без упоминания GraalVM , высокопроизводительной виртуальной машины с полиглотом, основанной на SubstrateVM . Выбор правильной JVM может быть легкой победой с самого начала.

Что касается управления памятью и сборкой мусора ( GC ), все гораздо сложнее. В зависимости от версии JDK (8 или 11) и поставщика, мы говорим о Serial GC , Parallel GC , CMS , G1 , ZGC и Shenandoah . В выпуске JDK 11 был представлен экспериментальный Epsilon GC , который по сути является бездействующим GC .

Настройка GCэто искусство, требующее глубокого понимания того, как работает JVM . Парк анатомии JVM является одним из лучших и современных источников бесценного понимания внутренних органов JVM и GC . Но как бы вы диагностировали проблемы в ваших приложениях и действительно выяснили, что настраивать?

К счастью, теперь это возможно с помощью двух замечательных инструментов, Java Mission Control и Java Flight Recorder , которые были открыты с открытым исходным кодом начиная с выпуска JDK 11. Эти инструменты доступны только для виртуальной машины HotSpot и исключительно просты в использовании даже на производстве.

И последнее, но не менее важное: давайте немного поговорим о том, как контейнеризация (или, лучше сказать, докеризация) влияет на поведение JVM. После обновления 191 JDK 10 и JDK 8 JVM была изменена, чтобы полностью осознавать, что она работает в контейнере Docker и может правильно извлекать выделенное количество процессоров и общий объем памяти.

3. Микробенчмарки

Адаптировать настройки GC и JVM к потребностям ваших приложений и сервисов сложно, но это полезно. Однако, скорее всего, это не поможет, когда JVM наткнется на неэффективный код. Чаще всего реализация должна быть переписана с нуля или изменена, но как убедиться, что она превосходит старую? Методы микробенчмаркинга, поддерживаемые инструментом JHM , помогут вам.

JMH — это комплект Java для создания, запуска и анализа нано / микро / милли / макро тестов, написанных на Java и других языках, предназначенных для JVM. https://openjdk.java.net/projects/code-tools/jmh/

Вы можете быть удивлены, зачем использовать для этого специальный инструмент? В двух словах, бенчмаркинг выглядит просто, просто запустите соответствующий код в цикле и измерьте время, верно? На самом деле, написание тестов, которые должным образом измеряют производительность достаточно небольших частей приложения, очень сложно, особенно когда речь идет о JVM. Существует много оптимизаций, которые JVM могла бы применить, принимая во внимание гораздо меньшую область применения изолированных фрагментов кода, которые сравниваются. Это основная причина, по которой вам нужны такие инструменты, как JHM, который знает о поведении JVM и направляет вас к правильной реализации и выполнению эталонного теста, чтобы вы могли получить результаты измерений, которым можно доверять.

В репозитории JHM есть большое количество примеров, которые можно посмотреть и получить самостоятельно, но если вы хотите узнать больше по этому вопросу, Оптимизация Java: практические приемы повышения производительности приложений JVM Бенджамина Дж. Эванса , Джеймса Гофа и Криса Ньюланда потрясающая книга для изучения.

Как только вы освоите JHM и начнете использовать его изо дня в день, сравнение микробенчмарков может стать утомительным процессом. JMH Compare GUI — это небольшой графический инструмент, который поможет вам визуально сравнить эти результаты.

4. Apache JMeter

Давайте переключимся с микро- на макробенчмаркинг и поговорим об измерении производительности приложений и сервисов, развернутых где-то. Первый инструмент, который мы рассмотрим, это Apache JMeter , вероятно, один из старейших инструментов в этой категории.

Приложение Apache JMeter — это программное обеспечение с открытым исходным кодом, 100% чистое Java-приложение, предназначенное для загрузки функциональных возможностей тестирования и измерения производительности. Первоначально он был разработан для тестирования веб-приложений, но с тех пор расширился до других функций тестирования. https://jmeter.apache.org/

Apache JMeter поддерживает подход на основе пользовательского интерфейса для создания и управления довольно сложными планами тестирования. Сам интерфейс довольно интуитивно понятен, и ваш первый сценарий не займет много времени. Одна из сильных сторон Apache JMeter — высокий уровень расширяемости и поддержки сценариев.

Служба бронирования является ядром платформы JCG Car Rentals , поэтому на приведенном ниже снимке экрана показан краткий обзор простого плана тестирования по API бронирования RESTful .

Наличие удобного интерфейса отлично подходит для людей, но не для автоматизированного инструментария. К счастью, планы тестирования Apache JMeter можно запускать из командной строки , используя плагин Apache Maven, плагин Gradle или даже встраивать их в жгут тестов приложения .

Возможность простого внедрения в конвейеры непрерывной интеграции делает Apache JMeter идеальным решением для разработки сценариев автоматического тестирования нагрузки и производительности.

5. Гатлинг

Существует довольно много платформ для нагрузочного тестирования, которые продвигают подход, основанный на коде, к сценариям тестирования, и Gatling является одним из лучших примеров.

Gatling — это очень мощный инструмент для нагрузочного тестирования. Он разработан для простоты использования, удобства обслуживания и высокой производительности. https://gatling.io/docs/current/

Сценарии Гатлинга написаны на Scala, но этот аспект абстрагируется от краткого DSL , поэтому знание Scala желательно, но не обязательно. Давайте повторно реализуем тестовый сценарий Apache JMeter для службы резервирования, используя подход Gatling code-first.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class ReservationSimulation extends Simulation {
  val tokens: Map[String, String] = TrieMap[String, String]()
  val customers = csv("customers.csv").circular()
   
  val protocol = http
    .baseUrl("http://localhost:17000")
    .contentTypeHeader("application/json")
 
  val reservation = scenario("Simulate Reservation")
    .feed(customers)
    .doIfOrElse(session => tokens.get(session("username").as[String]) == None) {
      KeycloakToken
        .token
        .exec(session => {
          tokens.replace(session("username").as[String], session("token").as[String])
          session
        })
    } {
      exec(session => {       
        tokens.get(session("username").as[String]).fold(session)(session.set("token", _))
      })
    }
    .exec(
      http("Reservation Request")
        .post("/reservations")
        .header("Authorization", "Bearer ${token}")
        .body(ElFileBody("reservation-payload.json")).asJson
        .check(status.is(201)))
 
  setUp(
    reservation.inject(rampUsers(10) during (20 seconds))
  ).protocols(protocol)
}

Тестовый сценарий, или с точки зрения Гатлинга , симуляции, довольно легко следовать. Небольшое осложнение связано с необходимостью получения токена доступа с помощью API Keycloak, но существует несколько способов его решения. В приведенном выше моделировании мы сделали это частью потока резервирования, поддерживаемого кэшем токенов в памяти. Как вы могли видеть, сложные, многошаговые симуляции выглядят легко в Gatling .

Отчетность Гатлинга действительно удивительна. Из коробки вы получаете результаты моделирования в красивой HTML- разметке, изображение ниже — лишь небольшой фрагмент. Вы также можете извлечь эти данные из файла журнала симуляции и интерпретировать их так, как вам нужно.

Тестирование производительности и нагрузки - отчет Гатлинга
Гатлинг Репор

С самого начала Gatling был разработан для непрерывного нагрузочного тестирования и очень хорошо интегрируется с Apache Maven , SBT , Gradle и конвейерами непрерывной интеграции . Существует множество расширений для поддержки широкого спектра протоколов (и вы, несомненно, можете внести свой вклад).

6. Инструменты командной строки

Инструменты командной строки, вероятно, являются самым быстрым и понятным способом увеличить нагрузку на ваши службы и быстро получить столь необходимую обратную связь. Мы собираемся начать с Apache Bench (более известного как ab ), инструмента для сравнения сервисов и приложений на основе HTTP .

Например, тот же сценарий для службы резервирования, который мы видели в предыдущих разделах, можно подвергнуть нагрузочному тестированию с использованием ab , предполагая, что токен безопасности был получен ранее.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
$ ab -c 5 -n 1000 -H "Authorization: Bearer $TOKEN" -T "application/json" -p reservation-payload.json  http://localhost:17000/reservations
 
This is ApacheBench, Version 2.3
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
 
Benchmarking localhost (be patient)
...
Completed 1000 requests
Finished 1000 requests
 
Server Software:
Server Hostname:        localhost
Server Port:            17000
 
Document Path:          /reservations
Document Length:        0 bytes
 
Concurrency Level:      5
Time taken for tests:   22.785 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      487000 bytes
Total body sent:        1836000
HTML transferred:       0 bytes
Requests per second:    43.89 [#/sec] (mean)
Time per request:       113.925 [ms] (mean)
Time per request:       22.785 [ms] (mean, across all concurrent requests)
Transfer rate:          20.87 [Kbytes/sec] received
                        78.69 kb/s sent
                        99.56 kb/s total
 
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.4      0       1
Processing:     6  113 449.4     27    4647
Waiting:        5  107 447.7     19    4645
Total:          6  114 449.4     28    4648
 
Percentage of the requests served within a certain time (ms)
  50%     28
  66%     52
  75%     57
  80%     62
  90%     83
  95%    326
  98%   1024
  99%   2885
 100%   4648 (longest request)

Когда простота ab становится ограничителем показа, вы можете взглянуть на wrk , современный инструмент для тестирования производительности HTTP . Он имеет мощную поддержку сценариев, созданную Lua , и способен моделировать сложные сценарии загрузки.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
$ wrk -s reservation.lua -d60s -c50 -t5 --latency -H "Authorization: Bearer $TOKEN" http://localhost:17000/reservations
 
Running 1m test @ http://localhost:17000/reservations
  5 threads and 50 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   651.87ms   93.89ms   1.73s    85.20%
    Req/Sec    16.94     10.00    60.00     71.02%
  Latency Distribution
     50%  627.14ms
     75%  696.23ms
     90%  740.52ms
     99%    1.02s
  4579 requests in 1.00m, 2.04MB read
Requests/sec:     76.21
Transfer/sec:     34.83KB

Если вы не хотите использовать скрипты, есть еще один замечательный вариант, который стоит упомянуть: vegeta , инструмент для нагрузочного тестирования HTTP (и библиотека). Он имеет огромное количество функций и даже включает в себя готовые графики.

1
$ echo "POST http://localhost:17000/reservations" | vegeta attack -duration=60s -rate=20 -header="Authorization: Bearer $TOKEN" -header="Content-Type: application/json" -body reservation-payload.json > results.bin

Как только соответствующие результаты нагрузочного теста сохранены (в нашем случае в файле results.bin ), их можно легко преобразовать в текстовый отчет:

01
02
03
04
05
06
07
08
09
10
$ cat results.bin | vegeta report
 
Requests      [total, rate]            1200, 20.01                                                       
Duration      [total, attack, wait]    59.9714976s, 59.9617223s, 9.7753ms                                
Latencies     [mean, 50, 95, 99, max]  26.286524ms, 9.424435ms, 104.754362ms, 416.680833ms, 846.8242ms   
Bytes In      [total, mean]            0, 0.00                                                           
Bytes Out     [total, mean]            174000, 145.00                                                    
Success       [ratio]                  100.00%                                                           
Status Codes  [code:count]             201:1200                                                          
Error Set:                                                                                               

Или просто преобразовать в графическое представление диаграммы:

1
$ cat results.bin | vegeta plot
Тестирование производительности и нагрузки - Vegeta Charts
Vegeta Charts

Как мы уже видели, каждый из этих инструментов командной строки соответствует различным потребностям, которые вы можете иметь в виду для конкретного сценария нагрузки или производительности. Хотя есть много других , эти три — довольно безопасный выбор.

7. Как насчет gRPC? HTTP / 2? TCP?

Все инструменты, о которых мы говорили до сих пор, поддерживают тестирование производительности веб-сервисов и API на основе HTTP с самого начала. Но как насчет того, чтобы подчеркнуть сервисы, которые используют gRPC , HTTP / 2 или даже простые старые протоколы UDP ?

Хотя волшебного швейцарского армейского ножа пока не существует, конечно, есть несколько вариантов. Например, Gatling имеет встроенную поддержку HTTP / 2 начиная с версии 3.0.0 , тогда как gRPC и UDP поддерживаются расширениями сообщества. С другой стороны, vegeta имеет поддержку HTTP / 2, тогда как Apache JMeter поддерживает SMTP , FTP и TCP .

Если вдаваться в подробности, существует официальное руководство по бенчмаркингу gRPC, в котором кратко излагаются инструменты бенчмаркинга производительности, сценарии, рассмотренные тестами, и инфраструктура тестирования для сервисов на основе gRPC .

8. Больше инструментов вокруг нас

Помимо инструментов и платформ, которые мы обсуждали до сих пор, стоит упомянуть несколько других вариантов, которые хороши, но, возможно, не являются родными для разработчиков Java. Первый — это Locust , простая в использовании, распределенная, масштабируемая среда нагрузочного тестирования, написанная на Python . Второй — Tsung , инструмент для распределенного нагрузочного тестирования с открытым исходным кодом, написанный на Erlang .

Одним из многообещающих проектов, за которыми стоит следить, является Test Armada , парк инструментов, позволяющий разработчикам внедрять автоматизацию качества в масштабе, который также планирует внедрить поддержку тестирования производительности (на основе Apache JMeter ).

И будет нечестно закончить, не говоря уже о Grinder , одной из самых ранних сред нагрузочного тестирования Java, которая позволяет легко выполнять распределенный тест с использованием множества машин с инжектором нагрузки. К сожалению, проект кажется мертвым, без каких-либо признаков развития за последние несколько лет.

9. Микросервисы для разработчиков Java: тестирование производительности и нагрузки — Выводы

В этой части руководства мы поговорили об инструментах, методах и средах для тестирования производительности и нагрузки, особенно в контексте платформы JVM. Очень важно уделить достаточно времени, заранее поставить цели и разработать реалистичные сценарии производительности и нагрузочного тестирования. Сама по себе это дисциплина, но самое главное, результаты этих симуляций могут помочь владельцам сервисов правильно сформировать многие аспекты SLA, которые мы обсуждали ранее .

10. Что дальше

В следующем разделе этого руководства мы собираемся завершить обсуждение, связанное с тестированием микросервисов , сосредоточив внимание на инструментах для тестирования безопасности.

Образцы и источники для этого раздела доступны для скачивания здесь .