SpringBoot啟動一下就停止了,springboot 優雅停機_Spring boot 2.3優雅下線,距離生產還有多遠?

 2023-10-04 阅读 32 评论 0

摘要:簡介:對于任何一個線上應用,如何在服務更新部署過程中保證業務無感知是開發者必須要解決的問題,即從應用停止到重啟恢復服務這個階段不能影響正常的業務請求,這使得無損下線成為應用生命周期中必不可少的一個環節。前言在生產環境中,隨著云
簡介:對于任何一個線上應用,如何在服務更新部署過程中保證業務無感知是開發者必須要解決的問題,即從應用停止到重啟恢復服務這個階段不能影響正常的業務請求,這使得無損下線成為應用生命周期中必不可少的一個環節。

前言

在生產環境中,隨著云原生架構的發展,自動的彈性伸縮、滾動升級、分批發布等云原生能力讓用戶享受到了資源、成本、穩定性的最優解。但是在應用的縮容、發布等過程中,由于實例下線處理得不夠優雅,將會導致短暫的服務不可用,短時間內業務監控會出現大量 io 異常報錯;如果業務沒做好事務,那么還會引起數據不一致的問題,那么需要緊急手動訂正錯誤數據;甚至每次發布,您需要發告示停機發布,否則您的用戶會出現一段時間服務不可用。沒處理好服務實例下線,無論發生上述哪種情況,都會對您業務的連續性造成困擾。

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

SpringBoot啟動一下就停止了?同時在多次 Dubbo Meetup 中,平滑上下線一直都是位居微服務開發痛點前 Top 3。

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

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 bean的生命周期、在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格式的值,默認30s。

例如:Properties 文件

1、#To enable graceful shutdown

2、server.shutdown=graceful
3、#To configure the timeout period
4、spring.lifecycle.timeout-per-shutdown-phase=20s

spring2.0技術手冊,當我們使用了如上配置開啟了優雅停機功能,當我們通過SIGTERM信號關閉 Spring Boot 應用時
1、 此時如果應用中沒有正在進行的請求,應用程序將會直接關閉,而無需等待超時時間結束后才關閉。
2、此時如果應用中有正在處理的請求,則應用程序將等待超時時間結束后才會關閉。如果應用在超時時間之后仍然有未處理完的請求,應用程序將拋出異常并繼續強制關閉。

源碼實現分析

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

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

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

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

SpringBoot項目、2、public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
3、 Assert.notNull(tomcat, "Tomcat Server must not be null");
4、 this.tomcat = tomcat;
5、 this.autoStart = autoStart;
6、 this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
7、 initialize();
8、}

可以看到當我們配置 server.shutdown=graceful 時,其中 gracefulShutdown 成員就不為null,而是被置為 GracefulShutdown 實例。
當我們關閉SpringBoot的應用容器時,會觸發其生命周期的 stop 方法,我們看到其中會執行webServer的shutDownGracefully方法

c6c26632814d32a1ba9b899792c53fa2.png


因為我們配置 了server.shutdown=graceful ,所以 gracefulShutdown 成員并不為null,而是會觸發 gracefulShutdown 的 shutDownGracefully 方法

7d44920e3fca11dc5c11889b92ef5205.png


我們看一下shutDownGracefully 方法是如何做到graceful shutdown的

393ed99e408e408de24459bfece9301a.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 應用正常分批發布的流程
1、服務發布前,消費者根據負載均衡規則調用服務提供者,業務正常。
2、服務提供者 B 需要發布新版本,先對其中的一個節點進行操作,先是正常停止 Java 進程。
3、服務停止過程中,首先去注冊中心注銷服務,然后等待服務端線程處理完成,再停止服務。
4、注冊中心則將通知消費者,其中的一個服務提供者節點已下線。這個過程包含推送和輪詢兩種方式,推送可以認為是準實時的,輪詢的耗時由服務消費者輪詢間隔決定,最差的情況下需要 1 分鐘。
5、服務消費者刷新服務列表,感知到服務提供者已經下線了一個節點,但是這個過程中Spring Cloud 的負載均衡組件 Ribbon 默認的刷新時間是 30 秒 ,最差情況下需要耗時 30 秒。
6、服務消費者不再調用已經下線的節點

27331e10f4bc776a42df02f6124cd1fe.png

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

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

f674b4ec2b696cff54679061c89dffa7.png


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

EDAS 3.0 無損下線

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

e9f8386d6adc239660e3fe4535bf317d.png

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

2bb2ddc4e9fec7ced493bcd057fc13f6.png


如圖看到,我們通過3個步驟的增強,主動注銷、服務提供者通知下線信息、服務消費者調用其他服務提供者。

可以看到,真正做到無損下線能力是需要客戶端增強一起聯動的

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

a6f8b8b7cdc7d8b14240455284cb84d5.png

完整的解決方案

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

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

  • 云上客戶存在多種微服務網關,支持主流開源微服務網關(Spring Cloud Gateway、Zuul等)的無損下線
  • 有些用戶的流量是通過 Ingress、SLB、Nginx 等方式打到服務端的場景
  • MQ消息等異步訂閱關系的微服務場景
  • K8s 使用 Service 服務發現的微服務場景
    為了做到全鏈路的無損下線,EDAS 3.0 通過無侵入的方式涵蓋多種場景的完整解決方案,確保您的發布平滑無損。
即使面對白天大流量的場景,發布依舊風輕云淡。


作者信息:
泮圣偉(花名:十眠)阿里云智能開發工程師,負責 Dubbo / Spring Cloud 商業化產品開發相關工作,目前主要關注云原生、微服務等技術方向。

原文鏈接

本文為阿里云原創內容,未經允許不得轉載。

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/3/112033.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息