Acelere el tiempo de inicio de Spring Boot


115

Tengo una aplicación Spring Boot. Agregué muchas dependencias (desafortunadamente, parece que las necesito todas) y el tiempo de inicio aumentó bastante. Solo hacer una SpringApplication.run(source, args)toma 10 segundos.

Si bien eso puede no ser mucho en comparación con lo que estamos "acostumbrados", no me satisface que requiera tanto, principalmente porque rompe el flujo de desarrollo. La aplicación en sí es bastante pequeña en este punto, así que supongo que la mayor parte del tiempo está relacionada con las dependencias agregadas, no con las clases de la aplicación en sí.

Supongo que el problema es el escaneo de classpath, pero no estoy seguro de cómo:

  • Confirme que ese es el problema (es decir, cómo "depurar" Spring Boot)
  • Si realmente es la causa, ¿cómo puedo limitarlo para que sea más rápido? Por ejemplo, si sé que alguna dependencia o paquete no contiene nada que Spring debería estar escaneando, ¿hay alguna forma de limitar eso?

Supongo que mejorar Spring para que tenga una inicialización de bean paralelo durante el inicio aceleraría las cosas, pero esa solicitud de mejora ha estado abierta desde 2011, sin ningún progreso. Veo algunos otros esfuerzos en Spring Boot, como Investigar mejoras de velocidad de Tomcat JarScanning , pero eso es específico de Tomcat y se ha abandonado.

Este artículo:

aunque está dirigido a pruebas de integración, sugiere usar lazy-init=true , sin embargo, no sé cómo aplicar esto a todos los beans en Spring Boot usando la configuración de Java, ¿algún consejo aquí?

Cualquier (otra) sugerencia será bienvenida.


Publique su código. Normalmente, solo se analiza el paquete que está definido por el corredor de la aplicación. Si tiene otros paquetes definidos, también @ComponentScanse analizarán. Otra cosa es asegurarse de no haber habilitado el registro de depuración o seguimiento, ya que generalmente el registro es lento, muy lento.
M. Deinum

Si usa Hibernate, también tiende a consumir mucho tiempo al inicio de la aplicación.
Knut Forkalsrud

El enlace automático de Spring por tipo junto con los beans de fábrica tiene el potencial de ser lento si agrega muchos beans y dependencias.
Knut Forkalsrud

O puede usar el almacenamiento en caché, spring.io/guides/gs/caching
Cassian

2
Gracias a todos por los comentarios. Desafortunadamente, no podría publicar el código (muchos frascos internos), sin embargo, todavía estoy buscando una manera de depurar esto. Sí, podría estar usando A o B o haciendo X o Y, lo que lo ralentiza. ¿Cómo determino esto? Si agrego una dependencia X, que tiene 15 dependencias transitivas, ¿cómo sé cuál de esas 16 la ralentizó? Si puedo averiguarlo, ¿hay algo que pueda hacer más adelante para evitar que Spring los examine? ¡Consejos como ese serían útiles!
lluvia constante

Respuestas:


61

Spring Boot realiza muchas configuraciones automáticas que pueden no ser necesarias. Por lo tanto, es posible que desee limitar solo la configuración automática que se necesita para su aplicación. Para ver la lista completa de la configuración automática incluida, simplemente ejecute el registro org.springframework.boot.autoconfigureen modo DEBUG ( logging.level.org.springframework.boot.autoconfigure=DEBUGen application.properties). Otra opción es ejecutar la aplicación Spring Boot con la --debugopción:java -jar myproject-0.0.1-SNAPSHOT.jar --debug

Habría algo como esto en la salida:

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

Inspeccione esta lista e incluya solo las configuraciones automáticas que necesita:

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

El código se copió de esta publicación de blog .


1
mediste esto ??? ¿Fue mucho más rápido? En mi opinión, este es un caso excepcional, mucho más importante para asegurarse de que la caché de contexto de prueba de Spring funcione
idmitriev

@idmitriev Acabo de medir esto en mi aplicación y mi aplicación se inició a los 53 segundos, en comparación con los 73 segundos sin excluir las clases de configuración automática. Sin embargo, excluí muchas más clases de las enumeradas anteriormente.
apkisbossin

Es bueno importar toda la configuración. ¿Cómo lidiar con BatchConfigurerConfiguration.JpaBatchConfiguration si se agrega la dependencia al proyecto? ¿Cómo lidiar con métodos referenciados como ConfigurationPropertiesRebinderAutoConfiguration # configurationPropertiesBeans?
user1767316

¿Cómo lidiar con las clases de configuración privadas?
user1767316

44

La respuesta más votada hasta ahora no es incorrecta, pero no entra en la profundidad que me gustaría ver y no proporciona evidencia científica. El equipo de Spring Boot realizó un ejercicio para reducir el tiempo de inicio de Boot 2.0 y el ticket 11226 contiene mucha información útil. También hay un ticket 7939 abierto para agregar información de tiempo a la evaluación de la condición, pero no parece tener una ETA específica.

El enfoque más útil y metódico para depurar el inicio de arranque lo ha realizado Dave Syer. https://github.com/dsyer/spring-boot-startup-bench

También tuve un caso de uso similar, así que tomé el enfoque de microevaluación comparativa de Dave con JMH y lo seguí. El resultado es el proyecto de referencia de arranque . Lo diseñé de tal manera que pueda usarse para medir el tiempo de inicio de cualquier aplicación Spring Boot, usando el jar ejecutable producido por bootJar(anteriormente llamadobootRepackage en Boot 1.5) la tarea Gradle. Siéntase libre de usarlo y enviar comentarios.

Mis hallazgos son los siguientes:

  1. La CPU importa. Mucho.
  2. Iniciar la JVM con -Xverify: none ayuda significativamente.
  3. La exclusión de configuraciones automáticas innecesarias ayuda.
  4. Dave recomendó el argumento JVM -XX: TieredStopAtLevel = 1 , pero mis pruebas no mostraron una mejora significativa con eso. Además, -XX:TieredStopAtLevel=1probablemente ralentizaría su primera solicitud.
  5. Ha habido informes de que la resolución del nombre de host es lenta, pero no encontré que fuera un problema para las aplicaciones que probé.

1
@ user991710 No estoy seguro de cómo se rompió, pero ya está arreglado. Gracias por el informe.
Abhijit Sarkar

2
Para agregar a esto, ¿podría agregar un ejemplo de cómo alguien podría usar su punto de referencia con una aplicación personalizada? ¿Tiene que agregarse como un proyecto similar minimalo simplemente se puede suministrar el frasco? Intenté hacer lo primero pero no llegué muy lejos.
user991710

1
No ejecute -Xverify:noneen producción ya que rompe la verificación del código y puede tener problemas. -XX:TieredStopAtLevel=1está bien si ejecuta una aplicación durante un período breve (unos segundos), de lo contrario, será menos productiva, ya que proporcionará a la JVM optimizaciones de ejecución prolongada.
loicmathieu

3
el documento de Oracle enumera Use of -Xverify:none is unsupported.¿qué significa?
sakura

1
Muchos grupos (Oracle UCP con seguridad, pero en mis pruebas también Hikari y Tomcat) cifran datos en el grupo. En realidad, no sé si están encriptando la información de conexión o envolviendo la transmisión. Independientemente, el cifrado utiliza la generación de números aleatorios, por lo que tener una fuente de entropía de alto rendimiento y alta disponibilidad hace una diferencia notable en el rendimiento.
Daniel

18

Spring Boot 2.2.M1 ha agregado una función para admitir la inicialización diferida en Spring Boot.

De forma predeterminada, cuando se actualiza un contexto de aplicación, se crean todos los beans del contexto y se inyectan sus dependencias. Por el contrario, cuando se configura una definición de bean para que se inicialice de forma diferida, no se creará y sus dependencias no se inyectarán hasta que se necesite.

Habilitar la inicialización diferida establecida spring.main.lazy-initializationen verdadero

Cuándo habilitar la inicialización diferida La inicialización diferida puede ofrecer mejoras significativas en el tiempo de inicio, pero también existen algunas desventajas notables y es importante habilitarla con cuidado

Para obtener más detalles, consulte Doc.


3
si habilita la inicialización diferida, la primera carga es súper rápida, pero cuando el cliente accede por primera vez, puede notar cierto retraso. Realmente recomiendo esto para el desarrollo, no para la producción.
Isuru Dewasurendra

Como sugirió @IsuruDewasurendra, con razón no es una forma recomendada, puede aumentar significativamente la latencia cuando la aplicación comienza a entregar carga.
Narendra Jaggi

Simplemente patea la lata por el camino.
Abhijit Sarkar

10

Como se describe en esta pregunta / respuesta, creo que el mejor enfoque es, en lugar de agregar solo las que cree que necesita, excluir las dependencias que sabe que no necesita.

Ver: Minimizar el tiempo de inicio de Spring Boot

En resumen:

Puede ver lo que está sucediendo bajo las sábanas y habilitar el registro de depuración tan simple como especificar --debug al iniciar la aplicación desde la línea de comandos. También puede especificar debug = true en su application.properties.

Además, puede establecer el nivel de registro en application.properties tan simple como:

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

Si detecta un módulo configurado automáticamente que no desea, puede deshabilitarlo. Los documentos para esto se pueden encontrar aquí: http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#using-boot-disabling-specific-auto-configuration

Un ejemplo se vería así:

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

4

Bueno, hay una lista completa de posibles acciones descritas aquí: https://spring.io/blog/2018/12/12/how-fast-is-spring

Pondré las notas más importantes del lado de Spring (ajustado un poco):

  • Exclusiones de classpath de los iniciadores web Spring Boot:
    • Validador de hibernación
    • Jackson (pero los actuadores Spring Boot dependen de él). Use Gson si necesita renderizado JSON (solo funciona con MVC de fábrica).
    • Logback: use slf4j-jdk14 en su lugar
  • Utilice el indexador de contexto de primavera. No va a aportar mucho, pero todo ayuda.
  • No utilice actuadores si puede permitirse no hacerlo.
  • Utilice Spring Boot 2.1 y Spring 5.1. Cambie a 2.2 y 5.2 cuando estén disponibles.
  • Corrija la ubicación de los archivos de configuración de Spring Boot con spring.config.location(argumento de línea de comando o propiedad del sistema, etc.). Ejemplo de prueba en IDE:spring.config.location=file://./src/main/resources/application.properties .
  • Apague JMX si no lo necesita spring.jmx.enabled=false(este es el valor predeterminado en Spring Boot 2.2)
  • Hacer que las definiciones de beans sean perezosas por defecto Hay una nueva bandera spring.main.lazy-initialization=trueen Spring Boot 2.2 (se usa LazyInitBeanFactoryPostProcessorpara Spring anterior).
  • Desempaquete el tarro gordo y ejecútelo con un classpath explícito.
  • Ejecute la JVM con -noverify. También considere -XX:TieredStopAtLevel=1(eso ralentizará el JIT más adelante a expensas del tiempo de inicio guardado).

Lo mencionado LazyInitBeanFactoryPostProcessor(puede usarlo para Spring 1.5 si no puede aplicar la bandera spring.main.lazy-initialization=truedisponible desde 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);
      }
  }
}

También puede usar (o escribir el suyo propio, es simple) algo para analizar el tiempo de inicialización de los beans: https://github.com/lwaddicor/spring-startup-analysis

¡Espero eso ayude!


0

En mi caso, hubo demasiados puntos de interrupción. Cuando hice clic en "Mute Breakpoints" y reinicié la aplicación en modo de depuración, la aplicación se inició 10 veces más rápido.


-1

Si está intentando optimizar el tiempo de respuesta del desarrollo para las pruebas manuales, le recomiendo encarecidamente el uso de devtools .

Las aplicaciones que usan spring-boot-devtools se reiniciarán automáticamente cada vez que cambien los archivos en la ruta de clases.

Simplemente vuelva a compilar, y el servidor se reiniciará solo (para Groovy solo necesita actualizar el archivo fuente). si está utilizando un IDE (por ejemplo, 'vscode'), puede compilar automáticamente sus archivos java, por lo que simplemente guardar un archivo java puede iniciar un reinicio del servidor, indirectamente, y Java se vuelve tan fluido como Groovy en este sentido.

La belleza de este enfoque es que el reinicio incremental cortocircuita algunos de los pasos de inicio desde cero, por lo que su servicio volverá a funcionar mucho más rápido.


Desafortunadamente, esto no ayuda con los tiempos de inicio para la implementación o las pruebas unitarias automatizadas.


-1

ADVERTENCIA: Si no usa Hibernate DDL para la generación automática de esquemas de base de datos y no usa la caché L2, esta respuesta NO se aplica a usted. Desplácese hacia adelante.

Mi hallazgo es que Hibernate agrega un tiempo significativo al inicio de la aplicación. La desactivación de la inicialización de la base de datos y el caché L2 da como resultado un inicio más rápido de la aplicación Spring Boot. Deje el caché activado para producción y desactívelo para su entorno de desarrollo.

application.yml:

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

Resultados de la prueba:

  1. La caché L2 está ACTIVADA y 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 caché L2 está desactivada y 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)

Ahora me pregunto que haré con todo este tiempo libre


hibernate.hbm2ddl.auto = la actualización no tiene nada que ver con la caché l2. ddl .. = update especifica escanear el esquema de la base de datos actual y calcular el sql necesario para actualizar el esquema para reflejar sus entidades. 'Ninguno' no realiza esta verificación (además, no intenta actualizar el esquema). Las mejores prácticas son usar una herramienta como liquibase, donde manejará los cambios de su esquema y también podrá rastrearlos.
Radu Toader

@RaduToader esta pregunta y mi respuesta tratan de acelerar el tiempo de inicio de Spring Boot. No tienen nada que ver con la discusión de Hibernate DDL vs Liquibase; estas herramientas tienen sus pros y sus contras. Mi punto es que podemos deshabilitar la actualización del esquema de base de datos y habilitarla solo cuando sea necesario. Hibernate toma un tiempo significativo en el inicio incluso cuando el modelo no cambió desde la última ejecución (para comparar el esquema de base de datos con el esquema generado automáticamente). Lo mismo ocurre con la caché L2.
naXa

sí, lo sé, pero mi punto es que es un poco peligroso no explicar lo que realmente hace. Es muy fácil que termine con su base de datos vacía.
Radu Toader

@RaduToader Había un enlace a una página de documentación sobre la inicialización de la base de datos en mi respuesta. ¿Lo leíste? Contiene una guía exhaustiva que enumera todas las herramientas más populares (Hibernate y Liquibase, así como JPA y Flyway). También hoy agrego una advertencia clara al principio de mi respuesta. ¿Crees que necesito otros cambios para explicar las consecuencias?
naXa

Perfecto. Gracias
Radu Toader

-3

Me parece extraño que nadie sugiriera estas optimizaciones antes. A continuación, se ofrecen algunos consejos generales sobre cómo optimizar la creación y el inicio del proyecto durante el desarrollo:

  • excluir directorios de desarrollo del escáner antivirus:
    • directorio del proyecto
    • construir directorio de salida (si está fuera del directorio del proyecto)
    • Directorio de índices IDE (por ejemplo, ~ / .IntelliJIdea2018.3)
    • directorio de implementación (aplicaciones web en Tomcat)
  • actualizar el hardware. use CPU y RAM más rápidas, mejor conexión a Internet (para descargar dependencias) y conexión a la base de datos, cambie a SSD. una tarjeta de video no importa.

ADVERTENCIAS

  1. la primera opción viene por el precio de seguridad reducida.
  2. la segunda opción cuesta dinero (obviamente).

La cuestión es mejorar el tiempo de arranque, no el tiempo de compilación.
ArtOfWarfare

@ArtOfWarfare volvió a leer la pregunta. la pregunta plantea el problema como "No estoy contento de que lleve tanto [tiempo], principalmente porque interrumpe el flujo de desarrollo". Sentí que este es un problema principal y lo abordé en mi respuesta.
naXa

-9

A mí me parece que estás usando una configuración incorrecta. Empiece por comprobar myContainer y los posibles conflictos. Para determinar quién está usando la mayor cantidad de recursos, debe verificar los mapas de memoria (¡vea la cantidad de datos!) Para cada dependencia a la vez, y eso también requiere mucho tiempo ... (y privilegios SUDO). Por cierto: ¿suele probar el código con las dependencias?

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.