Acelere o tempo de inicialização do Spring Boot


115

Eu tenho um aplicativo Spring Boot. Eu adicionei muitas dependências (infelizmente, parece que preciso de todas elas) e o tempo de inicialização aumentou bastante. Basta fazer isso SpringApplication.run(source, args)leva 10 segundos.

Embora isso possa não ser muito comparado ao que estamos "acostumados", estou infeliz que demore tanto, principalmente porque quebra o fluxo de desenvolvimento. O aplicativo em si é bastante pequeno neste ponto, então presumo que a maior parte do tempo esteja relacionado às dependências adicionadas, não às próprias classes do aplicativo.

Presumo que o problema seja a digitalização do classpath, mas não tenho certeza de como:

  • Confirme se é o problema (ou seja, como "depurar" o Spring Boot)
  • Se for realmente a causa, como posso limitá-la para que fique mais rápida? Por exemplo, se eu souber que alguma dependência ou pacote não contém nada que o Spring deva verificar, há uma maneira de limitar isso?

Presumo que aprimorar o Spring para ter inicialização de bean paralelo durante a inicialização aceleraria as coisas, mas essa solicitação de aprimoramento está aberta desde 2011, sem nenhum progresso. Vejo alguns outros esforços no próprio Spring Boot, como as melhorias de velocidade do Investigue Tomcat JarScanning , mas isso é específico do Tomcat e foi abandonado.

Este artigo:

embora voltado para testes de integração, sugere o uso lazy-init=true, porém não sei como aplicar isso a todos os beans do Spring Boot usando a configuração Java - alguma indicação aqui?

Quaisquer (outras) sugestões serão bem-vindas.


Publique o seu código. Normalmente, apenas o pacote em que o executor do aplicativo está definido é verificado. Se você tiver outros pacotes definidos, @ComponentScaneles também serão verificados. Outra coisa é certificar-se de que você não habilitou o registro de depuração ou rastreamento, pois geralmente o registro é lento, muito lento.
M. Deinum

Se você usar o Hibernate, ele também tende a demorar bastante no início do aplicativo.
Knut Forkalsrud de

A ligação automática do Spring por tipo, juntamente com os beans de fábrica, tem o potencial de ser lenta quando você adiciona muitos beans e dependências.
Knut Forkalsrud

Ou você pode usar o cache, spring.io/guides/gs/caching
Cassian

2
Obrigado a todos pelos comentários - infelizmente não seria capaz de postar o código (muitos jars internos), no entanto, ainda estou procurando uma maneira de depurar isso. Sim, posso estar usando A ou B ou fazendo X ou Y, o que diminui a velocidade. Como faço para determinar isso? Se eu adicionar uma dependência X, que tem 15 dependências transitivas, como saberei qual dessas 16 a tornou mais lenta? Se eu conseguir descobrir, há algo que eu possa fazer mais tarde para impedir que Spring os examine? Dicas como essa seriam úteis!
chuva constante de

Respostas:


61

O Spring Boot faz várias configurações automáticas que podem não ser necessárias. Portanto, você pode querer restringir apenas a configuração automática necessária para o seu aplicativo. Para ver a lista completa de autoconfiguração incluída, apenas execute o log de org.springframework.boot.autoconfigureno modo DEBUG ( logging.level.org.springframework.boot.autoconfigure=DEBUGem application.properties). Outra opção é executar o aplicativo Spring Boot com a --debugopção:java -jar myproject-0.0.1-SNAPSHOT.jar --debug

Haveria algo assim na saída:

=========================
AUTO-CONFIGURATION REPORT
=========================

Inspecione esta lista e inclua apenas as configurações automáticas de que você precisa:

@Configuration
@Import({
        DispatcherServletAutoConfiguration.class,
        EmbeddedServletContainerAutoConfiguration.class,
        ErrorMvcAutoConfiguration.class,
        HttpEncodingAutoConfiguration.class,
        HttpMessageConvertersAutoConfiguration.class,
        JacksonAutoConfiguration.class,
        ServerPropertiesAutoConfiguration.class,
        PropertyPlaceholderAutoConfiguration.class,
        ThymeleafAutoConfiguration.class,
        WebMvcAutoConfiguration.class,
        WebSocketAutoConfiguration.class,
})
public class SampleWebUiApplication {

O código foi copiado desta postagem do blog .


1
você mediu isso ?? Foi muito mais rápido ?? Na minha opinião, este é um caso excepcional, muito mais importante para garantir que o cache de contexto de teste do Spring funcione
idmitriev

@idmitriev Acabei de medir isso em meu aplicativo e ele inicializou em 53 segundos, em comparação a 73 segundos sem excluir as classes de configuração automática. Eu excluí muito mais classes do que as listadas acima.
apkisbossin

É bom importar todas as configurações. Como lidar com BatchConfigurerConfiguration.JpaBatchConfiguration, caso se acrescente a dependência ao projeto? Como lidar com métodos referenciados como ConfigurationPropertiesRebinderAutoConfiguration # configurationPropertiesBeans?
user1767316

Como lidar com classes de configuração privada?
user1767316

44

A resposta mais votada até agora não está errada, mas não vai com a profundidade que gosto de ver e não fornece nenhuma evidência científica. A equipe do Spring Boot fez um exercício para reduzir o tempo de inicialização do Boot 2.0, e o tíquete 11226 contém muitas informações úteis. Também há um tíquete 7939 aberto para adicionar informações de tempo para avaliação de condição, mas não parece ter um ETA específico.

A abordagem mais útil e metódica para depurar a inicialização do Boot foi feita por Dave Syer. https://github.com/dsyer/spring-boot-startup-bench

Eu também tinha um caso de uso semelhante, então peguei a abordagem de Dave de micro-benchmarking com JMH e executei-a. O resultado é o projeto boot-benchmark . Eu o projetei de forma que possa ser usado para medir o tempo de inicialização de qualquer aplicativo Spring Boot, usando o jar executável produzido pela tarefa Gradle bootJar(anteriormente chamada bootRepackagede Boot 1.5). Sinta-se à vontade para usá-lo e fornecer feedback.

Minhas descobertas são as seguintes:

  1. A CPU é importante. Muito.
  2. Iniciar a JVM com -Xverify: none ajuda significativamente.
  3. Excluir autoconfigurações desnecessárias ajuda.
  4. Dave recomendou o argumento JVM -XX: TieredStopAtLevel = 1 , mas meus testes não mostraram melhorias significativas com isso. Além disso, -XX:TieredStopAtLevel=1provavelmente retardaria sua primeira solicitação.
  5. Houve relatos de lentidão na resolução de nomes de host, mas não achei que fosse um problema para os aplicativos que testei.

1
@ user991710 Não sei como quebrou, mas agora está consertado. Obrigado pelo relatório.
Abhijit Sarkar

2
Para adicionar a isso, você poderia adicionar um exemplo de como alguém pode usar seu benchmark com um aplicativo personalizado? Ele precisa ser adicionado como um projeto semelhante minimalou o jar pode simplesmente ser fornecido? Tentei fazer o primeiro, mas não fui muito longe.
user991710

1
Não execute -Xverify:noneem produção, pois isso quebra a verificação de código e você pode ter problemas. -XX:TieredStopAtLevel=1está OK se você executar um aplicativo por um período curto (alguns segundos), caso contrário, será menos produtivo, pois fornecerá otimizações de longa execução à JVM.
loicmathieu de

3
o documento oracle lista Use of -Xverify:none is unsupported.o que isso significa?
sakura

1
Muitos pools (Oracle UCP com certeza, mas em meus testes também Hikari e Tomcat) criptografam dados no pool. Na verdade, não sei se eles estão criptografando as informações de conexão ou encapsulando o fluxo. Independentemente disso, a criptografia usa geração de número aleatório e, portanto, ter uma fonte de entropia de alto rendimento e alta disponibilidade faz uma diferença notável no desempenho.
Daniel

19

Spring Boot 2.2.M1 adicionou um recurso para suportar a inicialização lenta no Spring Boot.

Por padrão, quando um contexto de aplicativo está sendo atualizado, cada bean no contexto é criado e suas dependências são injetadas. Por outro lado, quando uma definição de bean é configurada para ser inicializada lentamente, ela não será criada e suas dependências não serão injetadas até que sejam necessárias.

Ativando a inicialização lenta definida spring.main.lazy-initializationcomo verdadeira

Quando ativar a inicialização lenta

a inicialização lenta pode oferecer melhorias significativas no tempo de inicialização, mas existem algumas desvantagens notáveis ​​e é importante habilitá-la com cuidado

Para mais detalhes, consulte o Doc


3
se você habilitar a inicialização lenta, o carregamento inicial é super rápido, mas quando o cliente acessa pela primeira vez, pode haver algum atraso. Eu realmente recomendo isso para o desenvolvimento, não para a produção.
Isuru Dewasurendra

Como @IsuruDewasurendra sugeriu, certamente não é uma maneira recomendada, pois pode aumentar significativamente a latência quando o aplicativo começa a servir a carga.
Narendra Jaggi

Ele apenas chuta a lata pela estrada.
Abhijit Sarkar

10

Conforme descrito nesta pergunta / resposta, acho que a melhor abordagem é, em vez de adicionar apenas aqueles que você acha que precisa, excluir as dependências que você sabe que não precisa.

Veja: Minimize o tempo de inicialização do Spring Boot

Em suma:

Você pode ver o que está acontecendo nos bastidores e habilitar o registro de depuração tão simples quanto especificar --debug ao iniciar o aplicativo a partir da linha de comando. Você também pode especificar debug = true em seu application.properties.

Além disso, você pode definir o nível de registro em application.properties tão simples como:

logging.level.org.springframework.web: DEBUG logging.level.org.hibernate: ERROR

Se você detectar um módulo configurado automaticamente que não deseja, ele pode ser desativado. Os documentos para isso podem ser encontrados aqui: http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#using-boot-disabling-specific-auto-configuration

Um exemplo seria:

@Configuration
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
public class MyConfiguration {
}

4

Bem, há uma lista completa de ações possíveis descritas aqui: https://spring.io/blog/2018/12/12/how-fast-is-spring

Vou colocar as notas mais importantes do lado da primavera (ajustadas um pouco):

  • Exclusões de Classpath dos iniciantes da Web do Spring Boot:
    • Validador Hibernate
    • Jackson (mas os atuadores Spring Boot dependem disso). Use Gson se precisar de renderização JSON (funciona apenas com MVC pronto para uso).
    • Logback: use slf4j-jdk14 ao invés
  • Use o spring-context-indexer. Não vai acrescentar muito, mas cada pequena ajuda.
  • Não use atuadores se você não puder.
  • Use Spring Boot 2.1 e Spring 5.1. Mude para 2.2 e 5.2 quando estiverem disponíveis.
  • Corrija a localização do (s) arquivo (s) de configuração do Spring Boot com spring.config.location(argumento da linha de comando ou propriedade do sistema, etc.). Exemplo para teste em IDE: spring.config.location=file://./src/main/resources/application.properties.
  • Desligue o JMX se não precisar dele spring.jmx.enabled=false(este é o padrão no Spring Boot 2.2)
  • Torne as definições de bean preguiçosas por padrão. Há um novo sinalizador spring.main.lazy-initialization=trueno Spring Boot 2.2 (use LazyInitBeanFactoryPostProcessorpara Spring mais antigo).
  • Descompacte o jar de gordura e execute com um classpath explícito.
  • Execute o JVM com -noverify. Considere também -XX:TieredStopAtLevel=1(isso tornará o JIT mais lento, prejudicando o tempo de inicialização economizado).

O mencionado LazyInitBeanFactoryPostProcessor(você pode usá-lo para o Spring 1.5 se não puder aplicar o sinalizador spring.main.lazy-initialization=truedisponível no Spring 2.2):

public class LazyInitBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
      for (String beanName : beanFactory.getBeanDefinitionNames()) {
        BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
        definition.setLazyInit(true);
      }
  }
}

Você também pode usar (ou escrever seu próprio - é simples) algo para analisar o tempo de inicialização dos beans: https://github.com/lwaddicor/spring-startup-analysis

Espero que ajude!


0

No meu caso, havia muitos pontos de interrupção. Quando eu cliquei em "Mute Breakpoints" e reiniciei o aplicativo no modo de depuração, o aplicativo foi iniciado 10 vezes mais rápido.


-1

Se você está tentando otimizar o retorno do desenvolvimento para testes manuais, recomendo fortemente o uso de devtools .

Os aplicativos que usam spring-boot-devtools serão reiniciados automaticamente sempre que os arquivos no classpath forem alterados.

Basta recompilar - e o servidor será reiniciado (para Groovy, você só precisa atualizar o arquivo de origem). se você estiver usando um IDE (por exemplo, 'vscode'), ele pode compilar automaticamente seus arquivos java, portanto, apenas salvar um arquivo java pode iniciar uma reinicialização do servidor, indiretamente - e o Java torna-se tão simples quanto o Groovy nesse aspecto.

A beleza dessa abordagem é que a reinicialização incremental causa um curto-circuito em algumas das etapas de inicialização do zero - para que seu serviço volte a funcionar muito mais rapidamente!


Infelizmente, isso não ajuda com os tempos de inicialização para implantação ou teste de unidade automatizado.


-1

AVISO: Se você não usa Hibernate DDL para geração automática de esquema de banco de dados e não usa cache L2, esta resposta NÃO se aplica a você. Avance.

Minha descoberta é que o Hibernate adiciona um tempo significativo para a inicialização do aplicativo. Desativando o cache L2 e a inicialização do banco de dados resulta na mais rápida do aplicativo Spring Boot. Deixe o cache ON para produção e desative-o para seu ambiente de desenvolvimento.

application.yml:

spring:
  jpa:
    generate-ddl: false
    hibernate:
      ddl-auto: none
    properties:
      hibernate:
        cache:
          use_second_level_cache: false
          use_query_cache: false

Resultado dos testes:

  1. O cache L2 está LIGADO e ddl-auto: update

    INFO 5024 --- [restartedMain] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 23331 ms
    INFO 5024 --- [restartedMain] b.n.spring.Application : Started Application in 54.251 seconds (JVM running for 63.766)
  2. O cache L2 está DESLIGADO e ddl-auto: none

    INFO 10288 --- [restartedMain] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 9863 ms
    INFO 10288 --- [restartedMain] b.n.spring.Application : Started Application in 32.058 seconds (JVM running for 37.625)

Agora me pergunto o que vou fazer com todo esse tempo livre


hibernate.hbm2ddl.auto = update não tem nada a ver com o cache l2. ddl .. = update especifica a varredura do esquema do banco de dados atual e calcula o sql necessário para atualizar o esquema para refletir suas entidades. 'Nenhum' não faz esta verificação (também, não tenta atualizar o esquema). A prática recomendada é usar uma ferramenta como o liquibase, onde você lidará com as alterações do esquema e também poderá rastreá-las.
Radu Toader de

@RaduToader esta pergunta e minha resposta são sobre como acelerar o tempo de inicialização do Spring Boot. Eles não têm nada a ver com a discussão do Hibernate DDL vs Liquibase; essas ferramentas têm seus prós e contras. Meu ponto é que podemos desabilitar a atualização do esquema do banco de dados e habilitá-la somente quando necessário. O Hibernate leva um tempo significativo na inicialização, mesmo quando o modelo não mudou desde a última execução (para comparar o esquema do banco de dados com o esquema gerado automaticamente). O mesmo ponto é verdadeiro para o cache L2.
naXa de

sim, eu sei disso, mas meu ponto é que é um pouco perigoso não explicar o que realmente faz. Você pode facilmente acabar com o banco de dados vazio.
Radu Toader de

@RaduToader Havia um link para uma página de documentação sobre inicialização de banco de dados em minha resposta. Você leu? Ele contém um guia completo, listando todas as ferramentas mais populares (Hibernate e Liquibase, bem como JPA e Flyway). Também hoje acrescento um aviso claro no início da minha resposta. Você acha que preciso de alguma outra mudança para explicar as consequências?
naXa

Perfeito. Obrigado
Radu Toader

-3

Acho estranho que ninguém tenha sugerido essas otimizações antes. Aqui estão algumas dicas gerais sobre como otimizar a construção e inicialização do projeto durante o desenvolvimento:

  • excluir diretórios de desenvolvimento do scanner antivírus:
    • diretório do projeto
    • compilar o diretório de saída (se estiver fora do diretório do projeto)
    • Diretório de índices IDE (por exemplo, ~ / .IntelliJIdea2018.3)
    • diretório de implantação (webapps no Tomcat)
  • atualizar o hardware. use CPU e RAM mais rápidas, melhor conexão de internet (para baixar dependências) e conexão de banco de dados, mude para SSD. uma placa de vídeo não importa.

AVISOS

  1. a primeira opção vem pelo preço do título reduzido.
  2. a segunda opção custa dinheiro (obviamente).

A questão é sobre como melhorar o tempo de inicialização, não o tempo de compilação.
ArtOfWarfare

@ArtOfWarfare leu a pergunta novamente. a pergunta afirma o problema como "Não estou satisfeito que demore tanto [tempo], principalmente porque quebra o fluxo de desenvolvimento". Eu senti que esse é um problema primário e o abordei em minha resposta.
naXa de

-9

Para mim, parece que você está usando uma definição de configuração errada. Comece verificando myContainer e possíveis conflitos. Para determinar quem está usando mais recursos, você deve verificar os mapas de memória (veja a quantidade de dados!) Para cada dependência de cada vez - e isso leva muito tempo também ... (e privilégios SUDO). A propósito: você costuma testar o código em relação às dependências?

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.