Przyspiesz czas uruchamiania Spring Boot

115

Mam aplikację Spring Boot. Dodałem sporo zależności (niestety, wygląda na to, że potrzebuję ich wszystkich) i czas uruchamiania znacznie się wydłużył. Samo wykonanie SpringApplication.run(source, args)zajmuje 10 sekund.

Chociaż może to nie być dużo w porównaniu do tego, do czego są „przyzwyczajeni”, jestem niezadowolony, że zajmuje to tyle czasu, głównie dlatego, że przerywa to proces programowania. Sama aplikacja jest w tym momencie raczej mała, więc zakładam, że większość czasu jest związana z dodanymi zależnościami, a nie z samymi klasami aplikacji.

Zakładam, że problemem jest skanowanie ścieżek klas, ale nie wiem, jak:

  • Potwierdź, że na tym polega problem (tj. Jak „debugować” Spring Boot)
  • Jeśli to naprawdę jest przyczyna, jak mogę to ograniczyć, żeby przyspieszyło? Na przykład, jeśli wiem, że pewna zależność lub pakiet nie zawiera niczego, co powinien skanować Spring, czy istnieje sposób, aby to ograniczyć?

Zakładam, że rozszerzenie Springa, aby miał równoległą inicjalizację fasoli podczas uruchamiania , przyspieszyłoby rzeczy, ale to żądanie ulepszenia jest otwarte od 2011 roku i nie ma żadnego postępu. Widzę inne wysiłki w samym Spring Boot, takie jak ulepszenia prędkości Investigate Tomcat JarScanning , ale jest to specyficzne dla Tomcat i zostało porzucone.

Ten artykuł:

chociaż ma na celu testy integracyjne, sugeruje użycie lazy-init=true, jednak nie wiem, jak zastosować to do wszystkich fasoli w Spring Boot przy użyciu konfiguracji Java - jakieś wskazówki tutaj?

Wszelkie (inne) sugestie będą mile widziane.

stały deszcz
źródło
Prześlij swój kod. Zwykle skanowany jest tylko pakiet zdefiniowany przez moduł uruchamiający aplikację. Jeśli masz zdefiniowane inne pakiety, @ComponentScanrównież są skanowane. Inną rzeczą jest upewnienie się, że nie włączyłeś rejestrowania debugowania lub śledzenia, ponieważ ogólnie rejestrowanie jest powolne, bardzo wolne.
M. Deinum
Jeśli używasz Hibernacji, zwykle zużywa dużo czasu na starcie aplikacji.
Knut Forkalsrud
Automatyczne wiązanie Springa według typu w połączeniu z fasolą fabryczną może być powolne, gdy dodajesz dużo ziaren i zależności.
Knut Forkalsrud
Lub możesz użyć buforowania, spring.io/guides/gs/caching
Cassian
2
Dziękuję wszystkim za komentarze - niestety nie byłbym w stanie opublikować kodu (dużo wewnętrznych plików jar), jednak wciąż szukam sposobu na debugowanie tego. Tak, mogę używać A lub B albo robić X lub Y, co spowalnia. Jak to ustalić? Jeśli dodam zależność X, która ma 15 zależności przechodnich, skąd mam wiedzieć, które z tych 16 spowolniło ją? Jeśli się dowiem, czy jest coś, co mogę zrobić później, aby powstrzymać Springa przed zbadaniem ich? Takie wskaźniki byłyby przydatne!
stały deszcz

Odpowiedzi:

61

Spring Boot wykonuje wiele automatycznych konfiguracji, które mogą nie być potrzebne. Możesz więc zawęzić tylko automatyczną konfigurację potrzebną dla Twojej aplikacji. Aby zobaczyć pełną listę automatycznej konfiguracji, po prostu uruchom logowanie org.springframework.boot.autoconfigurew trybie DEBUG ( logging.level.org.springframework.boot.autoconfigure=DEBUGin application.properties). Inną opcją jest uruchomienie aplikacji Spring Boot z --debugopcją:java -jar myproject-0.0.1-SNAPSHOT.jar --debug

Na wyjściu byłoby coś takiego:

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

Przejrzyj tę listę i uwzględnij tylko potrzebne autokonfiguracje:

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

Kod został skopiowany z tego posta na blogu .

luboskrnac
źródło
1
mierzyłeś to ??? Czy było dużo szybciej? Moim zdaniem jest to wyjątkowy przypadek, o wiele ważniejszy, aby upewnić się, że pamięć podręczna kontekstu testu Spring działa
idmitriev
@idmitriev Właśnie zmierzyłem to w mojej aplikacji i moja aplikacja uruchomiła się po 53 sekundach, podczas gdy bez wykluczania klas autokonfiguracji było to 73 sekundy. Wykluczyłem jednak znacznie więcej klas niż wymienione powyżej.
apkisbossin
Miło jest zaimportować całą konfigurację. Jak radzić sobie z BatchConfigurerConfiguration.JpaBatchConfiguration czy dodać zależność do projektu? Jak radzić sobie z metodami referencyjnymi, takimi jak ConfigurationPropertiesRebinderAutoConfiguration # configurationPropertiesBeans?
user1767316
Jak radzić sobie z prywatnymi klasami konfiguracji?
user1767316
44

Jak dotąd, najczęściej głosowana odpowiedź nie jest błędna, ale nie jest ona głęboka, którą lubię widzieć i nie dostarcza żadnych dowodów naukowych. Zespół Spring Boot przeszedł ćwiczenie mające na celu skrócenie czasu uruchamiania Boot 2.0, a bilet 11226 zawiera wiele przydatnych informacji. Istnieje również zgłoszenie 7939 umożliwiające dodawanie informacji o czasie do oceny stanu, ale wydaje się, że nie ma on określonego czasu ETA.

Najbardziej przydatne i metodyczne podejście do debugowania uruchamiania podczas rozruchu zostało wykonane przez Dave'a Syera. https://github.com/dsyer/spring-boot-startup-bench

Miałem również podobny przypadek użycia, więc przyjąłem podejście Dave'a do mikro-benchmarkingu z JMH i zacząłem z nim. Rezultatem jest projekt testu porównawczego rozruchu . Zaprojektowałem go w taki sposób, aby można go było wykorzystać do pomiaru czasu uruchamiania dowolnej aplikacji Spring Boot, używając wykonywalnego pliku jar utworzonego przez bootJar(wcześniej zwane bootRepackagew Boot 1.5) zadanie Gradle. Zapraszam do korzystania z niego i przekazywania opinii.

Moje ustalenia są następujące:

  1. CPU ma znaczenie. Dużo.
  2. Uruchomienie maszyny JVM z -Xverify: none znacząco pomaga.
  3. Pomaga wykluczenie niepotrzebnych autokonfiguracji.
  4. Dave zalecił argument JVM -XX: TieredStopAtLevel = 1 , ale moje testy nie wykazały znaczącej poprawy. Ponadto -XX:TieredStopAtLevel=1prawdopodobnie spowolniłoby twoją pierwszą prośbę.
  5. Pojawiły się doniesienia o powolnym rozpoznawaniu nazwy hosta, ale nie stwierdziłem, że stanowi to problem dla testowanych aplikacji.
Abhijit Sarkar
źródło
1
@ user991710 Nie jestem pewien, jak się zepsuł, ale teraz jest naprawiony. Dzięki za raport.
Abhijit Sarkar
2
Aby to dodać, czy mógłbyś dodać przykład, w jaki sposób ktoś może wykorzystać Twój test porównawczy w niestandardowej aplikacji? Czy trzeba go dodać jako projekt podobny do minimal, czy może po prostu słoik być dostarczony? Próbowałem zrobić to pierwsze, ale nie zaszedłem zbyt daleko.
user991710
1
Nie uruchamiaj -Xverify:nonena produkcji, ponieważ przerywa to weryfikację kodu i możesz wpaść w kłopoty. -XX:TieredStopAtLevel=1jest OK, jeśli uruchomisz aplikację na krótki czas (kilka sekund), w przeciwnym razie będzie mniej produktywna, ponieważ zapewni JVM długotrwałe optymalizacje.
loicmathieu
3
dokument wyroczni wymienia, Use of -Xverify:none is unsupported.co to oznacza?
sakura
1
Wiele pul (na pewno Oracle UCP, ale w moich testach również Hikari i Tomcat) szyfruje dane w puli. Właściwie nie wiem, czy szyfrują informacje o połączeniu, czy zawijają strumień. Niezależnie od tego, szyfrowanie wykorzystuje generowanie liczb losowych, więc posiadanie źródła entropii o wysokiej dostępności i wysokiej przepustowości powoduje zauważalną różnicę w wydajności.
Daniel
19

Spring Boot 2.2.M1 ma dodaną funkcję do obsługi Lazy Initialization w Spring Boot.

Domyślnie podczas odświeżania kontekstu aplikacji jest tworzony każdy komponent bean w kontekście, a jego zależności są wstrzykiwane. Z drugiej strony, jeśli definicja fasoli jest skonfigurowana do leniwego inicjowania, nie zostanie utworzona, a jej zależności nie zostaną wstrzyknięte, dopóki nie będą potrzebne.

Włączanie Lazy Initialization Ustawiono spring.main.lazy-initializationna true

Kiedy włączyć leniwą inicjalizację

leniwa inicjalizacja może zapewnić znaczną poprawę czasu uruchamiania, ale są też pewne istotne wady i ważne jest, aby włączyć ją ostrożnie

Więcej informacji można znaleźć w Doc

Niraj Sonawane
źródło
3
jeśli włączysz leniwą inicjalizację, ładowanie za pierwszym razem będzie super szybkie, ale gdy klient uzyskuje dostęp po raz pierwszy, może zauważyć pewne opóźnienie. Naprawdę polecam to do rozwoju, a nie do produkcji.
Isuru Dewasurendra
Jak zasugerował @IsuruDewasurendra, słusznie nie jest to zalecany sposób, może znacznie zwiększyć opóźnienie, gdy aplikacja zacznie obsługiwać ładowanie.
Narendra Jaggi
Po prostu kopie puszkę w dół drogi.
Abhijit Sarkar
10

Jak opisano w tym pytaniu / odpowiedzi, myślę, że najlepszym podejściem jest dodanie tylko tych, których Twoim zdaniem potrzebujesz, wykluczenie zależności, o których wiesz, że nie potrzebujesz.

Zobacz: Minimalizuj czas uruchamiania rozruchu sprężynowego

W podsumowaniu:

Możesz zobaczyć, co się dzieje pod okładkami i włączyć rejestrowanie debugowania tak proste, jak określenie --debug podczas uruchamiania aplikacji z wiersza poleceń. Możesz również określić debug = true w pliku application.properties.

Możesz również ustawić poziom rejestrowania w application.properties tak prosto, jak:

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

Jeśli wykryjesz automatycznie skonfigurowany moduł, którego nie chcesz, możesz go wyłączyć. Dokumentację na ten temat można znaleźć tutaj: http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#using-boot-disiring-specific-auto-configuration

Przykład wyglądałby tak:

@Configuration
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
public class MyConfiguration {
}
pczeus
źródło
4

Cóż, jest cała lista możliwych działań opisanych tutaj: https://spring.io/blog/2018/12/12/how-fast-is-spring

Najważniejsze notatki umieszczę od strony wiosny (trochę poprawione):

  • Wykluczenia ścieżki klas ze starterów internetowych Spring Boot:
    • Hibernate Validator
    • Jackson (ale siłowniki Spring Boot zależą od tego). Użyj Gson, jeśli potrzebujesz renderowania JSON (działa tylko z MVC po wyjęciu z pudełka).
    • Logback: zamiast tego użyj slf4j-jdk14
  • Użyj Spring-Context Indexer. To niewiele doda, ale każda odrobina pomoże.
  • Nie używaj siłowników, jeśli możesz sobie na to pozwolić.
  • Użyj Spring Boot 2.1 i Spring 5.1. Przejdź do wersji 2.2 i 5.2, gdy będą dostępne.
  • Popraw lokalizację plików konfiguracyjnych Spring Boot za pomocą spring.config.location(argument wiersza poleceń lub właściwość System itp.). Przykład badania ide: spring.config.location=file://./src/main/resources/application.properties.
  • Wyłącz JMX, jeśli go nie potrzebujesz spring.jmx.enabled=false(jest to domyślne ustawienie w Spring Boot 2.2)
  • Domyślnie ustawia leniwość w definicjach. W spring.main.lazy-initialization=trueSpring Boot 2.2 pojawiła się nowa flaga (użyj LazyInitBeanFactoryPostProcessordla starszej wersji Spring).
  • Rozpakuj słoik z tłuszczem i uruchom z wyraźną ścieżką klas.
  • Uruchom JVM z -noverify. Rozważ również -XX:TieredStopAtLevel=1(to spowolni później JIT kosztem zaoszczędzonego czasu uruchamiania).

Wspomniane LazyInitBeanFactoryPostProcessor(możesz go użyć na wiosnę 1.5, jeśli nie możesz zastosować flagi spring.main.lazy-initialization=truedostępnej od wiosny 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);
      }
  }
}

Możesz także użyć (lub napisać własne - to proste) czegoś do analizy czasu inicjalizacji ziaren: https://github.com/lwaddicor/spring-startup-analysis

Mam nadzieję, że to pomoże!

Przemek Nowak
źródło
0

W moim przypadku było zbyt wiele punktów przerwania. Kiedy kliknąłem „Mute Breakpoints” i ponownie uruchomiłem aplikację w trybie debugowania, aplikacja uruchomiła się 10 razy szybciej.

Daulet Kadirbekov
źródło
-1

Jeśli próbujesz zoptymalizować cykl rozwoju pod kątem testowania ręcznego, zdecydowanie polecam użycie narzędzi devtools .

Aplikacje korzystające z spring-boot-devtools zostaną automatycznie uruchomione ponownie, gdy zmienią się pliki w ścieżce klas.

Po prostu przekompiluj - a serwer uruchomi się ponownie (w przypadku Groovy wystarczy zaktualizować plik źródłowy). jeśli używasz IDE (np. „vscode”), może ono automatycznie skompilować twoje pliki java, więc samo zapisanie pliku java może zainicjować restart serwera, pośrednio - a Java staje się tak samo płynna jak Groovy pod tym względem.

Piękno tego podejścia polega na tym, że przyrostowe ponowne uruchamianie powoduje zwarcie niektórych etapów uruchamiania od zera - dzięki czemu usługa będzie mogła zostać utworzona i uruchomiona znacznie szybciej!


Niestety nie pomaga to w czasie uruchamiania w przypadku wdrażania lub zautomatyzowanych testów jednostkowych.

Brent Bradburn
źródło
-1

OSTRZEŻENIE: Jeśli nie używasz Hibernate DDL do automatycznego generowania schematu bazy danych i nie używasz pamięci podręcznej L2, ta odpowiedź NIE dotyczy Ciebie. Przewiń do przodu.

Moje odkrycie jest takie, że Hibernate znacznie wydłuża czas uruchamiania aplikacji. Wyłączenie pamięci podręcznej L2 i inicjalizacji bazy danych powoduje szybsze uruchamianie aplikacji Spring Boot. Pozostaw pamięć podręczną WŁĄCZONĄ dla produkcji i wyłącz ją dla swojego środowiska programistycznego.

application.yml:

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

Wyniki testu:

  1. Pamięć podręczna L2 jest WŁĄCZONA i 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. Pamięć podręczna L2 jest WYŁĄCZONA i 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)

Teraz zastanawiam się, co będę robić z całym tym wolnym czasem

naXa
źródło
hibernate.hbm2ddl.auto = aktualizacja nie ma nic wspólnego z pamięcią podręczną L2. ddl .. = aktualizacja określa skanowanie bieżącego schematu bazy danych i obliczenie niezbędnego kodu sql w celu zaktualizowania schematu w celu odzwierciedlenia jednostek. „Brak” nie przeprowadza tej weryfikacji (również nie próbuje aktualizować schematu). Najlepszą praktyką jest użycie narzędzia takiego jak liquibase, w którym będziesz obsługiwać zmiany schematu, a także możesz je śledzić.
Radu Toader
@RaduToader to pytanie i moja odpowiedź dotyczą przyspieszenia uruchamiania Spring Boot. Nie mają one nic wspólnego z dyskusją między Hibernate DDL a Liquibase; te narzędzia mają swoje wady i zalety. Chodzi mi o to, że możemy wyłączyć aktualizację schematu DB i włączyć tylko wtedy, gdy jest to konieczne. Hibernacja zajmuje dużo czasu podczas uruchamiania, nawet jeśli model nie zmienił się od ostatniego uruchomienia (do porównania schematu bazy danych ze schematem generowanym automatycznie). To samo dotyczy pamięci podręcznej L2.
naXa
tak, wiem o tym, ale chodziło mi o to, że niewyjaśnienie tego, co naprawdę robi, jest trochę niebezpieczne. Możesz bardzo łatwo skończyć z pustą bazą danych.
Radu Toader
@RaduToader W mojej odpowiedzi był link do strony z dokumentacją o inicjalizacji DB. Przeczytałeś to? Zawiera wyczerpujący przewodnik z listą wszystkich najpopularniejszych narzędzi (Hibernate i Liquibase, a także JPA i Flyway). Również dzisiaj dodaję wyraźne ostrzeżenie na początku mojej odpowiedzi. Czy myślisz, że potrzebuję innych zmian, aby wyjaśnić konsekwencje?
naXa
Idealny. Dziękuję
Radu Toader
-3

Wydaje mi się dziwne, że nikt wcześniej nie sugerował tych optymalizacji. Oto kilka ogólnych wskazówek dotyczących optymalizacji kompilacji i uruchamiania projektu podczas tworzenia:

  • wyklucz katalogi programistyczne ze skanera antywirusowego:
    • katalog projektu
    • buduj katalog wyjściowy (jeśli znajduje się poza katalogiem projektu)
    • Katalog indeksów IDE (np. ~ / .IntelliJIdea2018.3)
    • katalog wdrożeniowy (aplikacje internetowe w Tomcat)
  • Zaktualizuj sprzęt. użyj szybszego procesora i pamięci RAM, lepszego połączenia internetowego (do pobierania zależności) i połączenia z bazą danych, przełącz się na dysk SSD. karta graficzna nie ma znaczenia.

OSTRZEŻENIA

  1. pierwsza opcja ma cenę obniżonego bezpieczeństwa.
  2. druga opcja kosztuje (oczywiście).
naXa
źródło
Pytanie dotyczy skrócenia czasu rozruchu, a nie czasu kompilacji.
ArtOfWarfare
@ArtOfWarfare ponownie przeczytaj pytanie. pytanie określa problem jako „Jestem niezadowolony, że zajmuje to tyle [czasu], głównie dlatego, że przerywa to przepływ rozwoju”. Czułem, że jest to główny problem i odniosłem się do niego w mojej odpowiedzi.
naXa
-9

Wydaje mi się, że używasz złego ustawienia konfiguracji. Zacznij od sprawdzenia myContainer i możliwych konfliktów. Aby określić, kto używa najwięcej zasobów, musisz sprawdzić mapy pamięci (zobacz ilość danych!) Dla każdej zależności naraz - i to również zajmuje dużo czasu ... (i uprawnienia SUDO). Przy okazji: czy zazwyczaj testujesz kod pod kątem zależności?


źródło