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