Accelera il tempo di avvio di Spring Boot


115

Ho un'applicazione Spring Boot. Ho aggiunto molte dipendenze (sfortunatamente, sembra che mi servano tutte) e il tempo di avvio è aumentato parecchio. Basta fare un SpringApplication.run(source, args)richiede 10 secondi.

Anche se potrebbe non essere molto rispetto a ciò a cui sono "abituato", non sono contento che ci voglia così tanto, soprattutto perché interrompe il flusso di sviluppo. L'applicazione stessa è piuttosto piccola a questo punto, quindi presumo che la maggior parte delle volte sia correlata alle dipendenze aggiunte, non alle classi dell'app stesse.

Presumo che il problema sia la scansione del percorso di classe, ma non sono sicuro di come:

  • Conferma che è questo il problema (ad esempio, come eseguire il "debug" di Spring Boot)
  • Se è davvero la causa, come posso limitarla, in modo che diventi più veloce? Ad esempio, se so che alcune dipendenze o pacchetti non contengono nulla che Spring dovrebbe sottoporre a scansione, c'è un modo per limitarlo?

Presumo che migliorare Spring per avere l'inizializzazione del bean parallelo durante l'avvio velocizzerebbe le cose, ma la richiesta di miglioramento è stata aperta dal 2011, senza alcun progresso. Vedo alcuni altri sforzi nello stesso Spring Boot, come i miglioramenti della velocità di Investigate Tomcat JarScanning , ma questo è specifico di Tomcat ed è stato abbandonato.

Questo articolo:

sebbene mirato a test di integrazione, suggerisce di utilizzare lazy-init=true , tuttavia non so come applicarlo a tutti i bean in Spring Boot utilizzando la configurazione Java - qualche puntatore qui?

Qualsiasi (altro) suggerimento sarebbe il benvenuto.


Pubblica il tuo codice. Normalmente viene scansionato solo il pacchetto definito dal runner dell'applicazione. Se hai altri pacchetti definiti, anche @ComponentScanquelli vengono scansionati. Un'altra cosa è assicurarsi di non aver abilitato il debug o la registrazione della traccia poiché generalmente la registrazione è lenta, molto lenta.
M. Deinum

Se usi Hibernate, tende anche a consumare molto tempo all'avvio dell'applicazione.
Knut Forkalsrud

Il binding automatico di Spring per tipo accoppiato con i bean di fabbrica ha il potenziale per essere lento quando si aggiungono molti bean e dipendenze.
Knut Forkalsrud

Oppure puoi usare il caching, spring.io/guides/gs/caching
Cassian

2
Grazie a tutti per i commenti - Sfortunatamente non sarei in grado di pubblicare il codice (molti barattoli interni), tuttavia sto ancora cercando un modo per eseguire il debug di questo. Sì, potrei usare A o B o fare X o Y, il che lo rallenta. Come lo determino? Se aggiungo una dipendenza X, che ha 15 dipendenze transitive, come faccio a sapere quale di quelle 16 l'ha rallentata? Se riesco a scoprirlo, c'è qualcosa che posso fare in seguito per impedire a Spring di esaminarli? Puntatori del genere sarebbero utili!
pioggia costante

Risposte:


61

Spring Boot esegue molte operazioni di configurazione automatica che potrebbero non essere necessarie. Quindi potresti voler restringere solo la configurazione automatica necessaria per la tua app. Per vedere l'elenco completo delle configurazioni automatiche incluse, basta eseguire la registrazione org.springframework.boot.autoconfigurein modalità DEBUG ( logging.level.org.springframework.boot.autoconfigure=DEBUGin application.properties). Un'altra opzione è eseguire l'applicazione di avvio primaverile con l' --debugopzione:java -jar myproject-0.0.1-SNAPSHOT.jar --debug

Ci sarebbe qualcosa di simile nell'output:

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

Ispeziona questo elenco e includi solo le configurazioni automatiche necessarie:

@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 {

Il codice è stato copiato da questo post del blog .


1
hai misurato questo ??? È stato molto più veloce ?? A mio parere questo è un caso eccezionale, molto più importante per assicurarsi che la cache del contesto del test di primavera
funzioni

@idmitriev L'ho appena misurato sulla mia applicazione e la mia applicazione si è avviata a 53 secondi, rispetto a senza escludere le classi di autoconfigurazione era di 73 secondi. Tuttavia, ho escluso molte più classi rispetto a quelle elencate sopra.
apkisbossin

Bello importare tutta la configurazione. Come gestire BatchConfigurerConfiguration.JpaBatchConfiguration si dovrebbe aggiungere la dipendenza al progetto? Come gestire i metodi di riferimento come ConfigurationPropertiesRebinderAutoConfiguration # configurationPropertiesBeans?
user1767316

Come gestire le classi di configurazione private?
user1767316

44

La risposta più votata finora non è sbagliata, ma non va nella profondità che mi piace vedere e non fornisce prove scientifiche. Il team di Spring Boot ha svolto un esercizio per ridurre il tempo di avvio per Boot 2.0 e il ticket 11226 contiene molte informazioni utili. C'è anche un ticket 7939 aperto per aggiungere informazioni sui tempi per la valutazione delle condizioni, ma non sembra avere un ETA specifico.

L'approccio più utile e metodico per il debug dell'avvio di avvio è stato eseguito da Dave Syer. https://github.com/dsyer/spring-boot-startup-bench

Avevo anche un caso d'uso simile, quindi ho preso l'approccio di Dave al micro-benchmarking con JMH e l'ho seguito. Il risultato è il progetto boot benchmark . L'ho progettato in modo che possa essere utilizzato per misurare il tempo di avvio di qualsiasi applicazione Spring Boot, utilizzando il jar eseguibile prodotto da Gradle task bootJar(precedentemente chiamato bootRepackagein Boot 1.5). Sentiti libero di usarlo e di fornire feedback.

I miei risultati sono i seguenti:

  1. La CPU è importante. Un sacco.
  2. Avviare la JVM con -Xverify: nessuno aiuta in modo significativo.
  3. L'esclusione di autoconfigurazioni non necessarie aiuta.
  4. Dave ha consigliato l'argomento JVM -XX: TieredStopAtLevel = 1 , ma i miei test non hanno mostrato miglioramenti significativi con quello. Inoltre, -XX:TieredStopAtLevel=1probabilmente rallenterebbe la tua prima richiesta.
  5. Ci sono state segnalazioni di lentezza nella risoluzione del nome host, ma non ho riscontrato che fosse un problema per le app che ho testato.

1
@ user991710 Non sono sicuro di come si sia rotto, ma ora è stato risolto. Grazie per la segnalazione.
Abhijit Sarkar

2
Per aggiungere a questo, potresti aggiungere un esempio su come qualcuno potrebbe utilizzare il tuo benchmark con un'applicazione personalizzata? Deve essere aggiunto come progetto simile a minimal, o il barattolo può essere semplicemente fornito? Ho tentato di fare il primo ma non sono andato molto lontano.
user991710

1
Non eseguire la -Xverify:noneproduzione in quanto interrompe la verifica del codice e potresti avere problemi. -XX:TieredStopAtLevel=1va bene se esegui un'applicazione per una breve durata (pochi secondi) altrimenti sarà meno produttiva in quanto fornirà alla JVM ottimizzazioni di lunga durata.
loicmathieu

3
il documento Oracle elenca Use of -Xverify:none is unsupported.cosa significa?
Sakura

1
Molti pool (Oracle UCP di sicuro, ma nei miei test anche Hikari e Tomcat) crittografano i dati nel pool. In realtà non so se stanno crittografando le informazioni di connessione o avvolgendo il flusso. Indipendentemente da ciò, la crittografia utilizza la generazione di numeri casuali e quindi avere una fonte di entropia ad alta velocità e alta disponibilità fa una notevole differenza in termini di prestazioni.
Daniel

19

Spring Boot 2.2.M1 ha aggiunto funzionalità per supportare l'inizializzazione pigra in Spring Boot.

Per impostazione predefinita, quando un contesto dell'applicazione viene aggiornato, ogni bean nel contesto viene creato e le sue dipendenze vengono iniettate. Al contrario, quando una definizione di bean è configurata per essere inizializzata pigramente, non verrà creata e le sue dipendenze non verranno inserite finché non sarà necessario.

Abilitazione dell'inizializzazione lenta Impostare spring.main.lazy-initializationsu true

Quando abilitare l'inizializzazione pigra

l'inizializzazione pigra può offrire miglioramenti significativi nel tempo di avvio, ma ci sono anche alcuni svantaggi notevoli ed è importante abilitarlo con cura

Per maggiori dettagli, consultare Doc


3
se abiliti l'inizializzazione pigra, il caricamento della prima volta è super veloce, ma quando il client accede per la prima volta potrebbe notare un certo ritardo. Lo consiglio vivamente per lo sviluppo non per la produzione.
Isuru Dewasurendra

Come suggerito da @IsuruDewasurendra, giustamente non è un modo consigliato, può aumentare significativamente la latenza quando l'app inizia a servire il carico.
Narendra Jaggi

Dà solo calci alla lattina lungo la strada.
Abhijit Sarkar

10

Come descritto in questa domanda / risposta, penso che l'approccio migliore sia invece di aggiungere solo quelli di cui pensi di aver bisogno, escludere le dipendenze che sai di non aver bisogno.

Vedere: Ridurre al minimo il tempo di avvio di Spring Boot

In sintesi:

Puoi vedere cosa sta succedendo sotto le coperte e abilitare la registrazione del debug semplice come specificare --debug quando si avvia l'applicazione dalla riga di comando. È inoltre possibile specificare debug = true in application.properties.

Inoltre, puoi impostare il livello di registrazione in application.properties in modo semplice come:

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

Se rilevi un modulo configurato automaticamente che non desideri, può essere disabilitato. I documenti per questo sono disponibili qui: http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#using-boot-disabling-specific-auto-configuration

Un esempio potrebbe essere:

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

4

Bene, c'è l'intero elenco di possibili azioni descritte qui: https://spring.io/blog/2018/12/12/how-fast-is-spring

Metterò le note più importanti dal lato della primavera (aggiustate un po '):

  • Esclusioni del percorso di classe dagli avviatori Web Spring Boot:
    • Validatore di ibernazione
    • Jackson (ma gli attuatori Spring Boot dipendono da questo). Usa Gson se hai bisogno del rendering JSON (funziona solo con MVC out of the box).
    • Logback: usa invece slf4j-jdk14
  • Usa l'indicizzatore del contesto primaverile. Non aggiungerà molto, ma ogni piccolo aiuta.
  • Non usare gli attuatori se puoi permetterti di non farlo.
  • Usa Spring Boot 2.1 e Spring 5.1. Passa a 2.2 e 5.2 quando sono disponibili.
  • Correggere la posizione dei file di configurazione di Spring Boot con spring.config.location(argomento della riga di comando o proprietà di sistema ecc.). Esempio per verificare in IDE: spring.config.location=file://./src/main/resources/application.properties.
  • Disattiva JMX se non ne hai bisogno con spring.jmx.enabled=false(questa è l'impostazione predefinita in Spring Boot 2.2)
  • Rendi pigre le definizioni dei bean per impostazione predefinita. C'è un nuovo flag spring.main.lazy-initialization=truein Spring Boot 2.2 (da usare LazyInitBeanFactoryPostProcessorper i precedenti Spring).
  • Decomprimere il fat jar ed eseguire con un classpath esplicito.
  • Esegui la JVM con -noverify. Considera anche -XX:TieredStopAtLevel=1(ciò rallenterà il JIT in seguito a scapito del tempo di avvio risparmiato).

Il menzionato LazyInitBeanFactoryPostProcessor(puoi usarlo per Spring 1.5 se non puoi applicare il flag spring.main.lazy-initialization=truedisponibile dalla 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);
      }
  }
}

Puoi anche usare (o scrivere il tuo - è semplice) qualcosa per analizzare il tempo di inizializzazione dei bean: https://github.com/lwaddicor/spring-startup-analysis

Spero che sia d'aiuto!


0

Nel mio caso, c'erano troppi punti di interruzione. Quando ho fatto clic su "Mute Breakpoints" e ho riavviato l'applicazione in modalità di debug, l'applicazione è stata avviata 10 volte più velocemente.


-1

Se stai cercando di ottimizzare il turn-around dello sviluppo per i test manuali, consiglio vivamente l'uso di devtools .

Le applicazioni che usano spring-boot-devtools si riavvieranno automaticamente ogni volta che i file sul classpath cambiano.

Basta ricompilare e il server si riavvierà (per Groovy devi solo aggiornare il file sorgente). se stai usando un IDE (ad esempio 'vscode'), potrebbe compilare automaticamente i tuoi file java, quindi il semplice salvataggio di un file java può avviare un riavvio del server, indirettamente - e Java diventa altrettanto semplice come Groovy a questo proposito.

La bellezza di questo approccio è che il riavvio incrementale mette in cortocircuito alcuni dei passaggi di avvio da zero, quindi il tuo servizio sarà di nuovo attivo e funzionante molto più rapidamente!


Sfortunatamente, questo non aiuta con i tempi di avvio per la distribuzione o il test di unità automatizzato.


-1

AVVERTENZA: se non utilizzi Hibernate DDL per la generazione automatica dello schema DB e non utilizzi la cache L2, questa risposta NON è applicabile a te. Scorri avanti.

La mia scoperta è che Hibernate aggiunge tempo significativo all'avvio dell'applicazione. La disabilitazione della cache L2 e dell'inizializzazione del database determina un avvio più rapido dell'app Spring Boot. Lascia la cache attiva per la produzione e disabilitala per il tuo ambiente di sviluppo.

application.yml:

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

Risultati del test:

  1. La cache L2 è attiva 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. La cache L2 è disattivata 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)

Ora mi chiedo cosa farò di tutto questo tempo libero


hibernate.hbm2ddl.auto = l'aggiornamento non ha nulla a che fare con la cache l2. ddl .. = update specifica di scansionare lo schema del database corrente e di calcolare lo sql necessario per aggiornare lo schema per riflettere le tue entità. "Nessuno" non esegue questa verifica (inoltre, non tenta di aggiornare lo schema). La best practice consiste nell'utilizzare uno strumento come liquibase, dove gestirai le modifiche allo schema e potrai anche tenerne traccia.
Radu Toader

@RaduToader questa domanda e la mia risposta riguardano l'accelerazione del tempo di avvio di Spring Boot. Non hanno nulla a che fare con la discussione tra Hibernate DDL e Liquibase; questi strumenti hanno entrambi i loro pro e contro. Il punto è che possiamo disabilitare l'aggiornamento dello schema DB e abilitarlo solo quando necessario. Hibernate impiega molto tempo all'avvio anche quando il modello non è cambiato dall'ultima esecuzione (per confrontare lo schema DB con lo schema generato automaticamente). Lo stesso punto è vero per la cache L2.
naXa

sì, lo so, ma il punto era che è un po 'pericoloso non spiegare cosa fa veramente. Potresti facilmente finire con il tuo db vuoto.
Radu Toader

@RaduToader C'era un collegamento a una pagina di documentazione sull'inizializzazione del DB nella mia risposta. L'hai letto? Contiene una guida esaustiva, che elenca tutti gli strumenti più popolari (Hibernate e Liquibase, oltre a JPA e Flyway). Anche oggi aggiungo un chiaro avvertimento all'inizio della mia risposta. Pensi che io abbia bisogno di altri cambiamenti per spiegare le conseguenze?
naXa

Perfetto. Grazie
Radu Toader

-3

Trovo strano che nessuno abbia suggerito prima queste ottimizzazioni. Ecco alcuni suggerimenti generali sull'ottimizzazione della creazione e dell'avvio del progetto durante lo sviluppo:

  • escludere le directory di sviluppo dallo scanner antivirus:
    • directory del progetto
    • costruire la directory di output (se è al di fuori della directory del progetto)
    • Directory degli indici IDE (ad esempio ~ / .IntelliJIdea2018.3)
    • directory di distribuzione (webapp in Tomcat)
  • aggiornare l'hardware. utilizzare CPU e RAM più veloci, una migliore connessione Internet (per scaricare le dipendenze) e una connessione al database, passare a SSD. una scheda video non ha importanza.

AVVERTENZE

  1. la prima opzione arriva al prezzo di una sicurezza ridotta.
  2. la seconda opzione costa denaro (ovviamente).

La domanda riguarda il miglioramento del tempo di avvio, non il tempo di compilazione.
ArtOfWarfare

@ArtOfWarfare ha letto di nuovo la domanda. la domanda afferma il problema come "Non sono contento che ci voglia così tanto [tempo], soprattutto perché interrompe il flusso di sviluppo". Ho sentito che questo è un problema primario e l'ho affrontato nella mia risposta.
naXa

-9

A me sembra che tu stia utilizzando un'impostazione di configurazione sbagliata. Inizia controllando myContainer e possibili conflitti. Per determinare chi sta usando la maggior parte delle risorse devi controllare le mappe di memoria (vedi la quantità di dati!) Per ogni dipendenza alla volta - e anche questo richiede molto tempo ... (e privilegi SUDO). A proposito: di solito stai testando il codice contro le dipendenze?

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.