«1. Введение
Растущая популярность облачных приложений и микросервисов порождает повышенный спрос на встроенные контейнеры сервлетов. Spring Boot позволяет разработчикам легко создавать приложения или службы, используя 3 наиболее зрелых доступных контейнера: Tomcat, Undertow и Jetty.
В этом руководстве мы продемонстрируем способ быстрого сравнения реализаций контейнеров с использованием метрик, полученных при запуске и при некоторой нагрузке.
2. Зависимости
Наша настройка для каждой доступной реализации контейнера всегда требует, чтобы мы объявляли зависимость от spring-boot-starter-web в нашем pom.xml.
В общем, мы хотим указать нашего родителя как spring-boot-starter-parent, а затем включить нужные стартеры:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2.1. Tomcat
При использовании Tomcat никаких дополнительных зависимостей не требуется, поскольку он включен по умолчанию при использовании spring-boot-starter-web.
2.2. Jetty
Чтобы использовать Jetty, нам сначала нужно исключить spring-boot-starter-tomcat из spring-boot-starter-web.
Затем мы просто объявляем зависимость от spring-boot-starter-jetty:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
2.3. Undertow
Настройка Undertow идентична Jetty, за исключением того, что мы используем spring-boot-starter-undertow в качестве зависимости:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
2.4. Actuator
Мы будем использовать Actuator Spring Boot как удобный способ как нагрузить систему, так и запросить метрики.
Прочтите эту статью, чтобы узнать больше об актуаторе. Мы просто добавляем зависимость в наш pom, чтобы сделать его доступным:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2.5. Apache Bench
Apache Bench — это утилита для нагрузочного тестирования с открытым исходным кодом, которая поставляется в комплекте с веб-сервером Apache.
Пользователи Windows могут загрузить Apache у одного из сторонних поставщиков, ссылка на который приведена здесь. Если Apache уже установлен на вашем компьютере с Windows, вы сможете найти ab.exe в каталоге apache/bin.
Если вы работаете на машине с Linux, ab можно установить с помощью команды apt-get:
$ apt-get install apache2-utils
3. Показатели запуска
3.1. Коллекция
Чтобы собрать наши метрики запуска, мы зарегистрируем обработчик событий, который будет запускаться в ApplicationReadyEvent Spring Boot.
Мы будем программно извлекать интересующие нас метрики, напрямую работая с MeterRegistry, используемым компонентом Actuator:
@Component
public class StartupEventHandler {
// logger, constructor
private String[] METRICS = {
"jvm.memory.used",
"jvm.classes.loaded",
"jvm.threads.live"};
private String METRIC_MSG_FORMAT = "Startup Metric >> {}={}";
private MeterRegistry meterRegistry;
@EventListener
public void getAndLogStartupMetrics(
ApplicationReadyEvent event) {
Arrays.asList(METRICS)
.forEach(this::getAndLogActuatorMetric);
}
private void processMetric(String metric) {
Meter meter = meterRegistry.find(metric).meter();
Map<Statistic, Double> stats = getSamples(meter);
logger.info(METRIC_MSG_FORMAT, metric, stats.get(Statistic.VALUE).longValue());
}
// other methods
}
Мы избегаем необходимости вручную запрашивать конечные точки REST Actuator или запускать автономную консоль JMX. регистрируя интересные показатели при запуске в нашем обработчике событий.
3.2. Выбор
Существует большое количество метрик, которые Actuator предоставляет из коробки. Мы выбрали 3 метрики, которые помогают получить общий обзор ключевых характеристик времени выполнения после запуска сервера:
-
jvm.memory.used — общий объем памяти, используемый JVM с момента запуска jvm.classes.loaded — общее количество загруженных классов jvm.threads.live — общее количество активных потоков. В нашем тесте это значение можно рассматривать как количество потоков «в состоянии покоя»
4. Показатели времени выполнения
4.1. Коллекция
Помимо предоставления метрик запуска, мы будем использовать конечную точку /metrics, предоставляемую Actuator, в качестве целевого URL-адреса при запуске Apache Bench, чтобы загрузить приложение.
Чтобы протестировать реальное приложение под нагрузкой, мы могли бы вместо этого использовать конечные точки, предоставляемые нашим приложением.
Как только сервер запустится, мы получим командную строку и выполним ab:
ab -n 10000 -c 10 http://localhost:8080/actuator/metrics
В приведенной выше команде мы указали в общей сложности 10 000 запросов с использованием 10 параллельных потоков.
4.2. Отбор
Apache Bench может очень быстро предоставить нам некоторую полезную информацию, включая время соединения и процент запросов, которые обслуживаются в течение определенного времени.
Для наших целей мы сосредоточились на количестве запросов в секунду и времени на запрос (среднее значение).
5. Результаты
При запуске мы обнаружили, что объем памяти для Tomcat, Jetty и Undertow сопоставим с тем, что Undertow требует немного больше памяти, чем два других, а Jetty требует наименьшего объема.
«Для нашего эталонного теста мы обнаружили, что производительность Tomcat, Jetty и Undertow была сопоставима, но Undertow был явно самым быстрым, а Jetty лишь немногим менее быстрым.
Metric | Tomcat | Jetty | Undertow |
---|---|---|---|
jvm.memory.used (MB) | 168 | 155 | 164 |
jvm.classes.loaded | 9869 | 9784 | 9787 |
jvm.threads.live | 25 | 17 | 19 |
Requests per second | 1542 | 1627 | 1650 |
Average time per request (ms) | 6.483 | 6.148 | 6.059 |
Обратите внимание, что метрики, естественно, являются репрезентативными для базового проекта; метрики вашего собственного приложения наверняка будут другими.
6. Обсуждение тестов
Разработка соответствующих тестов производительности для тщательного сравнения реализаций серверов может быть сложной. Чтобы извлечь наиболее важную информацию, очень важно иметь четкое представление о том, что важно для рассматриваемого варианта использования.
Важно отметить, что измерения производительности, собранные в этом примере, были выполнены с использованием очень специфической рабочей нагрузки, состоящей из HTTP-запросов GET к конечной точке Actuator.
Ожидается, что разные рабочие нагрузки, скорее всего, приведут к разным относительным измерениям в разных реализациях контейнеров. Если требуются более надежные или точные измерения, было бы неплохо разработать план тестирования, более точно соответствующий производственному варианту использования.
Кроме того, более сложное решение для сравнительного анализа, такое как JMeter или Gatling, скорее всего, даст более ценную информацию.
7. Выбор контейнера
Выбор правильной реализации контейнера, скорее всего, должен основываться на многих факторах, которые невозможно четко обобщить с помощью одной лишь горстки метрик. Уровень комфорта, функции, доступные параметры конфигурации и политика часто не менее важны, если не более того.
8. Заключение
В этой статье мы рассмотрели реализации встроенных контейнеров сервлетов Tomcat, Jetty и Undertow. Мы изучили характеристики времени выполнения каждого контейнера при запуске с конфигурациями по умолчанию, просмотрев метрики, предоставляемые компонентом Actuator.
Мы выполнили надуманную рабочую нагрузку на работающей системе, а затем измерили производительность с помощью Apache Bench.
Наконец, мы обсудили достоинства этой стратегии и упомянули несколько моментов, о которых следует помнить при сравнении контрольных показателей реализации. Как всегда, весь исходный код можно найти на GitHub.