Comparando contêineres de servlet incorporados na inicialização do Spring

Comparando contêineres de servlet incorporados na inicialização do Spring

1. Introdução

A crescente popularidade de aplicativos e microsserviços nativos da nuvem gera uma demanda crescente por contêineres de servlets incorporados. O Spring Boot permite que os desenvolvedores criem facilmente aplicativos ou serviços usando os três contêineres mais maduros disponíveis: Tomcat, Undertow e Jetty.

Neste tutorial, demonstraremos uma maneira de comparar rapidamente as implementações de contêiner usando métricas obtidas na inicialização e sob alguma carga.

2. Dependências

Nossa configuração para cada implementação de contêiner disponível sempre exigirá que declaremos uma dependência despring-boot-starter-web em nossopom.xml.

Em geral, queremos especificar nosso pai comospring-boot-starter-parent e, em seguida, incluir os iniciadores que queremos:


    org.springframework.boot
    spring-boot-starter-parent
    2.0.3.RELEASE
    



    
        org.springframework.boot
        spring-boot-starter
    

    
        org.springframework.boot
        spring-boot-starter-web
    

2.1. Tomcat

Nenhuma dependência adicional é necessária ao usar o Tomcat porque ele é incluído por padrão ao usarspring-boot-starter-web.

2.2. Jetty

Para usar o Jetty, primeiro precisamos excluirspring-boot-starter-tomcat despring-boot-starter-web.

Então, simplesmente declaramos uma dependência emspring-boot-starter-jetty:


    org.springframework.boot
    spring-boot-starter-web
    
        
            org.springframework.boot
            spring-boot-starter-tomcat
        
    


    org.springframework.boot
    spring-boot-starter-jetty

2.3. Ressaca

A configuração de Undertow é idêntica a Jetty, exceto que usamosspring-boot-starter-undertow como nossa dependência:


    org.springframework.boot
    spring-boot-starter-web
    
        
            org.springframework.boot
            spring-boot-starter-tomcat
        
    


    org.springframework.boot
    spring-boot-starter-undertow

2.4. Atuador do

Usaremos o atuador Spring Boot como uma maneira conveniente de estressar o sistema e consultar as métricas.

Verifiquethis article para obter detalhes sobre o atuador. Simplesmente adicionamos uma dependência em nossopom para torná-lo disponível:


    org.springframework.boot
    spring-boot-starter-actuator

 2.5. Apache Bench

Apache Bench é um utilitário de teste de carga de código aberto que vem com o servidor da web Apache.

Os usuários do Windows podem baixar o Apache de um dos fornecedores terceirizados vinculados ahere. Se o Apache já estiver instalado em sua máquina Windows, você poderá encontrarab.exe em seu diretórioapache/bin.

Se você estiver em uma máquina Linux,ab pode ser instalado usandoapt-get com:

$ apt-get install apache2-utils

3. Métricas de inicialização

3.1. Coleção

A fim de coletar nossas métricas de inicialização, registraremos um manipulador de eventos para disparar emApplicationReadyEvent do Spring Boot.

Extrairemos programaticamente as métricas nas quais estamos interessados ​​trabalhando diretamente com oMeterRegistry usado pelo componente Atuador:

@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 stats = getSamples(meter);

        logger.info(METRIC_MSG_FORMAT, metric, stats.get(Statistic.VALUE).longValue());
    }

    // other methods
}

Evitamos a necessidade de consultar manualmente os pontos de extremidade REST do Actuator ou de executar um console JMX independente, registrando métricas interessantes na inicialização no nosso manipulador de eventos.

3.2. Seleção

Há um grande número de métricas fornecidas pelo Actuator. Selecionamos três métricas que ajudam a obter uma visão geral de alto nível das principais características do tempo de execução quando o servidor estiver ativo:

  • jvm.memory.used - a memória total usada pela JVM desde a inicialização

  • jvm.classes.loaded - o número total de classes carregadas

  • jvm.threads.live - o número total de threads ativos. Em nosso teste, esse valor pode ser visto como a contagem de threads "em repouso"

4. Métricas de tempo de execução

4.1. Coleção

Além de fornecer métricas de inicialização, usaremos o ponto de envio/metrics exposto pelo Atuador como o URL de destino quando executamos o Apache Bench para colocar o aplicativo sob carga.

Para testar um aplicativo real sob carga, podemos usar pontos de extremidade fornecidos por nosso aplicativo.

Assim que o servidor for iniciado, obteremos um prompt de comando e executaremosab:

ab -n 10000 -c 10 http://localhost:8080/actuator/metrics

No comando acima, especificamos um total de 10.000 solicitações usando 10 threads simultâneos.

4.2. Seleção

O Apache Bench pode nos fornecer rapidamente informações úteis, incluindo o tempo de conexão e a porcentagem de solicitações que são atendidas dentro de um determinado período.

Para nossos propósitos,we focused on requests-per-second and time-per-request (mean).

5. Resultados

Na inicialização, descobrimos quethe memory footprint of Tomcat, Jetty, and Undertow was comparable com o Undertow exigindo um pouco mais de memória do que os outros dois e o Jetty exigindo a menor quantidade.

Para nosso benchmark, descobrimos quethe performance of Tomcat, Jetty, and Undertow was comparable, mas queUndertow was clearly the fastest and Jetty only slightly less fast. 

Métrica

Tomcat

Jetty

Ressaca

jvm.memory.used (MB)

168

155

164

jvm.classes.loaded

9869

9784

9787

jvm.threads.live

25

17

19

Pedidos por segundo

1542

1627

1650

Tempo médio por solicitação (ms)

6.483

6.148

6.059

Observe que as métricas são, naturalmente, representativas do projeto básico; as métricas de seu próprio aplicativo certamente serão diferentes.

6. Discussão de benchmark

O desenvolvimento de testes de benchmark apropriados para realizar comparações completas de implementações de servidor pode ser complicado. Para extrair as informações mais relevantes,it’s critical to have a clear understanding of what’s important for the use case in question.

É importante observar que as medições de benchmark coletadas neste exemplo foram tomadas usando uma carga de trabalho muito específica que consiste em solicitações HTTP GET para um endpoint do Atuador.

It’s expected that different workloads would likely result in different relative measurements across container implementations. Se fossem necessárias medições mais robustas ou precisas, seria uma boa idéia configurar um plano de teste que correspondesse melhor ao caso de uso da produção.

Além disso, uma solução de benchmarking mais sofisticada, comoJMeter ouGatling, provavelmente produziria insights mais valiosos.

7. Escolhendo um container

Selecting the right container implementation should likely be based on many factors that can’t be neatly summarized with a handful of metrics alone. Nível de conforto, recursos, opções de configuração disponíveis e política geralmente são igualmente importantes, se não mais.

8. Conclusão

Neste artigo, vimos as implementações de contêineres de servlets incorporados Tomcat, Jetty e Undertow. Examinamos as características de tempo de execução de cada contêiner na inicialização com as configurações padrão, observando as métricas expostas pelo componente Atuador.

Executamos uma carga de trabalho artificial no sistema em execução e medimos o desempenho usando o Apache Bench.

Por fim, discutimos os méritos dessa estratégia e mencionamos algumas coisas a serem lembradas ao comparar os benchmarks de implementação. Como sempre, todo o código-fonte pode ser encontradoover on GitHub.