做者介紹:三墩SRE,移動改變生活,技術影響將來;個人三墩,個人IT。java
問題描述linux
大規模的微服務改造後,在白天進行服務實例重啓或擴縮容時,系統常常出現「Can not get connection to server」報錯(該報錯是由微服務框架拋出來的異常,表示客戶端訪問不到分配的服務端),致使部分用戶業務受理失敗,影響用戶感知。服務器
問題分析架構
咱們生產環境的微服務框架是經過Zookeeper進行服務註冊發現的。以下圖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所示:
ShutdownHook如何被調用呢?使用java.lang.Runtime.addShutdownHook方法, 能夠註冊一個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」的報錯,也解決了業務感知問題。解決了該問題後,在大規模微服務架構的場景下,容器的自動化自愈、擴縮容等功能又能夠大展身手了。
總結
微服務的優雅停機沒有最優的解決方案,只要抓住核心思想進行設計便可。若是使用的框架中有此類解決方法,建議直接使用,其適配性確定是最高的。在微服務架構中,咱們能夠遵照如下建議規則來設計微服務的優雅停機機制:
全部微服務應用都應該支持優雅停機;
優先註銷註冊中心註冊的服務實例;
待停機的服務應用的接入點標記拒絕服務;
上游服務支持故障轉移因優雅停機而拒絕的服務;
根據具體業務也提供適當的停機接口。
【編輯推薦】
【責任編輯:未麗燕 TEL:(010)68476606】