Spring Boot 2.3.0配置Graceful-Shutdown,Readiness和Liveness

原文發表於kubernetes中文社區,爲作者原創翻譯 ,原文地址

更多kubernetes文章,請多關注kubernetes中文社區

目錄

背景:容器被廣泛使用

優雅停機( Gracefully Shutdown )

存活狀態( Liveness )

就緒狀態( Readiness )

1.如何更新狀態?

2.如何獲取應用狀態?

示例:Spring Boot 2.3.0配置Readiness和Liveness

代碼示例

測試:Readiness和Liveness


Spring Boot團隊最近發佈了2.3.0版本,該版本具有許多增強功能,升級功能。(具體可以參考,Spring Boot 2.3.0 發行說明)。

在早些時候,Spring團隊就已經宣佈2.3版本將專注於Kubernetes。考慮到這一點,本文我們將嘗試探索其中一些功能。

背景:容器被廣泛使用

衆所周知,容器是獨立的單元,它是將應用程序及其依賴項打包爲一個單元。因此,應用程序可以實現從一個環境快速可靠地遷移運行到另一個環境。*只要不同環境中存在兼容的容器運行時,就可以滿足容器的可移植性,。

容器是操作系統虛擬化的一種,它包含運行應用程序所需的一切:可執行二進制文件,類庫,配置文件,配置文件和系統庫。與服務器虛擬化相比,容器不爲每個虛擬環境提供單獨的操作系統,容器使用主機上的操作系統。

由於對容器的前所未有的使用,對諸如KubernetesDocker Swarm之類的容器編排和管理工具的需求也在增加。這些工具通過許多功能簡化了我們管理容器的工作,這些功能包括:

  • 容器分組 ( Container grouping )
  • 自我修復
  • 自動擴展
  • DNS管理
  • 負載均衡
  • 滾動更新或回滾
  • 資源監控和記錄

幾乎所有功能都圍繞應用程序(容器)提供以下方面的狀態:

  • 優雅停機( Gracefully Shutdown )
  • 存活狀態( Liveness )
  • 就緒狀態( Readiness )

讓我們詳細研究它們中的每一個,以及如何在啓動Spring Boot 2.3.0中實現它們。

優雅停機( Gracefully Shutdown )

通常,優雅停機意味着在關閉應用程序之前,應設置超時期限,以允許仍在進行中的請求操作完成。在此超時期間,將不允許新請求。這將使你在應用請求處理方面保持一致,即沒有未處理請求。

Spring Boot 2.3.0.RELEASE引入了Graceful Shutdown的功能。其中所有四個嵌入式Web服務器(Tomcat,Undertow,Netty和Jetty)都爲響應式和基於Servlet的Web應用程序提供優雅停機功能。優雅停機是關閉應用程序上下文的一部分,並且在SmartLifecycle bean停止的最早階段執行。

應用程序在寬限期內停止新請求的恰當方式,取決於所使用的服務器。根據官方文檔Tomcat,Jetty和Reactor Netty將在網絡層停止接受請求。Undertow將接受請求,但立即會以HTTP 503(服務不可用)來響應。

請注意,Tomcat 9.0.33或更高版本,才具備優雅停機功能。

在Spring Boot 2.3.0中,優雅停機非常容易實現,並且可以通過在應用程序配置文件中設置兩個屬性來進行管理。

  1. server.shutdown:此屬性可以支持的值有
    1. immediate:這是默認值,將導致服務器立即關閉。
    2. graceful:啓用優雅停機,並遵守spring.lifecycle.timeout-per-shutdown-phase屬性中給出的超時。
  2. spring.lifecycle.timeout-per-shutdown-phase:採用java.time.Duration格式的值。

例如:

Properties 文件

# Enable gracefule shutdown
server.shutdown=graceful
# Allow grace timeout period for 20 seconds
spring.lifecycle.timeout-per-shutdown-phase=20s
# Force enable health probes. Would be enabled on kubernetes platform by default
management.health.probes.enabled=true

現在,當我們配置了優雅停機時,可能會有兩種可能性:

  1. 應用中沒有正在進行的要求。在這種情況下,應用程序將會直接關閉,而無需等待寬限期結束後才關閉。
  2. 如果應用中有正在處理的請求,則應用程序將等待寬限期結束後才能關閉。如果應用在寬限期之後仍然有待處理的請求,應用程序將拋出異常並繼續強制關閉。

 

存活狀態( Liveness )

就應用程序而言,存活狀態是指應用程序的狀態是否正常。如果存活狀態不正常,則意味着應用程序本身已損壞,無法恢復。在Kubernetes中,如果存活探針檢測失敗,則kubelet將殺死Container,並且Container將接受其重新啓動策略。如果容器未提供存活探針,則默認狀態爲“ Success ”。

Spring Boot 2.3.0引入了org.springframework.boot.availability.LivenessState。可用狀態爲

  1. CORRECT :該應用程序正在運行,並且其內部狀態正常。
  2. BROKEN:應用程序正在運行,但內部狀態被打破。

就緒狀態( Readiness )

就緒狀態,指的是應用程序是否已準備好接受並處理客戶端請求。出於任何原因,如果應用程序尚未準備好處理服務請求,則應將其聲明爲繁忙,直到能夠正常響應請求爲止。如果“Readiness”狀態尚未就緒,則不應將流量路由到該實例。

例如,在Kubernetes中,如果就緒探針失敗,則 Endpoints 控制器將從與Endpoints中刪除Pod的IP地址。設置就緒狀態爲“Failure”。如果容器未提供就緒探針,則默認狀態爲“Success”。

Spring Boot在ReadinessState的幫助下引入了就緒狀態。可以設置的值有:

  1. ACCEPTING_TRAFFIC:應用程序準備好接收流量。這是默認值。
  2. REFUSING_TRAFFIC:應用程序拒絕接收流量。

現在,當我們瞭解概念後,腦海中會有兩個問題。

1.如何更新狀態?

Spring Boot選擇了Spring應用程序事件模型來更改可用性狀態。Spring Boot還配置了ApplicationAvailabilityBean類型的Bean,它是ApplicationAvailability接口的實現。該bean監聽這些事件並保持最新狀態。因此,我們使用ApplicationAvailability來獲取應用程序的狀態

// Available as a component in the application context
ApplicationAvailability availability;
LivenessState livenessState = availability.getLivenessState();
ReadinessState readinessState = availability.getReadinessState();

我們還可以使用AvailabilityChangeEvent來更新狀態。

2.如何獲取應用狀態?

最常見的用例是部署支持探針的Web應用程序。Spring boot Actuator是唯一需要的依賴項。因爲Spring Boot Actuator已經公開了應用程序運行狀況的端點( endpoint )。

啓動Spring Boot 2.3.0 Actuator還將在Health指示器中公開可用性狀態。這些指標將全部顯示在“/actuator/health*”上。這些健康狀況端點也可以作爲單獨的HTTP端點使用:“ /actuator/health/liveness ”和“ /actuator/health/readiness ”。

在Kubernetes平臺上運行時,默認情況下這些指標( indicators)包括在actuator/health的endpoint中,而在另一個平臺上則不可用。但是,你始終可以通過將management.health.probes.enabled屬性設置爲true來覆蓋此行爲。

Properties 文件

# Force enable health probes. Enabled on Kubernetes platform by default
management.health.probes.enabled=true

請注意,所有與可用性相關的組件都org.springframework.boot.availability軟件包的一部分。

示例:Spring Boot 2.3.0配置Readiness和Liveness

在我們編寫代碼之前,請記住

  • 使用ApplicationAvailability獲取應用程序的狀態。
  • 發佈AvailabilityChangeEvent來更新狀態。

出於演示的目的,我將在單個RestController中操作,並使用HTTP GET端點調用它們。在理想情況下,這些將由負責管理應用程序不同組件的bean完成。例如緩存,如果緩存失敗,則你的緩存bean可以發佈ReadinessState.REFUSING_TRAFFIC以拒絕請求到達此應用程序。

代碼示例

下面的代碼僅用於演示如何根據需要更改狀態。不能在生產中使用。

package org.sk.ms.probes;
​
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.LivenessState;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/** 
 * This is sample code to display how to use probes available in spring boot 2.3.0. Not to be used in production. This must be updated by {@link Component} beans for example caching or connection revalidators
 */
@RestController
public class ExampleController {
    private final Logger logger = org.slf4j.LoggerFactory.getLogger(ExampleController.class);
​
    @Autowired
    private  ApplicationEventPublisher eventPublisher;
​
    public ExampleController(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }
​
    @GetMapping("/complete-normally")
    public String completeNormally() throws Exception {
        return "Hello from Controller";
    }
    
    @GetMapping("/i-will-sleep-for-30sec")
    public String destroy() throws Exception {
        logger.info("------------------ Sleeping for 30 sec");
        Thread.sleep(30000);
        return "sleep complete";
    }
​
    @GetMapping("/readiness/accepting")
    public String markReadinesAcceptingTraffic() {
        AvailabilityChangeEvent.publish(eventPublisher, this, ReadinessState.ACCEPTING_TRAFFIC);
        return "Readiness marked as ACCEPTING_TRAFFIC";
    }
​
    @GetMapping("/readiness/refuse")
    public String markReadinesRefusingTraffic() {
        AvailabilityChangeEvent.publish(eventPublisher, this, ReadinessState.REFUSING_TRAFFIC);
        return "Readiness marked as REFUSING_TRAFFIC";
    }
​
    @GetMapping("/liveness/correct")
    public String markLivenessCorrect() {
        AvailabilityChangeEvent.publish(eventPublisher, this, LivenessState.CORRECT);
        return "Liveness marked as CORRECT";
    }
​
    @GetMapping("/liveness/broken")
    public String markLivenessBroken() {
        AvailabilityChangeEvent.publish(eventPublisher, this, LivenessState.BROKEN);
        return "Liveness marked as BROKEN";
    }
}

測試:Readiness和Liveness

1.檢查可用端點

檢查可用端點的初始值:

 

2.寬限期(沒有正在進行中的請求)

我們將寬限期設置爲20秒。而且沒有正在進行中的請求。觀察日誌。

 

3.寬限期已過

我們有20秒的寬限期。讓我們發出一個需要30秒才能完成的請求,然後嘗試停止該應用程序。

在這種情況下,應用程序將因錯誤而關閉。注意觀察日誌。

 

4.將存活狀態更新爲“BROKEN”,然後將其設置爲“CORRECT”

默認情況下,存活狀態是由Spring應用程序上下文設置的。

存活狀態,一旦標記爲 correct ,它將反映在運行狀況端點中,並且容器管理器將知道此實例。

例如,你的應用程序依賴於緩存,並且無法刷新緩存,應用程序的存活狀態就會被標記爲 broken ,容器管理器會基於配置採取適當的措施處理。

 

5.將就緒狀態設置爲REFUSING_TRAFFIC,並在恢復後返回ACCEPTING_TRAFFIC

一旦你的應用程序準備好處理請求,可以將就緒狀態設置爲REFUSING_TRAFFIC。

例如,在緩存更新時,應該將應用程序標記爲REFUSING_TRAFFIC,否則可能會處理過時的數據。在這種情況下,容器管理器應停止向該實例發送流量。

但請記住,恢復後應將狀態標記回ACCEPTING_TRAFFIC,以便可以將流量再次路由到該實例。

 

你可以在此GitHub倉庫找到以上示例的完整代碼。

譯文鏈接: https://dzone.com/articles/configuring-graceful-shutdown-readiness-and-livene

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章