如何預熱Spring Boot應用? - sebstein

banq發表於2021-11-02

Spring Boot 是用於開發 Java 和 Kotlin 後端的成熟工具。如果您重新啟動這樣的後端,第一個 REST 呼叫總是需要很長時間。我研究了為什麼會這樣,以及如何在應用程式啟動時對其進行預熱,以便快速處理第一個請求。

在Spring Boot 應用程式啟動期間,JVM載入各種類。通過第一個REST 請求,JVM 再次載入許多類,但不會通過對 REST 端點的任何進一步呼叫載入更多類。

Spring Boot 中的第一個請求很慢,那麼為什麼第一個請求必須快?API 有約定的響應時間,此外,應用程式每週更新幾次,有多個例項,這導致每週有大量的“第一次請求”。

我現在應該如何預熱應用程式?第一個天真的想法是在啟動時簡單地呼叫 REST API 的所有端點,以便載入所有類。

實際上,這很困難。

解決方案:特殊端點來預熱 Spring Boot:

在我的實驗中,我注意到只有對任何端點的第一個請求很慢。對另一個端點的下一個請求要快得多。基於這個觀察,我萌生了寫一個特殊的REST端點,只用於熱身的想法。

這個特殊端點就是:在端點中使用一個 DTO,能包含經常使用的資料型別如String、BigInteger 等,還包括經常使用的用於驗證的註釋:

例如,下面是使用WarmUpController加上相關的請求和響應 DTO:

public class WarmUpRequestDto {
    @NotBlank
    @Pattern(regexp = "warm me up")
    private String warmUpString;

    @Min(10)
    @Max(20)
    private int warmUpNumber;

    @Valid
    private WarmUpEnumDto warmUpEnumDto;

    @NotNull
    private BigDecimal warmUpBigDecimal;
...
}

WarmUpRequestDto 中,有在普通端點中使用的所有資料型別,並且還使用了一個Enum 類。所有屬性都進行了註釋以進行驗證。

如何自動啟用這個Rest端點?

在等待ApplicationReadyEvent的PreloadComponent中實現了一個事件偵聽器。ApplicationReadyEvent 是當應用程式從 Spring 的角度完全啟動時由 Spring 生成的。在函式sendWarmUpRestRequest () 中,我使用Spring WebClient向我的WarmUpController傳送一個 REST 請求。

private void sendWarmUpRestRequest() {
    final String serverPort = environment.getProperty("local.server.port");
    final String baseUrl = "http://localhost:" + serverPort;
    final String warmUpEndpoint = baseUrl + "/warmup";

    logger.info("Sending REST request to force initialization of Jackson...");

    final String response = webClientBuilder.build().post()
            .uri(warmUpEndpoint)
            .header(CONTENT_TYPE, APPLICATION_JSON_VALUE)
            .body(Mono.just(createSampleMessage()), WarmUpRequestDto.class)
            .retrieve()
            .bodyToMono(String.class)
            .timeout(Duration.ofSeconds(5))
            .block();

    logger.info("...done, response received: " + response);
}

GitHub 上的示例程式碼中,我註釋掉了對onApplicationEvent(ApplicationReadyEvent 事件)函式的呼叫。

有興趣的讀者如果想看懂,必須去掉這個註釋,重新編譯啟動應用程式:

mvn package
...
java -verbose:class -jar target/warm-me-up*.jar

幾秒鐘後,我的終端平靜下來,我再次刪除了內容。現在我使用 curl 再次執行第一個請求。第一次呼叫只用了 37 毫秒,而不是未優化的 200 毫秒。

Spring Boot 中還需要預熱什麼?

我的示例應用程式現在已充分預熱。當然,在現實中,事情更復雜,需要做更多的工作。還必須預熱以下元件,以便儘快處理第一個請求:

  • 建立資料庫連線並進行查詢
  • 建立與 Kafka 等訊息代理的連線
  • 例項化其他解析庫,例如用於 XML 處理的 JAXB
  • 載入本機 C 庫

修復這些問題中的任何一個都可能需要幾天的工作。但是很好,多虧了 JVM 選項:

-verbose:class

這個引數可以檢視 JVM 何時載入了哪些類!

 

相關文章