實操:大規模微服務架構下的優雅停機

做者介紹:三墩SRE,移動改變生活,技術影響將來;個人三墩,個人IT。java

問題描述linux

大規模的微服務改造後,在白天進行服務實例重啓或擴縮容時,系統常常出現「Can not get connection to server」報錯(該報錯是由微服務框架拋出來的異常,表示客戶端訪問不到分配的服務端),致使部分用戶業務受理失敗,影響用戶感知。服務器

問題分析架構

咱們生產環境的微服務框架是經過Zookeeper進行服務註冊發現的。以下圖1所示:框架

圖1:服務註冊調用流程圖

其主要調用邏輯爲:curl

  • 生成容器,服務在容器內啓動;ide

  • 註冊到服務路由器(Zookeeper);微服務

  • 服務調用者訂閱服務路由器;測試

  • 服務路由器上發生註冊變更,通知服務調用者從新獲取新的註冊列表;優化

  • 服務調用者根據獲取到的服務端列表,進行服務調用。

可是當服務端實例中止後,因爲服務端不會主動去更改服務路由器上的註冊信息,客戶端須要40秒(目前應用到Zookeeper的會話超時時間配置)才能剔除這個異常配置,在這40秒內應用仍是會不斷的嘗試訪問這個不存在的實例,致使產生大量業務報錯。

這也與每次實例重啓後,報錯的持續時間相符。且因爲微服務的特性,每筆實際業務請求,會根據業務須要屢次調用同一個服務,這增長了每筆業務請求訪問到異常實例的可能性,加重了業務失敗的機率。

因此這是一個因爲服務實例暴力中止,並被微服務架構下單筆業務請求屢次訪問的倍數放大後,產生的問題。

解決方案

既然這是因爲服務實例暴力停機致使的問題,因此咱們開始研究基於微服務的優雅停機方式。

微服務架構中的應用優雅停機,主要是指應用實例有計劃而平滑(即不產生須要處理的動做或不產生異常報錯)的退出方式。主要有兩種方式:

  • 方式一:經過微服務框架自帶的檢測能力來實現,如在Spring Cloud微服務框架中,提供了actuator組件的/health端點來實現。客戶端須要實現一個自定義的HealthCheckHandler,它將應用的健康狀態保存到內存中,只需在服務器上利用curl發送shutdown命令,狀態一旦發生改變,就會從新向服務器進行註冊。

  • 方式二:經過註冊JDK的ShutdownHook(鉤子)來實現,當系統接收到退出指令後,首先把本身從Zookeeper註冊服務器上下線,再也不接收新的消息,而後將積壓的請求處理完,最後調用資源回收接口將資源銷燬,最後各線程退出執行。

因爲咱們生產環境未採用開源通用型微服務架構,且應用都是基於JAVA開發,所以咱們採用的是方式二:經過註冊JDK的ShutdownHook(關閉鉤子)來實現優雅停機方式。

關閉鉤子本質是一個線程(也稱爲Hook線程),用來監聽JVM的關閉。經過Runtime的addShutdownHook能夠向JVM註冊一個關閉鉤子。Hook線程在JVM正常關閉纔會執行,強制關閉時不會執行。

JVM正常關閉的場景主要包括以下幾種:

  • Java程序正常運行完退出時會被調用;

  • 終端中經過ctrl-c終止命令時會被調用;

  • JVM發生OutOfMemory而退出時會被調用;

  • Java程序中執行System.exit()時會被調用;

  • 操做系統關閉時會被調用;

  • linux經過kill pid(或者kill -15 pid)結束進程時會被調用。

JDK中ShutdownHook相關的源碼以下圖2所示:

圖2:添加刪除實現

ShutdownHook如何被調用呢?使用java.lang.Runtime.addShutdownHook方法, 能夠註冊一個JVM關閉的鉤子(線程),以下圖3所示。咱們想要程序在JVM退出時作的各類掃尾工做,好比:關閉資源、註銷服務路由器上的註冊信息、等待在途請求處理完成等均可以在該線程裏添加實現。

圖3:註冊一個JVM關閉的鉤子示例

固然優雅退出須要有超時控制機制,若是到達超時時間仍然沒有完成退出前的資源回收等操做,則由停機腳本直接調用KILL -9 PID或調用強制關閉進程的代碼方式進行強制退出,否則可能會等待很長時間,影響咱們正常的啓停操做。

其它注意事項

一、在如下幾種場景下,會直接中止JVM進程,JVM徹底沒有機會執行關閉鉤子線程中的掃尾工做,沒法實現優雅停機:

  • kill -9(SIGKILL信號);

  • 調用了java.lang.runtime.halt()方法;

  • 主機直接crash;

  • 主機直接關機;

  • 主機內存(或者容器內存)不夠,觸發操做系統OOM-KILLER。

二、hook線程會延遲JVM的關閉時間,因此儘量減小執行時間,並作好超時控制。

效果

在代碼優化後,經過測試環境的驗證和固定場景的實際生產演練,容器在正常銷燬、重啓時,均未出現「Can not get connection to server」的報錯,也解決了業務感知問題。解決了該問題後,在大規模微服務架構的場景下,容器的自動化自愈、擴縮容等功能又能夠大展身手了。

總結

微服務的優雅停機沒有最優的解決方案,只要抓住核心思想進行設計便可。若是使用的框架中有此類解決方法,建議直接使用,其適配性確定是最高的。在微服務架構中,咱們能夠遵照如下建議規則來設計微服務的優雅停機機制:

  • 全部微服務應用都應該支持優雅停機;

  • 優先註銷註冊中心註冊的服務實例;

  • 待停機的服務應用的接入點標記拒絕服務;

  • 上游服務支持故障轉移因優雅停機而拒絕的服務;

  • 根據具體業務也提供適當的停機接口。

【編輯推薦】

  1. Nginx的微服務參考架構

  2. 5個規則,確保你的微服務優化運行

  3. 微服務和API網關限流熔斷實現關鍵邏輯思路

  4. 微服務的成功應用

  5. 微服務、迷你服務和宏服務

【責任編輯:未麗燕 TEL:(010)68476606】

相關文章
相關標籤/搜索