Spring boot 2.3優雅下線,距離生產還有多遠?

簡介: 對於任何一個線上應用,如何在服務更新部署過程當中保證業務無感知是開發者必需要解決的問題,即從應用中止到重啓恢復服務這個階段不能影響正常的業務請求,這使得無損下線成爲應用生命週期中必不可少的一個環節。java

前言

在生產環境中,隨着雲原生架構的發展,自動的彈性伸縮、滾動升級、分批發布等雲原生能力讓用戶享受到了資源、成本、穩定性的最優解。可是在應用的縮容、發佈等過程當中,因爲實例下線處理得不夠優雅,將會致使短暫的服務不可用,短期內業務監控會出現大量 io 異常報錯;若是業務沒作好事務,那麼還會引發數據不一致的問題,那麼須要緊急手動訂正錯誤數據;甚至每次發佈,您須要發告示停機發布,不然您的用戶會出現一段時間服務不可用。沒處理好服務實例下線,不管發生上述哪一種狀況,都會對您業務的連續性形成困擾。react

對於任何一個線上應用,如何在服務更新部署過程當中保證業務無感知是開發者必需要解決的問題,即從應用中止到重啓恢復服務這個階段不能影響正常的業務請求,這使得無損下線成爲應用生命週期中必不可少的一個環節。web

同時在屢次 Dubbo Meetup 中,平滑上下線一直都是位居微服務開發痛點前 Top 3。spring

下面咱們來了解一下 Spring Boot 2.3 中提供的新特性 Graceful Shutdown,來分析一下它對咱們生產穩定性帶來什麼樣的幫助。tomcat

Spring Boot graceful shutdown

Graceful shutdown服務器

Graceful shutdown is supported with all four embedded web servers (Jetty, Reactor Netty, Tomcat, and Undertow) and with both reactive and Servlet-based web applications. When enabled using server.shutdown=graceful, upon shutdown, the web server will no longer permit new requests and will wait for a grace period for active requests to complete. The grace period can be configured using spring.lifecycle.timeout-per-shutdown-phase. Please see the reference documentation for further details.網絡

Spring Boot 2.3.0.RELEASE引入了Graceful Shutdown的功能。其中應用在等待下線期間對待新請求的方式,取決於咱們所使用的 Server 類型。根據官方文檔Tomcat、Jetty 和 Reactor Netty將會在網絡層面中止接收新的請求。Undertow 會繼續接收新的請求,但當即會以 HTTP 503(服務不可用)來響應。架構

配置與使用

在Spring Boot 2.3.0中,優雅停機的使用很是簡單,能夠經過在應用程序配置文件中設置兩個屬性來進行。
一、 server.shutdown 屬性能夠支持的值有兩種
app

  1. immediate 這是默認值,配置後服務器當即關閉,無優雅停機邏輯。
  2. graceful 開啓優雅停機功能,並遵照 spring.lifecycle.timeout-per-shutdown-phase 屬性中給出的超時來做爲服務端等待的最大時間。
    二、spring.lifecycle.timeout-per-shutdown-phase 服務端等待最大超時時間,採用java.time.Duration格式的值,默認30s。

例如:Properties 文件負載均衡

一、#To enable graceful shutdown

二、server.shutdown=graceful
三、#To configure the timeout period
四、spring.lifecycle.timeout-per-shutdown-phase=20s

當咱們使用瞭如上配置開啓了優雅停機功能,當咱們經過SIGTERM信號關閉 Spring Boot 應用時
一、 此時若是應用中沒有正在進行的請求,應用程序將會直接關閉,而無需等待超時時間結束後才關閉。
二、此時若是應用中有正在處理的請求,則應用程序將等待超時時間結束後纔會關閉。若是應用在超時時間以後仍然有未處理完的請求,應用程序將拋出異常並繼續強制關閉。

源碼實現分析

咱們以 Tomcat 爲例看一下是SpringBoot 2.3如何實現graceful shutdown的

這裏注意下,Tomcat 9.0.33或更高版本,才具有graceful shutdown功能。

咱們看一下 SpringBoot 的 TomcatWebServer 的實現,先看其中構造函數

一、org.springframework.boot.web.embedded.tomcat.TomcatWebServer

二、public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
三、 Assert.notNull(tomcat, "Tomcat Server must not be null");
四、 this.tomcat = tomcat;
五、 this.autoStart = autoStart;
六、 this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
七、 initialize();
八、}





能夠看到當咱們配置 server.shutdown=graceful 時,其中 gracefulShutdown 成員就不爲null,而是被置爲 GracefulShutdown 實例。
當咱們關閉SpringBoot的應用容器時,會觸發其生命週期的 stop 方法,咱們看到其中會執行webServer的shutDownGracefully方法
image.png
由於咱們配置 了server.shutdown=graceful ,因此 gracefulShutdown 成員並不爲null,而是會觸發 gracefulShutdown 的 shutDownGracefully 方法
image.png
咱們看一下shutDownGracefully 方法是如何作到graceful shutdown的
image.png





來看一下doShutdown的邏輯
org.springframework.boot.web.embedded.tomcat.GracefulShutdown#doShutdown
private void doShutdown(GracefulShutdownCallback callback) {

List<Connector> connectors = getConnectors();
connectors.forEach(this::close);
try {
    for (Container host : this.tomcat.getEngine().findChildren()) {
        for (Container context : host.findChildren()) {
            while (isActive(context)) {
                if (this.aborted) {
                    logger.info("Graceful shutdown aborted with one or more requests still active");
                    callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE);
                    return;
                }
                Thread.sleep(50);
            }
        }
    }

}
catch (InterruptedException ex) {
    Thread.currentThread().interrupt();
}
logger.info("Graceful shutdown complete");
callback.shutdownComplete(GracefulShutdownResult.IDLE);

}

先是關閉掉全部的鏈接,在網絡層中止接受請求,而後再等待全部請求處理完畢。
其中關於 spring.lifecycle.timeout-per-shutdown-phase 配置,是經過等待配置的時間後,再執行TomcatWebServer的stop方法,將其aborted成員置爲true,實現若是應用在寬限期以後仍然有待處理的請求,應用程序將拋出異常並繼續強制關閉,而不是一直等待下去。
@Override
public void stop() throws WebServerException {


synchronized (this.monitor) {
    boolean wasStarted = this.started;
    try {
        this.started = false;
        try {
            if (this.gracefulShutdown != null) {
                this.gracefulShutdown.abort();
            }
            stopTomcat();
            this.tomcat.destroy();
        }
        catch (LifecycleException ex) {
            // swallow and continue
        }
    }
    catch (Exception ex) {
        throw new WebServerException("Unable to stop embedded Tomcat", ex);
    }
    finally {
        if (wasStarted) {
            containerCounter.decrementAndGet();
        }
    }
}

}

void abort() {

this.aborted = true;

}

在微服務場景下問題彷佛依舊存在...
總結一下一個 Spring Cloud 應用正常分批發布的流程
一、服務發佈前,消費者根據負載均衡規則調用服務提供者,業務正常。
二、服務提供者 B 須要發佈新版本,先對其中的一個節點進行操做,先是正常中止 Java 進程。
三、服務中止過程當中,首先去註冊中心註銷服務,而後等待服務端線程處理完成,再中止服務。
四、註冊中心則將通知消費者,其中的一個服務提供者節點已下線。這個過程包含推送和輪詢兩種方式,推送能夠認爲是準實時的,輪詢的耗時由服務消費者輪詢間隔決定,最差的狀況下須要 1 分鐘。
五、服務消費者刷新服務列表,感知到服務提供者已經下線了一個節點,可是這個過程當中Spring Cloud 的負載均衡組件 Ribbon 默認的刷新時間是 30 秒 ,最差狀況下須要耗時 30 秒。
六、服務消費者再也不調用已經下線的節點
image.png







咱們看到,當一個Spring Cloud服務端經過SpringBoot提供的graceful shutdown下線時,它會拒絕客戶端新的請求,而且等待已經在處理的線程處理完成後,或者在配置的應用最長等待時間到了以後進行下線。

可是在服務端重啓開始拒絕客戶端新的請求的時刻開始,即執行了Connectors.stop開始,到客戶端感知到服務端該實例下線這段時間內,客戶端向該實例發起的全部請求都會被拒絕,從而引發服務調用異常。

image.png
若是客戶端考慮增長重試能力,這必定程度上能夠緩解發布過程當中服務調用報錯的問題,可是沒法根本上保證下線過程的無損,若是服務調用報錯期過程,或者分批發布時候同一批次下線的節點數過多,沒法保證僅僅增長屢次重試就可以調用到未下線的節點上。這不能根本解決問題!同時須要考慮配置重試帶來的業務上存在不冪等的風險。

EDAS 3.0 無損下線

EDAS 3.0 經過Java Agent技術無侵入加強您的應用,使其具有無損下線能力。
• 您無需修改一行代碼與配置,自然具有無侵入特色
• 同時支持 ECS 、K8s 場景
• 全面兼容開源,支持開源Dubbo、Spring Cloud 以及開源微服務網關
image.png



EDAS的應用如何作到無損下線?

image.png
如圖看到,咱們經過3個步驟的加強,主動註銷、服務提供者通知下線信息、服務消費者調用其餘服務提供者。

能夠看到,真正作到無損下線能力是須要客戶端加強一塊兒聯動的

• 主動註銷
咱們在應用服務下線前,主動通知註冊中心註銷該實例
• 通知下線信息
咱們會在服務端實例下線前主動通知客戶端,該服務節點下線的信息
• 調用其餘提供者
咱們在客戶端加強其負載均衡能力,在服務端下線後,客戶端主動調用其餘服務提供者節點
同時咱們提供應用等待的邏輯,使要下線的服務端等待已經收到的請求處理完成再關閉 Spring 容器。





image.png

完整的解決方案

EDAS 3.0無損下線不只僅支持 Spring Cloud 與 Dubbo 服務,咱們還打通了消息、網關等微服務組件,讓您的應用在EDAS中作到全鏈路的下線無損。

EDAS 3.0支持端到端的無損下線

  • 雲上客戶存在多種微服務網關,支持主流開源微服務網關(Spring Cloud Gateway、Zuul等)的無損下線
  • 有些用戶的流量是經過 Ingress、SLB、Nginx 等方式打到服務端的場景
  • MQ消息等異步訂閱關係的微服務場景
  • K8s 使用 Service 服務發現的微服務場景
    爲了作到全鏈路的無損下線,EDAS 3.0 經過無侵入的方式涵蓋多種場景的完整解決方案,確保您的發佈平滑無損。

即便面對白天大流量的場景,發佈依舊風輕雲淡。

 

原文連接 本文爲阿里雲原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索