1、實踐背景
現(xiàn)在java主流的微服務(wù)技術(shù)棧毫無疑問是SpringCloud,這也是經(jīng)銷商技術(shù)部微服務(wù)實踐采用的技術(shù)棧。注冊中心采用公司技術(shù)部的nacos。在SpringCloud實踐中大家普遍遇到的問題是應(yīng)用默認(rèn)是無法做到無損下線的,需要更多的輔助措施才能得到無損下線的效果,本文主要分享我們團隊解決應(yīng)用無損下線的一些實踐。
2、有損下線
有損下線指的是應(yīng)用在下線過程中,部分請求沒有被妥善處理,出現(xiàn)請求異常進而影響應(yīng)用可用性,影響用戶使用。有損下線原因分析:
損失原因1:springboot實例默認(rèn)接收到停止信號TERM時,馬上停止服務(wù)。如下圖
當(dāng)springboot實例收到TERM信號立即關(guān)閉的時候,很有可能請求隊列中還有請求,還有一部分正在處理的請求。如果立即關(guān)閉這些請求都會損失掉。
解決方案:在springboot 2.3版本以上,提供了優(yōu)雅關(guān)閉(Graceful Shutdown)配置。如下:
server:
shutdown: graceful
配置了優(yōu)雅關(guān)閉后,實例關(guān)閉過程如下圖所示:
springboot實例在收到TERM信號后,不會立即關(guān)閉應(yīng)用,而是進入優(yōu)雅處理階段,這個優(yōu)雅時間段時長可以配置,默認(rèn)為30秒。
springboot在網(wǎng)絡(luò)層拒絕新的請求進入的同時,會等隊列中的請求和正在處理請求都處理完成或者優(yōu)雅時間耗盡才關(guān)閉應(yīng)用。這樣只要給應(yīng)用配置合適的優(yōu)雅關(guān)閉階段時長就可以避免這類請求損失。
損失原因2:在使用注冊中心的默認(rèn)情況下,服務(wù)下線狀態(tài)無法實時通知給調(diào)用方。
在微服務(wù)引入注冊中心的場景下,provider(服務(wù)提供方),registry(注冊中心),consumer(服務(wù)調(diào)用方)正常服務(wù)調(diào)用過程如下:
服務(wù)下線時的時序圖如下:
當(dāng)provider收到TERM信號進入Graceful shutdown階段的時候也就是圖中的1時間點,provider在網(wǎng)絡(luò)層便不再接收新的請求,然后直到時間點4,consumer才停止向provider發(fā)送請求。所以損失時間 = 時間點4 – 時間點1。
3、解決方案
方案1:provider下線盡快通知到consumer。
Ribbon默認(rèn)采用輪詢拉取服務(wù)列表的方式,時間間隔默認(rèn)為30秒,也就是說
時間點3 – 時間點2 = 30秒 (最長)
對一個服務(wù)來說,如果30秒不可用,情況是相當(dāng)糟糕的,改進方法有兩個,一是縮短輪詢間隔到5秒左右;二是可以實現(xiàn)注冊中心事件推送方式通知consumer更新服務(wù)列表,盡量縮短損失時長。但是這只能治標(biāo)不治本,因為雖然損失時長縮短了,但是仍然還有幾秒損失,每次上線都需要損失請求仍然是不可接受的。
方案2:在provider關(guān)閉前通知到consumer下線。
如果是可以人工干預(yù)下線過程,我們大概會這么干:首先把要下線的provider實例從負載中摘除掉確保不再有流量打到下線實例后,才真正執(zhí)行下線命令。這樣就不會有任何流量損失了。只需要將這個流程自動化就可以完美解決了。
好在k8s提供了preStop配置,在k8s平臺中,preStop和TERM信號關(guān)系是這樣的:preStop會先執(zhí)行完,然后k8s才會給pod發(fā)送TERM信號,k8s會給pod的停止設(shè)置一個寬限時長,超過寬限時長會強殺掉,這個寬限時長從preStop調(diào)用開始計時。采用這個方案的話,服務(wù)下線時序圖如下:
可以看到當(dāng)consumer不再往provider打流量后,provider才開始執(zhí)行shutdown,這樣不會有任何損失。
4、實現(xiàn)步驟
1) 縮短輪詢間隔
在springboot應(yīng)用中增加如下配置:
ribbon:
ServerListRefreshInterval: 5000 # 服務(wù)列表刷新間隔
2)springboot應(yīng)用增加preStop hook實現(xiàn)
擴展springboot actuator節(jié)點,具體代碼如下。
其中sleepSeconds可以根據(jù)項目配置。
3)在云平臺配置preStop
4)安全加固
由于暴露了prestop hook接口,如果服務(wù)直接暴露至公網(wǎng),有可能被惡意掃描調(diào)用,對接口進行安全加固是很有必要的。我們在prestop hook接口增加token參數(shù),例如:
prestop-hook-api?token=xxxxxxxxxxxxxx
每個項目可以約定不同token值,并對token值進行校驗,token值不對的訪問將被拒絕,這樣惡意掃描就無法訪問到prestop hook 接口了。
代碼實現(xiàn)我們已經(jīng)封裝在harmless-starter中并集成到項目模板里,這樣使用者只需要配置sleepSeconds即可。
5、總結(jié)
本文主要記錄了經(jīng)銷商技術(shù)部在保證SpringCloud應(yīng)用無損下線的一些實踐探索和總結(jié),其中對SpringCloud服務(wù)下線造成損失的原因進行了比較全面的分析并提供了最終解決方案和實現(xiàn),希望對其他人也有所幫助。