Перейти к основному содержимому

Сравнение встроенных контейнеров сервлетов в Spring Boot

· 6 мин. чтения

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 никаких дополнительных зависимостей не требуется, поскольку он включен по умолчанию при использовании spring-boot-starter-web .

2.2. пристань

Чтобы использовать 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 идентична 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 Spring Boot как удобный способ как нагрузить систему, так и запросить метрики.

Ознакомьтесь с этой статьей для получения подробной информации об актуаторе. Мы просто добавляем зависимость в наш pom , чтобы сделать его доступным:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2.5. Апачская скамья

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 лишь немного уступал ему.

   | Метрика    | Кот    | пристань    | Отлив   | 
| jvm.memory.used (МБ) | 168 | 155 | 164 |
| jvm.classes.loaded | 9869 | 9784 | 9787 |
| jvm.threads.live | 25 | 17 | 19 |
| Запросов в секунду | 1542 | 1627 | 1650 |
| Среднее время на запрос (мс) | 6.483 | 6.148 | 6.059 |

Обратите внимание, что метрики, естественно, являются репрезентативными для базового проекта; метрики вашего собственного приложения наверняка будут другими.

6. Обсуждение эталона

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

Важно отметить, что измерения производительности, собранные в этом примере, были выполнены с использованием очень специфической рабочей нагрузки, состоящей из HTTP-запросов GET к конечной точке Actuator.

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

Кроме того, более сложное решение для сравнительного анализа, такое как JMeter или Gatling , вероятно, даст более ценную информацию.

7. Выбор контейнера

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

8. Заключение

В этой статье мы рассмотрели реализации контейнеров встроенных сервлетов Tomcat, Jetty и Undertow. Мы изучили характеристики времени выполнения каждого контейнера при запуске с конфигурациями по умолчанию, просмотрев метрики, предоставляемые компонентом Actuator.

Мы выполнили надуманную рабочую нагрузку на работающей системе, а затем измерили производительность с помощью Apache Bench.

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