1. 程式人生 > 其它 >解決微服務架構下流量有損問題的實踐和探索

解決微服務架構下流量有損問題的實踐和探索

簡介:絕⼤多數的軟體應⽤⽣產安全事故發⽣在應⽤上下線釋出階段,儘管通過遵守業界約定俗成的可灰度、可觀測和可滾回的安全⽣產三板斧,可以最⼤限度的規避釋出過程中由於應⽤⾃身程式碼問題對⽤戶造成的影響。但對於⾼併發⼤流量情況下的短時間流量有損問題卻仍然⽆法解決。因此,本文將圍繞釋出過程中如何解決流量有損問題實現應⽤釋出過程中的⽆損上下線效果相關內容展開⽅案介紹。

作者 | 鋮樸
來源 | 阿里開發者公眾號

絕⼤多數的軟體應⽤⽣產安全事故發⽣在應⽤上下線釋出階段,儘管通過遵守業界約定俗成的可灰度、可觀測和可滾回的安全⽣產三板斧,可以最⼤限度的規避釋出過程中由於應⽤⾃身程式碼問題對⽤戶造成的影響。但對於⾼併發⼤流量情況下的短時間流量有損問題卻仍然⽆法解決。因此,本文將圍繞釋出過程中如何解決流量有損問題實現應⽤釋出過程中的⽆損上下線效果相關內容展開⽅案介紹。

無損上下線背景

據統計,應⽤的事故⼤多發⽣在應⽤上下線過程中,有時是應⽤本身程式碼問題導致。但有時我們也會發現儘管程式碼本身沒有問題,但在應⽤上下線釋出過程中仍然會出現短時間的服務調⽤報錯,⽐如調⽤時出現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 中提供的啟動探測器可以知道應⽤程式容器什麼時候啟動了。如果配置了這類探測器,就可以控制容器在啟動成功後再進⾏存活性和就緒檢查,確保這些存活、就緒探測器不會影響應⽤程式的啟動。這可以⽤於對慢啟動容器進⾏存活性檢測,避免它們在啟動運⾏之前就被殺掉。

探針使用小結

1.當需要在容器已經啟動後再執⾏存活探針或者就緒探針檢查,則可通過設定啟動探針實現。

2.當容器應⽤在遇到異常或不健康的情況下會⾃⾏崩潰,則不⼀定需要存活探針,Kubernetes 能根據 Pod 的 restartPolicy 策略⾃動執⾏預設的操作。

3.當容器在探測失敗時被Kill並重新啟動,則可通過指定⼀個存活探針,並指定restartPolicy 為Always 或 OnFailure。

4.當希望容器僅在探測成功時 Pod 才開始接收外部請求流量,則可使⽤就緒探針。

更多Kubernetes探針技術使用示例參考[2]。

當前容器+Kubernetes的應⽤運維部署⽅式已經成為了業界的事實標準,相關技術為微服務應⽤運維部署帶來巨⼤便利的同時,在某些特殊的應⽤部署場景中也有⼀些問題需要解決。⽐如,使⽤Kubernetes的滾動釋出功能進⾏應⽤釋出,由於Kubernetes的滾動釋出⼀般關聯的就緒檢查機制,是通過檢查應⽤特定端⼝是否啟動作為應⽤就緒的標誌來觸發下⼀批次的例項釋出,但在微服務應⽤中只有當應⽤完成了服務註冊才可對外提供服務調⽤。因此某些情況下會出現新應⽤還未註冊到註冊中⼼,⽼應⽤例項就被設定下線,導致⽆服務可⽤。

針對這樣⼀類微服務應⽤的釋出態與應⽤運⾏態⽆法對⻬的問題導致的應⽤上線事故,當前業界也已經有相關解決⽅案進⾏應對。⽐如可以通過就緒檢查關聯服務註冊的⽅法,通過位元組碼技術植⼊應⽤服務註冊邏輯前後,然後在應⽤中開啟⼀個探測應⽤服務是否完成註冊的端⼝供Kubernetes的就緒探針進⾏應⽤就緒態探測進⽽繫結⽤戶的釋出態與運⾏態實現微服務的就緒檢查,避免出現相關狀態不⼀致導致的應⽤釋出上線流量有損問題。

參考資料

[1] Dubbo延遲註冊:延遲暴露 | Apache Dubbo

[2]Kubernetes探針技術使⽤例項:
配置存活、就緒和啟動探測器 | Kubernetes

原文連結

本文為阿里雲原創內容,未經允許不得轉載。