作者 | 鋮樸
絕?多數的軟件應??產安全事故發?在應?上下線發布階段,盡管通過遵守業界約定俗成的可灰度、可觀測和可滾回的安全?產三板斧,可以最?限度的規避發布過程中由于應??身代碼問題對?戶造成的影響。但對于?并發?流量情況下的短時間流量有損問題卻仍然?法解決。因此,本文將圍繞發布過程中如何解決流量有損問題實現應?發布過程中的?損上下線效果相關內容展開?案介紹。
無損上下線背景
據統計,應?的事故?多發?在應?上下線過程中,有時是應?本身代碼問題導致。但有時我們也會發現盡管代碼本身沒有問題,但在應?上下線發布過程中仍然會出現短時間的服務調?報錯,?如調?時出現Connection refused和No instance等現象。相關問題的原因有相關發布經歷的同學或多或少可能有?定了解,?且?家發現該類問題?般在流量?峰時刻尤為明顯,半夜流量少的時候就?較少見,于是很多?便選擇半夜三更進?應?發布希望以此來規避線上發布事故。本節將就這些問題出現的背后真實原因以及業界對應的設計?案展開介紹。常見的流量有損現象出現的原因包括但不限于以下?種:
- 服務?法及時下線:服務消費者感知注冊中?服務列表存在延時,導致應?特定實例下線后在?段時間內服務消費者仍然調?已下線實例造成請求報錯。
- 初始化慢:應?剛啟動接收線上流量進?資源初始化加載,由于流量太?,初始化過程慢,出現?量請求響應超時、阻塞、資源耗盡從?造成剛啟動應?宕機。
- 注冊太早:服務存在異步資源加載問題,當服務還未初始化完全就被注冊到注冊中?,導致調?時資源未加載完畢出現請求響應慢、調?超時報錯等現象。
- 發布態與運?態未對?:使?Kubernetes的滾動發布功能進?應?發布,由于Kubernetes的滾動發布?般關聯的就緒檢查機制,是通過檢查應?特定端?是否啟動作為應?就緒的標志來觸發下?批次的實例發布,但在微服務應?中只有當應?完成了服務注冊才可對外提供服務調?。因此某些情況下會出現新應?還未注冊到注冊中?,?應?實例就被下線,導致?服務可?。
接下來,將就具體的下線和上線過程中如何避免流量損耗問題進?分別介紹。
無損下線
由于微服務應用自身調用特點,在高并發下,服務提供端應用實例的直接下線,會導致服務消費端應用實例無法實時感知下游實例的實時狀態因而出現繼續將請求轉發到已下線的實例從而出現請求報錯,流量有損。
圖1 Spring Cloud應?消費者?法及時感知提供者服務下線
例如對于Spring Cloud應?如上圖1所示,當應?的兩個實例A’和A中的A下線時,由于Spring Cloud框架為了在可?性和性能??做平衡,消費者默認是30s去注冊中?拉取最新的服務列表,因此A實例的下線不能被實時感知,流量較?時,消費者會繼續通過本地緩存調?已下線的A實例導致出現流量有損。基于上述背景,業界提出了相應的?損下線(也叫優雅下線)的技術?案來應對上述問題。本節將對業界主流的?些?損下線技術?案進?介紹。
針對該類問題,業界一般的解決方式是通過將應用更新流程劃分為手工摘流量、停應用、更新重啟三個步驟。由人工操作實現客戶端避免調用已下線實例,這種方式簡單而有效,但是限制較多:不僅需要借助流控能力來實現實時摘流量,還需要在停應用前人工判斷來保證在途請求已經處理完畢。這種需要人工介入的方式運維復雜度較高,只適用于規模較小的應用,無法解決當前云原生架構下,自動化的彈性伸縮、滾動升級等場景中的實例下線過程中的流量有損問題。本節將對業界應用于云原生場景中的一些無損下線技術方案進行介紹。
1.主動通知
一般注冊中心都提供了主動注銷接口供微服務應用正常關閉時調用,以便下線實例能及時更新其在注冊中心上的狀態。主動注銷在部分基于事件感知注冊中心服務列表的微服務框架比如Dubbo中能及時讓上游服務消費者感知到提供者下線避免后續調用已下線實例。但對于像Spring Cloud這類微服務框架服務消費者感知注冊中心實例變化是通過定時拉取服務列表的方式實現。盡管下線實例通過注冊中心主動注銷接口更新了其自身在注冊中心上的應用狀態信息但由于上游消費者需要在下一次拉取注冊中心應用列表時才能感知到,因此會出現消費者感知注冊中心實例變化存在延時。在流量較大、并發較高的場景中,當實例下線后,仍無法實現流量無損。既然無法通過注冊中心讓存量消費者實例實時感知下游服務提供者的變化情況,業界提出了利用主動通知解決該類問題。主動通知過程如下圖2所示:
圖2 ?損下線?案
如圖2所示,服務提供者B中某個實例在下線時為避免主動在注冊中心中注銷的服務實例狀態無法實時被上游消費者A感知到,從而導致調用已下線實例的問題。在接收到下線命令即將下線前,提供者B對于在等待下線階段內收到的請求,在其返回值中都增加上特殊標記讓服務消費者接收到返回值并識別到相關標志后主動拉取一次注冊中心服務實例從而實時感知B實例最新狀態,從而達到服務提供者的下線狀態能夠被服務消費者實時感知。
2.自適應等待
在并發度不?的場景下,主動通知?法可以解決絕?部分應?下線流量有損問題。但對于?并發?流量應?下線場景,如果主動通知完,可能仍然存在?些在途請求需要待下線應?處理完才能下線否則這些流量就?法正常被響應。為解決該類在途請求問題,可通過給待下線應?在下線前通過?適應等待機制在處理完所有在途請求后,再下線以實現流量?損。
圖3 ?適應等待機制
如上圖3所示,?適應等待機制是通過待下線應?統計應?中是否仍然存在未處理完的在途請求,來決定應?下線的時機,從?讓待下線應?在下線前處理完所有剩余請求。
無損上線
延遲加載是軟件框架設計過程中最常?的?種策略,例如在Spring Cloud框架中Ribbon組件的拉取服務列表初始化默認都是要等到服務的第?次調?時刻,例如下圖4是Spring Cloud應?中第?次和第?次通過調?RestTemplate調?遠程服務的耗時對比情況:
圖4 應?啟動資源初始化與正常運?過程中耗時情況對?
由圖4結果可?,第?次調?由于進?了?些資源初始化,耗時是正常情況的數倍之多。因此把新應?發布到線上直接處理?流量極易出現?量請求響應慢,資源阻塞,應?實例宕機的現象。
業界針對上述應??損上線場景提出如下包括延遲注冊、?流量服務預熱以及就緒檢查等?系列解決?案,詳細完整的?案如下圖5所示:
圖5 ?損上線整體?案
1.延遲注冊
對于初始化過程需要異步加載資源的復雜應?啟動過程,由于注冊通常與應?初始化過程同步進?,從?出現應?還未完全初始化就已經被注冊到注冊中?供外部消費者調?,此時直接調?由于資源未加載完成可能會導致請求報錯。通過設置延遲注冊,可讓應?在充分初始化后再注冊到注冊中?對外提供服務。例如開源微服務治理框架Dubbo原?就提供延遲注冊功能[1]。
2.小流量服務預熱
在線上發布場景下,很多時候剛啟動的冷系統直接處理?量請求,可能由于系統內部資源初始化不徹底從?出現?量請求超時、阻塞、報錯甚?導致剛發布應?宕機等線上發布事故出現。為了避免該類問題業界針對不同框架類型以及應??身特點設計了不同的應對舉措,?如針對類加載慢問題有編寫腳本促使JVM進?預熱、阿?巴巴集團內部HSF(High Speed Framework)使?的對接?分批發布、延遲注冊、通過mock腳本對應?進?模擬請求預熱以及?流量預熱等。本節將對其中適?范圍最?的?流量預熱?法進?介紹。
相?于?般場景下,剛發布微服務應?實例跟其他正常實例?樣?起平攤線上總QPS。?流量預熱?法通過在服務消費端根據各個服務提供者實例的啟動時間計算權重,結合負載均衡算法控制剛啟動應?流量隨啟動時間逐漸遞增到正常?平的這樣?個過程幫助剛啟動運?進?預熱,詳細QPS隨時間變化曲線如圖6所示:
圖6 應??流量預熱過程QPS曲線
開源Dubbo所實現的?流量服務預熱過程原理如下圖7所示:
圖7 應??流量預熱過程原理圖
服務提供端在向注冊中?注冊服務的過程中,將?身的預熱時? WarmupTime、服務啟動時間StartTime 通過元數據的形式注冊到注冊中?中,服務消費端在注冊中?訂閱相關服務實例列表,調?過程中根據 WarmupTime、StartTime 計算個實例所分批的調?權重。剛啟動StartTime 距離調?時刻差值較?的實例權重下,從?實現對剛啟動應?分配更少流量實現對其進??流量預熱。 開源Dubbo所實現的?流量服務預熱模型計算如下公式所示:
模型中應用QPS對應的 f(x) 隨調用時刻 x 線性變化,x表示調用時刻的時間,startTime是應用開始時間,warmupTime是用戶配置的應用預熱時長,k是常數,一般表示各實例的默認權重。
圖8 應??流量預熱權重計算 通過?流量預熱?法,可以有效解決,?并發?流量下,資源初始化慢所導致的?量請求響應 慢、請求阻塞,資源耗盡導致的剛啟動應?宕機事故。
3.微服務就緒檢查
在介紹微服務就緒檢查之間,先簡單介紹?下相關的Kubernetes探針技術作為技術背景,以便更好的理解后?內容:
Kubernetes探針技術
在云原?領域,Kubernetes為了確保應? Pod 在對外提供服務之前應?已經完全啟動就緒或者應?Pod?時間運?期間出現意外后能及時恢復,提供了探針技術來動態檢測應?的運?情況,為保證應?的?損上線和?時間健康運?提供了保障。
存活探針
Kubernetes 中提供的存活探測器來探測什么時候進?容器重啟。例如,存活探測器可以捕捉到死鎖(應?程序在運?,但是?法繼續執?后?的步驟)。在這樣的情況下重啟容器有助于讓應?程序在有問題的情況下更可?。
就緒探針
Kubernetes 中提供的就緒探測器可以知道容器什么時候準備好了并可以開始接受請求流量,當?個 Pod 內的所有容器都準備好了,才能把這個 Pod 看作就緒了。這種信號的?個?途就是控制哪個 Pod 作為 Service 的后端。在Pod 還沒有準備好的時候,會從 Service 的負載均衡器中被剔除的。 啟動探針 Kubernetes 中提供的啟動探測器可以知道應?程序容器什么時候啟動了。如果配置了這類探測器,就可以控制容器在啟動成功后再進?存活性和就緒檢查,確保這些存活、就緒探測器不會影響應?程序的啟動。這可以?于對慢啟動容器進?存活性檢測,避免它們在啟動運?之前就被殺掉。
探針使??結
- 當需要在容器已經啟動后再執?存活探針或者就緒探針檢查,則可通過設定啟動探針實現。
- 當容器應?在遇到異常或不健康的情況下會??崩潰,則不?定需要存活探針,Kubernetes 能根據 Pod 的 restartPolicy 策略?動執?預設的操作。
- 當容器在探測失敗時被Kill并重新啟動,則可通過指定?個存活探針,并指定restartPolicy 為Always 或 OnFailure。
- 當希望容器僅在探測成功時 Pod 才開始接收外部請求流量,則可使?就緒探針。
更多Kubernetes探針技術使用示例參考[2]。
當前容器+Kubernetes的應?運維部署?式已經成為了業界的事實標準,相關技術為微服務應?運維部署帶來巨?便利的同時,在某些特殊的應?部署場景中也有?些問題需要解決。?如,使?Kubernetes的滾動發布功能進?應?發布,由于Kubernetes的滾動發布?般關聯的就緒檢查機制,是通過檢查應?特定端?是否啟動作為應?就緒的標志來觸發下?批次的實例發布,但在微服務應?中只有當應?完成了服務注冊才可對外提供服務調?。因此某些情況下會出現新應?還未注冊到注冊中?,?應?實例就被設置下線,導致?服務可?。
針對這樣?類微服務應?的發布態與應?運?態?法對?的問題導致的應?上線事故,當前業界也已經有相關解決?案進?應對。?如可以通過就緒檢查關聯服務注冊的?法,通過字節碼技術植?應?服務注冊邏輯前后,然后在應?中開啟?個探測應?服務是否完成注冊的端?供Kubernetes的就緒探針進?應?就緒態探測進?綁定?戶的發布態與運?態實現微服務的就緒檢查,避免出現相關狀態不?致導致的應?發布上線流量有損問題。
參考資料
[1] Dubbo延遲注冊:https://dubbo.apache.org/zh/docs/advanced/delay-publish/
[2]Kubernetes探針技術使?實例:https://kubernetes.io/zh/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/