Accélérez le temps de démarrage de Spring Boot


115

J'ai une application Spring Boot. J'ai ajouté beaucoup de dépendances (malheureusement, j'ai besoin de toutes) et le temps de démarrage a beaucoup augmenté. Faire juste un SpringApplication.run(source, args)prend 10 secondes.

Bien que ce ne soit peut-être pas grand-chose par rapport à ce à quoi on est «habitué», je ne suis pas content que cela en prenne autant, principalement parce que cela rompt le flux de développement. L'application elle-même est plutôt petite à ce stade, je suppose donc que la plupart du temps est liée aux dépendances ajoutées, pas aux classes d'application elles-mêmes.

Je suppose que le problème est l'analyse du chemin de classe, mais je ne sais pas comment:

  • Confirmez que c'est le problème (c'est-à-dire comment «déboguer» Spring Boot)
  • Si c'est vraiment la cause, comment puis-je la limiter, pour qu'elle soit plus rapide? Par exemple, si je sais qu'une dépendance ou un package ne contient rien que Spring devrait analyser, y a-t-il un moyen de limiter cela?

Je suppose que l' amélioration de Spring pour avoir l'initialisation du bean parallèle au démarrage accélérerait les choses, mais cette demande d'amélioration est ouverte depuis 2011, sans aucun progrès. Je vois d'autres efforts dans Spring Boot lui-même, tels que les améliorations de la vitesse Investigate Tomcat JarScanning , mais cela est spécifique à Tomcat et a été abandonné.

Cet article:

bien que destiné aux tests d'intégration, suggère d'utiliser lazy-init=true, mais je ne sais pas comment appliquer cela à tous les beans dans Spring Boot en utilisant la configuration Java - des pointeurs ici?

Toute (autre) suggestion serait la bienvenue.


Postez votre code. Normalement, seul le package défini par le programme d'exécution de l'application est analysé. Si vous avez défini d'autres packages pour @ComponentScanceux-ci, ils sont également analysés. Une autre chose est de vous assurer que vous n'avez pas activé la journalisation de débogage ou de trace car la journalisation est généralement lente, très lente.
M. Deinum

Si vous utilisez Hibernate, cela a également tendance à prendre beaucoup de temps au démarrage de l'application.
Knut Forkalsrud

La liaison automatique de Spring par type associée aux beans d'usine a le potentiel d'être lente si vous ajoutez beaucoup de beans et de dépendances.
Knut Forkalsrud

Ou vous pouvez utiliser la mise en cache, spring.io/guides/gs/caching
Cassian

2
Merci à tous pour les commentaires - je ne pourrais malheureusement pas publier le code (beaucoup de jars internes), mais je cherche toujours un moyen de déboguer cela. Oui, je pourrais utiliser A ou B ou faire X ou Y, ce qui le ralentit. Comment le déterminer? Si j'ajoute une dépendance X, qui a 15 dépendances transitives, comment savoir laquelle de ces 16 l'a ralentie? Si je peux le découvrir, y a-t-il quelque chose que je puisse faire plus tard pour empêcher Spring de les examiner? Des pointeurs comme ça seraient utiles!
pluie régulière

Réponses:


61

Spring Boot effectue de nombreuses configurations automatiques qui peuvent ne pas être nécessaires. Vous souhaiterez peut-être limiter uniquement la configuration automatique nécessaire à votre application. Pour voir la liste complète de la configuration automatique incluse, exécutez simplement la journalisation org.springframework.boot.autoconfigureen mode DEBUG ( logging.level.org.springframework.boot.autoconfigure=DEBUGin application.properties). Une autre option consiste à exécuter l'application Spring Boot avec l' --debugoption:java -jar myproject-0.0.1-SNAPSHOT.jar --debug

Il y aurait quelque chose comme ça dans la sortie:

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

Inspectez cette liste et incluez uniquement les configurations automatiques dont vous avez besoin:

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

Le code a été copié à partir de ce billet de blog .


1
avez-vous mesuré cela ??? Était-ce beaucoup plus rapide ?? À mon avis, c'est un cas exceptionnel, beaucoup plus important pour s'assurer que le cache de contexte de test Spring fonctionne
idmitriev

@idmitriev Je viens de mesurer cela sur mon application et mon application a démarré à 53 secondes, contre 73 secondes sans exclure les classes d'autoconfiguration. J'ai cependant exclu beaucoup plus de classes que celles énumérées ci-dessus.
apkisbossin

Nice à importer toute la configuration. Comment gérer BatchConfigurerConfiguration.JpaBatchConfiguration faut-il ajouter la dépendance au projet? Comment gérer les méthodes référencées telles que ConfigurationPropertiesRebinderAutoConfiguration # configurationPropertiesBeans?
user1767316

Comment gérer les classes de configuration privées?
user1767316

44

La réponse la plus votée jusqu'à présent n'est pas fausse, mais elle ne va pas dans la profondeur que j'aime voir et ne fournit aucune preuve scientifique. L'équipe Spring Boot a effectué un exercice pour réduire le temps de démarrage de Boot 2.0, et le ticket 11226 contient de nombreuses informations utiles. Il existe également un ticket 7939 permettant d'ajouter des informations de synchronisation à l'évaluation des conditions, mais il ne semble pas avoir d'ETA spécifique.

L'approche la plus utile et la plus méthodique pour déboguer le démarrage du démarrage a été réalisée par Dave Syer. https://github.com/dsyer/spring-boot-startup-bench

J'avais également un cas d'utilisation similaire, alors j'ai pris l'approche de Dave en matière de micro-benchmarking avec JMH et j'ai couru avec. Le résultat est le projet boot-benchmark . Je l'ai conçu de telle sorte qu'il puisse être utilisé pour mesurer le temps de démarrage de toute application Spring Boot, en utilisant le fichier jar exécutable produit par la tâche Gradle bootJar(précédemment appelée bootRepackagedans Boot 1.5). N'hésitez pas à l'utiliser et à fournir des commentaires.

Mes conclusions sont les suivantes:

  1. Le processeur compte. Beaucoup.
  2. Démarrer la JVM avec -Xverify: aucune aide de manière significative.
  3. L'exclusion des autoconfigurations inutiles aide.
  4. Dave a recommandé l'argument JVM -XX: TieredStopAtLevel = 1 , mais mes tests n'ont pas montré d'amélioration significative avec cela. En outre, -XX:TieredStopAtLevel=1cela ralentirait probablement votre première demande.
  5. Il a été signalé que la résolution du nom d'hôte était lente, mais je n'ai pas trouvé que cela posait un problème pour les applications que j'ai testées.

1
@ user991710 Je ne sais pas comment cela s'est cassé, mais c'est corrigé maintenant. Merci pour le rapport.
Abhijit Sarkar

2
Pour ajouter à cela, pourriez-vous s'il vous plaît ajouter un exemple de la façon dont quelqu'un pourrait utiliser votre benchmark avec une application personnalisée? Doit-il être ajouté en tant que projet similaire à minimal, ou le pot peut-il simplement être fourni? J'ai essayé de faire le premier mais je ne suis pas allé très loin.
user991710

1
N'exécutez pas -Xverify:noneen production car cela interrompt la vérification du code et vous pouvez rencontrer des problèmes. -XX:TieredStopAtLevel=1est OK si vous exécutez une application pour une courte durée (quelques secondes) sinon elle sera moins productive car elle fournira à la JVM des optimisations de longue durée.
loicmathieu

3
le document oracle répertorie Use of -Xverify:none is unsupported.ce que cela signifie?
sakura

1
De nombreux pools (Oracle UCP bien sûr, mais dans mes tests également Hikari et Tomcat) chiffrent les données dans le pool. En fait, je ne sais pas s'ils chiffrent les informations de connexion ou encapsulent le flux. Quoi qu'il en soit, le chiffrement utilise la génération de nombres aléatoires et ainsi, disposer d'une source d'entropie hautement disponible et à haut débit fait une différence notable en termes de performances.
Daniel

19

Spring Boot 2.2.M1 a ajouté une fonctionnalité pour prendre en charge l'initialisation paresseuse dans Spring Boot.

Par défaut, lorsqu'un contexte d'application est en cours d'actualisation, chaque bean du contexte est créé et ses dépendances sont injectées. En revanche, lorsqu'une définition de bean est configurée pour être initialisée paresseusement, elle ne sera pas créée et ses dépendances ne seront pas injectées tant qu'elles ne seront pas nécessaires.

Activation de l'initialisation différée définie spring.main.lazy-initializationsur true

Quand activer l'initialisation différée

l'initialisation paresseuse peut offrir des améliorations significatives dans le temps de démarrage, mais il y a aussi des inconvénients notables et il est important de l'activer avec précaution

Pour plus de détails, veuillez consulter Doc


3
si vous activez l'initialisation tardive, le premier chargement est très rapide, mais lorsque le client accède pour la première fois, il peut remarquer un certain retard. Je le recommande vraiment pour le développement et non pour la production.
Isuru Dewasurendra

Comme @IsuruDewasurendra l'a suggéré, ce n'est à juste titre pas une méthode recommandée, elle peut augmenter considérablement la latence lorsque l'application commence à servir la charge.
Narendra Jaggi le

Cela donne un coup de pied à la canette sur la route.
Abhijit Sarkar

10

Comme décrit dans cette question / réponse, je pense que la meilleure approche consiste à au lieu d'ajouter uniquement celles dont vous pensez avoir besoin, d'exclure les dépendances dont vous savez que vous n'avez pas besoin.

Voir: Réduire le temps de démarrage de Spring Boot

En résumé:

Vous pouvez voir ce qui se passe sous les couvertures et activer la journalisation du débogage aussi simplement que de spécifier --debug lors du démarrage de l'application à partir de la ligne de commande. Vous pouvez également spécifier debug = true dans votre application.properties.

En outre, vous pouvez définir le niveau de journalisation dans application.properties aussi simplement que:

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

Si vous détectez un module auto-configuré dont vous ne voulez pas, il peut être désactivé. La documentation pour cela peut être trouvée ici: http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#using-boot-disabling-specific-auto-configuration

Un exemple ressemblerait à ceci:

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

4

Eh bien, il y a toute la liste des actions possibles décrites ici: https://spring.io/blog/2018/12/12/how-fast-is-spring

Je vais mettre les notes les plus importantes du côté Spring (ajusté un peu):

  • Exclusions de classpath des démarreurs Web Spring Boot:
    • Validateur Hibernate
    • Jackson (mais les actionneurs Spring Boot en dépendent). Utilisez Gson si vous avez besoin d'un rendu JSON (fonctionne uniquement avec MVC prêt à l'emploi).
    • Logback: utilisez plutôt slf4j-jdk14
  • Utilisez le spring-context-indexer. Cela n'ajoutera pas grand-chose, mais chaque petite aide.
  • N'utilisez pas d'actionneurs si vous ne pouvez pas vous le permettre.
  • Utilisez Spring Boot 2.1 et Spring 5.1. Basculez vers 2.2 et 5.2 lorsqu'ils sont disponibles.
  • Corrigez l'emplacement du ou des fichiers de configuration Spring Boot avec spring.config.location(argument de ligne de commande ou propriété système, etc.). Exemple pour tester en IDE: spring.config.location=file://./src/main/resources/application.properties.
  • Désactivez JMX si vous n'en avez pas besoin avec spring.jmx.enabled=false(c'est la valeur par défaut dans Spring Boot 2.2)
  • Rendre les définitions de bean paresseuses par défaut. Il y a un nouveau drapeau spring.main.lazy-initialization=truedans Spring Boot 2.2 (à utiliser LazyInitBeanFactoryPostProcessorpour les versions Spring plus anciennes).
  • Décompressez le fat jar et exécutez-le avec un chemin de classe explicite.
  • Exécutez la JVM avec -noverify. Considérez également -XX:TieredStopAtLevel=1(cela ralentira le JIT plus tard au détriment du temps de démarrage enregistré).

Le mentionné LazyInitBeanFactoryPostProcessor(vous pouvez l'utiliser pour Spring 1.5 si vous ne pouvez pas appliquer le drapeau spring.main.lazy-initialization=truedisponible à partir du printemps 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);
      }
  }
}

Vous pouvez également utiliser (ou écrire le vôtre - c'est simple) quelque chose pour analyser le temps d'initialisation des beans: https://github.com/lwaddicor/spring-startup-analysis

J'espère que ça aide!


0

Dans mon cas, il y avait trop de points d'arrêt. Lorsque j'ai cliqué sur "Mute Breakpoints" et redémarré l'application en mode débogage, l'application a démarré 10 fois plus rapidement.


-1

Si vous essayez d'optimiser la rotation du développement pour les tests manuels, je recommande fortement l'utilisation de devtools .

Les applications qui utilisent spring-boot-devtools redémarreront automatiquement chaque fois que les fichiers sur le chemin de classe changent.

Recompilez simplement - et le serveur redémarrera (pour Groovy, il vous suffit de mettre à jour le fichier source). si vous utilisez un IDE (par exemple 'vscode'), il peut compiler automatiquement vos fichiers java, donc le simple fait d'enregistrer un fichier java peut initier un redémarrage du serveur, indirectement - et Java devient tout aussi transparent que Groovy à cet égard.

La beauté de cette approche est que le redémarrage incrémentiel court-circuite certaines des étapes de démarrage à partir de zéro - de sorte que votre service sera de nouveau opérationnel beaucoup plus rapidement!


Malheureusement, cela n'aide pas avec les temps de démarrage pour le déploiement ou les tests unitaires automatisés.


-1

AVERTISSEMENT: Si vous n'utilisez pas Hibernate DDL pour la génération automatique de schéma de base de données et que vous n'utilisez pas le cache L2, cette réponse ne vous concerne PAS. Faites défiler vers l'avant.

Ma conclusion est que Hibernate ajoute un temps considérable au démarrage de l'application. La désactivation du cache L2 et de l' initialisation de la base de données accélère le démarrage de l'application Spring Boot. Laissez le cache activé pour la production et désactivez-le pour votre environnement de développement.

application.yml:

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

Résultats de test:

  1. Le cache L2 est activé et 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. Le cache L2 est désactivé et 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)

Maintenant je me demande ce que je vais faire de tout ce temps libre


hibernate.hbm2ddl.auto = la mise à jour n'a rien à voir avec le cache l2. ddl .. = update spécifie de scanner le schéma de base de données actuel et de calculer le sql nécessaire afin de mettre à jour le schéma pour refléter vos entités. «Aucun» ne fait pas cette vérification (et n'essaye pas non plus de mettre à jour le schéma). La meilleure pratique consiste à utiliser un outil tel que liquibase, dans lequel vous gérerez vos modifications de schéma et vous pourrez également les suivre.
Radu Toader

@RaduToader, cette question et ma réponse concernent l'accélération du temps de démarrage de Spring Boot. Ils n'ont rien à voir avec la discussion Hibernate DDL vs Liquibase; ces outils ont à la fois leurs avantages et leurs inconvénients. Mon point est que nous pouvons désactiver la mise à jour du schéma de base de données et l'activer uniquement lorsque cela est nécessaire. La mise en veille prolongée prend beaucoup de temps au démarrage même lorsque le modèle n'a pas changé depuis la dernière exécution (pour comparer le schéma de base de données avec le schéma généré automatiquement). Le même point est vrai pour le cache L2.
naXa

oui, je le sais, mais mon point était que c'est un peu dangereux de ne pas expliquer ce que cela fait vraiment. Vous pourriez très facilement vous retrouver avec votre base de données vide.
Radu Toader

@RaduToader Il y avait un lien vers une page de documentation sur l'initialisation de la base de données dans ma réponse. Est-ce que vous l'avez vu? Il contient un guide exhaustif, répertoriant tous les outils les plus populaires (Hibernate et Liquibase, ainsi que JPA et Flyway). Aujourd'hui également, j'ajoute un avertissement clair en haut de ma réponse. Pensez-vous que j'ai besoin d'autres changements pour expliquer les conséquences?
naXa

Parfait. Merci
Radu Toader

-3

Je trouve étrange que personne n'ait suggéré ces optimisations auparavant. Voici quelques conseils généraux sur l'optimisation de la construction et du démarrage du projet lors du développement:

  • exclure les répertoires de développement du scanner antivirus:
    • répertoire du projet
    • build du répertoire de sortie (s'il est en dehors du répertoire du projet)
    • Répertoire des index IDE (par exemple ~ / .IntelliJIdea2018.3)
    • répertoire de déploiement (webapps dans Tomcat)
  • mettre à niveau le matériel. utilisez un processeur et une RAM plus rapides, une meilleure connexion Internet (pour télécharger les dépendances) et une connexion à la base de données, passez au SSD. une carte vidéo n'a pas d'importance.

AVERTISSEMENTS

  1. la première option vient pour le prix d'une sécurité réduite.
  2. la deuxième option coûte de l'argent (évidemment).

La question est d'améliorer le temps de démarrage, pas le temps de compilation.
ArtOfWarfare

@ArtOfWarfare a relu la question. la question énonce le problème comme suit: "Je ne suis pas content que cela prenne autant [de temps], principalement parce que cela brise le flux de développement". J'ai senti que c'était un problème principal et je l'ai abordé dans ma réponse.
naXa

-9

Pour moi, il semble que vous utilisez un mauvais paramètre de configuration. Commencez par vérifier myContainer et les éventuels conflits. Pour déterminer qui utilise le plus de ressources, vous devez vérifier les cartes mémoire (voir la quantité de données!) Pour chaque dépendance à la fois - et cela prend également beaucoup de temps ... (et les privilèges SUDO). Au fait: testez-vous généralement le code par rapport aux dépendances?

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.