作者:項良、十眠
微服務上雲門檻降低,用好微服務纔是關鍵
據調研數據顯示,約 70% 的生產故障是由變更引起的。在阿里雲上的企業應用如茶百道、極氪汽車和來電等,他們是如何解決變更引起的穩定性風險,實現了在白天高流量情況下應用發佈平滑無損。今天我們將揭開阿里雲微服務全鏈路無損發佈解決方案的面紗, 快來一起參與探討,並動手實踐吧。
隨着微服務開源以及生態的成熟,大大地推進了微服務技術的標準化。下圖是展示單體和微服務架構選型的圖,其中橫座標代表的是系統複雜度,縱座標代表着效率,綠色曲線代表着單體架構,藍色曲線代表微服務架構。我們可以看到隨着系統複雜度的升高,單體架構跟微服務之間選型存在着一個拐點,拐點向右更適合選型微服務架構。隨着微服務技術的標準化,意味着微服務上雲的門檻也大幅度降低,他們之間選型的拐點也在持續地左移,採用微服務架構的企業日益增多。當企業開始大規模推廣使用微服務時,如何用好、用穩微服務成爲了大家關注的點,用好微服務的關鍵就是穩定性跟效率。
微服務變更風險大,發佈被迫選擇半夜
我們來一起看下一些客戶真實的訴求,某茶飲企業變更引起事故佔比一度超過 60%,每次發版都要避開高峯,在凌晨發版;某新能源企業業務連續性要求非常高,核心服務需要 7*24 小時持續在線,但是業務快速發展又需要保證迭代的效率,爲了保證業務的穩定性,只能在業務低峯期即凌晨進行發佈;同樣,某科技公司因爲缺少線上灰度能力,每次發佈全量發佈會導致影響面不可控,存在一定風險。
據調研數據稱,70% 的生產故障都是由於變更導致的,除應用本身問題外,運行時風險概括爲:不確定流量、不穩定調用、不穩定基礎設施。今天,我們聊的解決方案就是爲了全面消除變更態風險,解決用戶生產變更態的穩定性風險。
我們先來分析一下,爲什麼微服務應用不能白天發佈?因爲相比於單體應用來說,微服務架構本身就是複雜的拓撲網絡狀的調用跟依賴結構。那麼就意味着如果我們的微服務應用沒有處理好上下線有損的問題,那麼我們的任一微服務在發佈的過程中都會存在短暫的服務不可用的情況,短時間內會出現大量的異常,導致業務受損。
最直觀的感受就是我們一不小心點擊了某個應用的重新部署,然後報警羣裏就會收到大量訂單成功率下跌的告警,想想就很嚇人。其次,發佈時整個功能上線到線上的最後一個環節,一些在設計、研發、測試過程中累計下來的問題,在最後發佈的環節纔會觸發。如果發佈涉及到多個應用,如何進行合理發佈,並且不會因爲版本的問題而導致流量損失?我們總不希望看到,同一個用戶在下單頁面時候訪問到新的版本,到支付階段又碰到了老版本。
左下圖是我拿開源 Spring Cloud 應用進行壓測,並且在壓測過程中重啓應用後,整體壓測請求的表現,我們可以看到有大量的異常。再進一步想一下,如果此時是生產系統,並且在白天大流量的場景下,極小的問題都會由於大流量的因素被快速放大,影響面難以控制。所以很多企業跟業務團隊,寧願選擇在凌晨進行發佈。
MSE 微服務全鏈路無損發佈方案,消除變更風險
如何徹底解決變更穩定性問題?那就介紹今天的主角,MSE 微服務全鏈路無損發佈方案。
我們分別從兩個角度來分析問題:
-
如果我們新版本的代碼沒任何問題,服務上下線過程還是會有損,應該怎麼解決?
-
如果我們新版本的代碼有問題,那麼我們如何控制問題的影響面?
首先,代碼沒問題,爲什麼上下線的環節中流量會有損失。當我們的微服務提供者下線後,服務的消費者無法實時感知節點下線,持續調用已經下線的節點地址,那麼流量就會出現持續的報錯;如果服務提供者在下線的過程中,請求執行到一半時,應用節點就直接停止了,那麼就會導致在途請求以及處理中的請求失敗,嚴重時還會導致線上的數據不一致;同樣,在應用上線環節中也會有許多問題,比如新啓動的節點如果沒有完成預熱,就直接承受打流量,那麼就會導致新啓動的節點直接被打垮,更有嚴重情況下,大流量場景下服務擴容都擴不出來。同樣,微服務體系的生命週期沒有跟 K8s 維度的 Readiness/Liveness 生命週期沒有對齊,那麼在發佈的過程中會出現沒對齊而導致的請求異常。
如果新版本的代碼存在問題,那我們希望在發佈新版本的過程中儘可能地控制影響面,阿里巴巴安全生產最佳實踐告訴我們,在發佈變更的過程中需要做到安全生產三板斧,即可灰度、可觀測、可回滾。可灰度就是通過灰度的方式控制問題的影響面,如果我們業務代碼新版本存在 bug,大多數情況下我們只能選擇在業務低峯期(凌晨)進行發佈;在微服務架構下,灰度發佈需要做到全鏈路灰度的能力,目前開源自建很難實現全鏈路灰度的能力,如果出現流量丟標的情況,會導致灰度流量打到生產環境,引入了額外不可控的風險;可回滾,要求我們在出問題後,需要快速恢復止血,但如果沒有一套完整的回滾方案與策略,回滾速度慢或者在回滾過程中引入更大的問題都是不可接受的。
如何解決微服務上下線過程流量有損問題
如果新版本代碼沒問題,如何解決服務上下線過程中流量有損的問題?
減少不必要的 API 報錯,是最好的用戶體驗,也是最好的微服務開發體驗。如何解決這個在微服務領域內讓人頭疼的問題呢。在這之前我們先來了解一下爲什麼我們的微服務在下線的過程中會有可能出現流量損失的問題。
原理分析:無損下線
如上圖右側所示,是一個微服務節點下線的正常流程。
-
下線前,消費者根據負載均衡規則調用服務提供者,業務正常。
-
服務提供者節點 A 準備下線,先對其中的一個節點進行操作,首先是觸發停止 Java 進程信號。
-
節點停止過程中,服務提供者節點會向註冊中心發送服務節點註銷的動作。
-
服務註冊中心接收到服務提供者節點列表變更的信號後會,通知消費者服務提供者列表中的節點已下線。
-
服務消費者收到新的服務提供者節點列表後,會刷新客戶端的地址列表緩存,然後基於新的地址列表重新計算路由與負載均衡。
-
最終,服務消費者不再調用已經下線的節點。
微服務下線的流程雖然比較複雜,但整個流程還是非常符合邏輯的,微服務架構是通過服務註冊與發現實現的節點感知,自然也是通過這條路子實現節點下線變化的感知。
參考我們這邊給出的一些簡單的實踐數據,我想你的看法可能就會變得不同。從第 2 步到第 6 步的過程中,Eureka 在最差的情況下需要耗時 2 分鐘,即使是 Nacos 在最差的情況下需要耗時 50 秒;在第 3 步中,Dubbo 3.0 之前的所有版本都是使用的是服務級別的註冊與發現模型,意味着當業務量過大時,會引起註冊中心壓力大,假設每次註冊/註銷動作需要花費 20~30ms,五六百個服務則需要註冊/註銷花費掉近 15s 的時間;在第 5 步中, Spring Cloud 使用的 Ribbon 負載均衡默認的地址緩存刷新時間是 30 秒一次,那麼意味着及時客戶端實時地從註冊中心獲取到下線節點的信號,依舊會有一段時間客戶端會將請求負載均衡至老的節點中。
如上圖左側所示,只有到客戶端感知到服務端下線並且使用最新的地址列表進行路由與負載均衡時,請求才不會被負載均衡至下線的節點上。那麼在節點開始下線的開始到請求不再被打到下線的節點上的這段時間內,業務請求都有可能出現問題,這段時間我們可以稱之爲服務調用報錯期。
通過對微服務下線的流程分析,我們理解了解決微服務下線問題的關鍵就是:保證每一次微服務下線過程中,儘可能縮短服務調用報錯期,同時確保待下線節點處理完任何發往該節點的請求之後再下線。
那麼如何縮短服務調用報錯期呢?我們想到了一些策略:
-
將步驟 3 即節點向註冊中心執行服務下線的過程提前到步驟 2 之前,即讓服務註銷的通知行爲在應用下線前執行,考慮到 K8s 提供了 Prestop 接口,那麼我們就可以將該流程抽象出來,放到 K8s 的 Prestop 中進行觸發。
-
如果註冊中心能力不行,那麼我們是否有可能服務端在下線之前繞過註冊中心直接告知客戶端當前服務端節點下線的信號,該動作也可以放在 K8s 的 Prestop 接口中觸發。
-
客戶端在收到服務端通知後,是否可以主動刷新客戶端的地址列表緩存。
如何儘可能得保證服務端節點在處理完任何發往該節點的請求之後再下線?站在服務端視角考慮,在告知了客戶端下線的信號後,可以提供一種等待機制,保證所有的在途請求以及服務端正在處理的請求被處理完成之後再進行下線流程。
實戰演練:縮容過程中無損下線的表現
下面我們就通過一個實踐來看看無損下線的效果與表現:
該實踐分爲四個步驟,首先我們在 ACK 控制檯配置定時伸縮任務,模擬應用擴縮容的場景,我們可以配置 5 分鐘內第 2 分鐘擴容,第 4 分鐘縮容;第二步,應用接入 MSE 服務治理後默認會具備無損下線能力,我們需要將基線環境增加環境變量關閉無損下線能力,作爲對照組;灰度環境接入 MSE 服務治理後,不做任何操作,即默認開啓無損下線能力,作爲實驗組;第三步,我們同時發起基線跟灰度的流量,觀察其表現;第四步,我們在 MSE 應用詳情的 QPS 數據模塊中可以看出,未打標環境(關閉無損下線的應用)在擴縮容過程中出現了 99 個異常,然而灰度環境則無任何錯誤數。
📝 更多實驗細節詳見 MSE 文檔,結果驗證:無損下線功能 [ 1]
原理分析:無損上線
爲什麼有了無損下線,還需要無損上線?應用啓動的過程中也存在許多問題。我們將一個微服務應用的啓動過程抽象總結成如下流程,在如下各個階段我們都會遇到一些問題。
我們先看一下一個標準的微服務啓動過程中分別要經過哪幾個階段:
-
首先是應用初始化階段,在這段時間內會進行 Spring Bean 的加載與初始化邏輯;
-
第二階段就是連接池連接建立階段,比如我們有用到 Redis 的話,就有涉及到 JedisPool 的連接池創建,默認情況下連接池創建後不會立即創建連接,只有等待請求進入後纔會去建立連接,之前有個頭部客戶,在應用啓動的過程中就因爲連接還沒建立完成,但是有大流量湧入,導致大量線程阻塞在連接創建階段,導致大量的請求錯誤,因此在這個階段我們需要提前將連接池的核心連接建立完成;
-
第三階段就是服務註冊,我們知道一旦服務完成註冊,那麼就意味着 Consumer 應用可以發現當前服務,就會有微服務的流量進入當前應用,因此我們需要確保在服務註冊之前應用初始化就緒;在某些場景下,比如大數據計算服務,服務在啓動後需要異步加載一些準備資源,大數據服務需要提前從 OSS 拉取幾百兆的數據,等就緒後才能提供服務,因此如果應用啓動後直接註冊服務,會導致在異步資源就緒前的流量都因爲前置資源沒就緒而導致報錯;
-
第四個階段就是需要聯動 K8s 生命週期,一旦 K8s 的就緒檢查通過就認爲當前 Pod 已經啓動完成。在 K8s 滾動發佈的過程中,Readiness 通過後說明 Pod 啓動就緒,K8s 就會自動進行下一批 Pod 的滾動發佈。如果 Readiness 僅僅關聯端口啓動,會出現這樣的情況,老的 Pod 都已經停機了,但是最早新起的 Pod 依舊沒進行完服務註冊,從而在短時間內會存在註冊中心中該服務沒有地址的情況,客戶端就會在發佈過程中出現 service no provider 異常。
-
第五個階段就是應用跟 Pod 都就緒了,微服務應用在剛啓動後由於 JIT 預熱、框架資源懶加載等情況,會導致應用剛啓動後容量會相比正常情況下小很多,Java 應用在這個場景下問題會顯得更加明顯。如果這時候不進行任何預處理,應用直接接收線上均分下來的大流量極易出現大量請求響應慢,資源阻塞,應用實例宕機的現象。因此在這階段我們需要通過小流量預熱的方式,合理分配少量的流量啓到充分預熱我們系統的作用。
只有妥善處理完上面的所有流程,我們的微服務應用在其啓動後纔可以從容地面對生產上的大流量。
實戰演練:無損上線效果演示
下面我們來快速看一下無損上線的 Demo:
首先,我們需要在 MSE 無損上線頁面開啓無損上線開關,並配置預熱時長;然後我們重啓應用後,我們就可以看到上線 Pod 的流量曲線如上圖所示,緩慢增長,符合小流量預熱曲線。
📝 更多實驗細節詳見 MSE 文檔,結果驗證:無損上線功能 [ 2]
實戰演練:壓測態下的無損上下線表現
本次實踐的 Demo 以開源 Spring Cloud 框架爲例,我們準備瞭如下 demo。流量是阿里雲性能測試服務 PTS 發起,通過開源的 Zuul 網關流入我們的系統。其中其服務調用鏈路如下圖所示:
圖中流量從 Netflix Zuul 對應的 Ingress 進來,會調用 SC-A 應用對應的服務,SC-A 應用內部調用 SC-B 應用的服務,SC-B 應用內部調用 SC-C 應用的服務。
我們看下真實壓測(500qps 持續壓測)場景下無損上下線的效果:
左圖是未接入 MSE 的開源 Spring Cloud 應用表現,我們觀察到從 17:53:42 開始出錯,17:54:24 停止報錯,其中報錯時長持續 42 秒,總共出現 1 萬多個異常。相比實驗組,接入 MSE 無損上下線的應用在這個過程中,應用發佈對業務流量來說沒任何損失,整個過程非常平滑。
📝 更多實驗細節詳見 MSE 文檔,揭祕大流量場景下發布如「絲般順滑」背後的原因 [ 3]
一個簡單的 Demo 應用表現都如此令人喫驚,那麼在微服務架構下,生產系統面對每秒上萬次請求的流量洪峯,在這個過程中即使服務調用報錯期只有短短几秒,對於企業來說都是非常痛的影響。在一些更極端的情況下,服務調用報錯期可能會惡化到數分鐘,這導致許多企業不敢發佈,最後不得不每次發版都安排在凌晨兩三點。對於研發來說每次發版都是心驚膽顫,苦不堪言。無損上下線的能力就是爲了解決這個問題,保證發佈變更過程中流量無損。
如何控制微服務變更過程中的風險
如果我們新版本的代碼有問題,那麼我們如何可以有效地控制問題影響面?
爲了控制變更過程中的風險,我們都知道通過灰度發佈的方式來控制問題的影響面;但是在微服務架構的場景下,傳統的灰度發佈模式往往不能滿足微服務交付的複雜、多樣化的需求。這是因爲:
- 微服務調用鏈路比較長,比較複雜。在微服務架構中,服務之間的調用鏈路比較複雜,一個服務的改動可能會影響到整個調用鏈路,從而影響整個應用的穩定性。
- 一次灰度可能涉及多個模塊,整個鏈路都要調用新版本。由於微服務架構中服務之間相互依賴,一個服務的修改可能需要其他服務的相應調整。這就導致了在進行灰度發佈時,需要同時調用多個服務的新版本,增加了發佈的複雜度和不確定性。
- 多個項目並行,需要部署多套環境,環境構建不靈活、成本高。在微服務架構中,往往會有多個項目並行開發,需要部署多套環境來支持不同的項目。這就增加了環境構建的難度和成本,從而導致發佈效率低下。
爲了解決這些問題,我們需要採用更加靈活、可控並且適用於微服務場景的發佈方式, 全鏈路灰度發佈的場景也就應運而生。通常每個微服務都會有灰度環境或分組來接受灰度流量。我們希望進入上游灰度環境的流量也能進入下游灰度的環境中,確保 1 個請求始終在灰度環境中傳遞,從而形成流量“泳道”。在“泳道”內的流量鏈路中,即使這個調用鏈路上有一些微服務應用不存在灰度環境,那麼這些微服務應用在請求下游應用的時候依然能夠回到下游應用的灰度環境中。
生產的流量是端到端的,實現全鏈路灰度“泳道”那麼意味着我們需要控制流量在前端、網關、後端各個微服務等組件中閉環。不僅僅是 RPC/Http 的流量,對於異步調用比如 MQ 流量我們也需要符合全鏈路“泳道”調用的規則,這整個過程中涉及到的流量控制的複雜度也是非常高的。
MSE 全鏈路灰度方案
MSE 通過簡單的 UI 頁面透出流量“泳道”的模型,我們可以通過創建泳道組與泳道,快速實現微服務架構下的全鏈路灰度發佈能力。
-
MSE 支持在控制檯動態配置流量匹配規則引入精細化流量
-
- 支持數值比較、正則匹配、百分比條件等靈活的條件匹配規則,從而滿足複雜的灰度發佈訴求;
- 關於 Http 流量支持 Header、param、cookie 等參數進行匹配;關於 Dubbo 流量可按照服務、方法、參數進行匹配;
-
全鏈路隔離流量“泳道”
-
- 通過設置流量規則對所需流量進行'染色','染色'流量會路由到灰度機器。
- 灰度流量攜帶灰度標籤自動傳遞,形成灰度專屬環境流量泳道,對於無灰度環境應用灰度流量會默認選擇未打標的基線環境;其中流量標籤傳遞默認支持RPC、MQ 等流量。
-
端到端的穩定基線環境
-
- 未打標的應用屬於基線穩定版本的應用,即穩定的線上環境。當我們將發佈對應的灰度版本代碼,然後可以配置規則定向引入特定的線上流量,控制灰度代碼的風險。
- 在每一跳請求路由的過程中,灰度流量優先走流量標籤對應的機器,如果沒有匹配則 fallback 到基線環境;
-
流量一鍵動態切流,流量規則配置後,可根據需求進行一鍵停啓,增刪改查,實時生效。灰度引流更便捷。
-
低成本接入,基於 Java Agent 技術實現無需修改一行業務代碼;無縫支持市面上近 5 年的所有 Spring Cloud 和 Dubbo 的版本,用戶不用改一行代碼就可以使用,不需要改變業務的現有架構,隨時可上可下,沒有綁定。
同時 MSE 全鏈路灰度能力還提供了配套的可觀測能力,我們通過全鏈路灰度觀測可以實時看到灰度情況的流量曲線,無論是測試驗證階段、還是生產灰度發佈過程中,對於灰沒灰到的流量情況我們可以做到心中有數。
誰都在用 MSE 全鏈路無損發佈方案?
在阿里雲上的企業應用如茶百道、極氪汽車和來電等,他們是如何解決變更引起的穩定性風險,實現了在白天高流量情況下應用發佈平滑無損。MSE 全鏈路無損發佈方案助力雲上微服務實現白天發佈自由。
極氪汽車
隨着極氪汽車銷售越發火爆,其註冊用戶和每日活躍用戶快速增長,需要支持的業務場景和新功能也越來越多,平均兩三天一個小版本、半個月一個大版本的升級頻率。爲了不影響白天業務高峯,每次發版只能選擇在凌晨業務低峯期進行,想象一下如果開發/運維/測試人員每次都集中在晚上發佈,那麼這些參與發佈的同學第二天的工作效率將會受到影響;如果晚上選擇較少的人蔘與發佈,那麼當出問題的時候止血措施很可能會來不及實施,故障責任也不好劃分。
極氪汽車針對核心業務鏈路上多個微服務同時需要發版的場景,基於 MSE 全鏈路無損發佈解決方案實現了多業務的全鏈路灰度,通過這種方式讓客戶在不需要更改任何業務代碼的情況下實現多業務白天發版,同時通過逐步流量放大進行驗證,如出現問題可及時回切流量,降低了白天發佈可能導致的穩定性風險。 同時通過改造雲效流水線,幫助客戶實現核心業務自動化發佈,進一步提升了發佈的效率。
來電科技
來電科技在全面微服務化、容器化改造完成之後就面臨瞭如何用好微服務的難題,他們核心架構師也是 Dubbo 社區的參與者,對比了自研微服務治理的成本後,他們決心使用 MSE 治理解決微服務化深入後面臨的穩定性與效率的問題。來電科技通過 MSE 全鏈路無損發佈解決方案有效解決變更過程中灰度的訴求,以及發佈過程中流量抖動的問題, 這個過程中無需代碼改動與架構調整,藉助 MSE 服務治理以更經濟的方式、更高效的路徑在雲上快速構建起完整微服務治理體系。
總結
如何穩定地使用微服務已成爲大家關注的話題。本文詳細介紹了阿里雲微服務全鏈路無損發佈最佳實踐方案,相信這個方案可以幫助雲上的企業更好地利用微服務。在過去幾年許多企業客戶的實踐中,我們逐步完善了這個方案。我相信這些最佳實踐就像大海中的明珠一樣,在不斷的實踐和時間的磨礪中變得更加耀眼奪目。我們期待着將這些實踐與經驗分享給更多企業,讓他們能夠更好地應用微服務,取得更好的效果。
相關鏈接:
[1] 結果驗證:無損下線功能
[2] 結果驗證:無損上線功能
[3] 揭祕大流量場景下發布如「絲般順滑」背後的原因