如何构建一个流量无损的在线应用架构 | 专题中篇
簡介:本篇是整個《如何流量無損的在線應用架構》系列的第二篇,這一系列共三篇,旨在使用最為樸素的語言將影響在線應用流量穩定性的技術問題做一個歸類,這些問題的解決方案有的只是一些代碼層面的細節,有的需要工具進行配合,有的則需要昂貴的解決方案,如果您的應用想在云上有一個【流量無損】的一站式體驗,可以關注阿里云的《企業級分布式應用服務(EDAS)》這個云產品,EDAS 也將會持續向默認接入流量無損的方向演進.下一篇,我們將從數據服務交換的角度進行講解,更重要的是下一章還會點出重點預防的兩把鑰匙。
作者 | 孤弋、十眠
前言
上一篇如何構建流量無損的在線應用架構 | 專題開篇 是我們基于下圖,講解了流量解析與流量接入兩個位置做到流量無損的一些關鍵技術,這一篇我們主要從流量服務的維度,詳細聊聊正式服務過程中會影響線上流量的一些技術細節。
服務
最近我們分析了某大型互聯網公司最近一年的線上故障原因,其中,產品自身質量問題(設計不合理、BUG 等)占比最高,為 37%;由于線上發布、產品和配置變更引起的問題占比 36%;最后是依賴服務的高可用類的問題如:設備故障,上下游的依賴服務出現問題或者資源瓶頸等。
基于左側的原因分析可以看出,如何治理好變更、產品質量、和高可用類的問題,是服務如何做到流量無損的關鍵所在。我們把這個幾點原因結合應用的生命周期劃分成為了幾個關鍵階段:
- 應用變更態:當我們的服務(應用)進行版本發布、配置變更的整個過程中,我們需要有確切的手段做保護。
- 運行服務態:當我們的變更完畢后,一開始是一個 “冷”的狀態,如何在正常流量甚至超常規流量到來之前的,讓系統平穩度過?
- 高可用依賴態:當我們服務的某些節點出現問題,或者我們外部的依賴(如 其他微服務、DB、緩存)等出現瓶頸時,是否有相對應的辦法?
為了從容地應對以上這些場景,我們將分別列舉相關例子來探討相應的解決方案。
1、變更態:應用優雅下線
應用變更時有一步,是需要將原有的應用停止。而在生產環境中的在線應用停止之前,需要將服務流量的服務下線。我們常說的流量主要有兩種:1)同步調用的流量,如:RPC、HTTP 請求等;2)另外一種是異步流量,如:消息消費、后臺任務調度等。
以上兩種流量,如果在服務端還存在未處理完的請求時將進程停止,都會造成流量損失。要想解決這一種情況,通常情況需要分兩步進行:
1)將現有節點在相應的注冊服務中摘除,場景有:將 RPC 服務在注冊中心的節點中摘除、將 HTTP 服務在上游的負載均衡處摘除、將后臺任務(消息消費等)的線程池嘗試關閉,不再進行新的消費或服務。
2)停頓一段時間(依據業務情況而定),確保進程中已經進來的流量能得到很好的處理之后,再將進程關閉。
2、變更態:應用調度
變更過程中另外一個動作就是選擇資源(機器或容器)之后發起一次部署,如何選擇資源就是我們通常意義上理解的【調度】,如果是傳統的物理機或者調度能力欠缺的虛擬機的情況,調度這一層沒有太多的發揮空間,因為他的資源基本上都是固定的;但是容器技術(尤其后來的 Kubernetes 技術的普及)的出現除了給交付領域帶來了諸多的變化之外,他也給調度領域帶來了的不一樣的故事,即他從傳統的規劃資源分配引領到了靈活調度的時代。
在 Kubernetes 中,默認可以根據應用使用的資源(CPU、內存、磁盤等)信息,來選擇一個最為合適的節點進行調度。如果我們更進一步,也可以根據自身應用的流量特征定制一個調度器,比如盡量讓流量大的應用不聚集在相同的節點上,這樣就能避免因為搶占帶寬而造成的流量損失。
3、變更態:應用優雅上線
當應用調度到了相應的資源之后,接下來是部署和啟動應用。和應用停止的場景類似,應用在完全初始化完成之前,我們的節點很可能已經被注冊,后臺任務的線程池也很可能開始啟動了。此時上游服務(如 SLB 開始路由、消息開始消費)就會有流量調度進來。但是在應用被完全初始化完成之前,流量的服務質量是無法得到保證的,比如一個 Java 應用啟動之后的前幾次請求,基本上是一個“卡頓”的狀態。
如何解決這個問題呢?和應用的優雅下線動作序列相反,我們需要有意識的將服務注冊、后臺任務線程池、消息消費者線程池的初始化動作滯后。要確保等到應用完全初始化之后再進行。如果有外置的負載均衡路由流量的場景,還需要應用部署的自動化工具進行相應的配合。
4、變更態:應用服務預熱
系統完成上線之后,有時如果遇到流量突增,可能會令系統水位瞬間升高進而導致崩潰。典型場景如大促時的零點,洪峰涌入時,應用實例會瞬間進入大量的流量,這些流量會觸發諸如 JIT 編譯、框架初始化、類加載等底層資源優化的問題,這些優化會在短時間之內給系統造成高負載的問題,進而造成業務流量損失。為了解決這個問題,我們需要控制流量緩慢增加,通過開啟類加載器并行類加載,框架提前初始化,日志異步化等方式提升剛啟動應用的業務容量,從而實現大流量場景下的擴容、上線等操作的流量無損。
5、變更態:Kubernetes 服務結合
從 2020 年開始,我們看到一個明顯的趨勢就是 Spring Cloud ?+ Kubernetes 已經成為了微服務體系中最流行的配搭。而在一個基于 Kubernetes 構建的微服務體系中, 如何將微服務體系和 Kubernetes 進行有效的結合是很有挑戰的一個點,Kubernetes 中的 Pod 生命周期管理本身就提供了兩個探測點:
- RreadinessProbe,用于探測一個 Pod 是否就緒接受流量,探測失敗將會在 Kubernetes Service 中摘取該節點,且該節點的狀態為 NotReady 。
- LivenessProbe,用于探測 Pod 是否健康,如探測失敗將會重啟 Pod。
如果我們的應用沒有配置 readinessProbe ,默認只會檢查容器內進程是否啟動運行,而對于進程中運行的業務是否真的健康是很難考量的。在發布的過程中,如果我們使用的是滾動發布策略,那么當 Kubernetes 發現新起的 Pod 中的業務進程已經啟動了,Kubernetes 就會開始銷毀老版本的 Pod,看起來這個過程是沒有什么問題的。但我們仔細想一下,“新起的 pod 中的業務進程已經啟動”,并不代表“業務已經啟動就緒”,有的時候如果業務代碼存在問題,那么我們的進程啟動了,甚至業務端口也已經暴露了,但是由于業務代碼等異常情況,導致進程起來后服務還沒來得及注冊。可此時老版本的 Pod 已經銷毀。對于應用的消費者來說,可能會出現 No Provider 的問題,從而導致在發布的過程中出現大量的流量損失。
同樣,如果我們的應用沒有配置 livenessProbe ,Kubernetes 默認只會檢查容器內進程是否存活,而當我們的應用的某個進程由于資源競爭、FullGc、線程池滿或者一些預期外的邏輯導致其處于假死的狀態時,進程雖然存活,但是服務質量低下甚至是為 0。那么此刻進入到當前應用的全部流量都會報錯,出現大量的流量損失。此刻我們的應用應該通過 livenessProbe 告訴 Kubernetes 當前應用的 Pod 處于不健康且已經無法自己恢復的狀態,需要對當前 Pod 進行重啟操作。
readinessProbe 和 livenessProbe 的配置,目的是及時且靈敏地反饋當前應用的健康情況,以此來保證 Pod 內的各個進程都處于健康狀態,從而保證業務的流量無損。
6、變更態:灰度
一次版本的迭代,我們很難保證新的代碼經過測試后,在線上就沒有任何問題。為什么大部分的故障和發布相關?因為發布是整體業務發布到線上的最后一個環節,一些研發過程中累計的問題,很多時候最后發布環節才會觸發。換句話說,一個潛規則就是公認線上發布基本上不可能沒有 BUG,只是大小而已,但是發布環節要解決的問題就是:既然肯定會有問題,那如何將問題的影響面降至最小?答案是灰度。如果有一些沒有測試到的問題,恰巧我們線上也是全量一批發布的,那么錯誤將會因為全網鋪開而被放大,出現大量且長時間的線上流量損失。如果我們系統具備灰度能力(甚至全鏈路灰度),那么我們就可以通過灰度發布的方式將問題的影響面控制到最低。如果系統具備完整的灰度過程中的可觀測能力,那么發布就會穩定且安全得多。如果灰度能力可以打通全鏈路流程,那么即使是同時面對多個應用的發布都可以有效保證線上流量無損。
7、運行態:服務降級
當應用遇到業務高峰期,發現下游的服務提供者遇到性能瓶頸,甚至即將影響業務時。我們可以對部分的服務消費者進行服務降級操作,讓不重要的業務方不進行真實地調用,直接返回Mock的結果甚至異常返回,將寶貴的下游服務提供者資源保留給重要的業務調用方使用,從而提升整體服務的穩定性。我們把這個過程叫做:服務降級。
當應用依賴的下游服務出現不可用的情況,導致業務流量損失。您可以通過配置服務降級能力,當下游服務出現異常時,服務降級使流量可以在調用端 "fail fast",有效防止雪崩。
8、運行態:自動離群摘除
與服務降級類似,自動離群摘除是在流量服務的過程中,遇到單個服務不可用時自動將節點進行摘除的能力,他區別于服務降級主要是體現在兩點:
1)自動完成:服務降級是一種運維動作,需要通過控制臺進行配置,并且指定對應的服務名才能做到相應的效果;而【自動離群摘除】能力是會主動探測上游節點的存活情況,在這條鏈路上整體做降級。
2)摘除粒度:服務降級降級的是(服務+節點 IP),以 Dubbo 舉例子,一個進程會發布以服務接口名(Interface)為服務名的微服務,如果觸發到這個服務的降級,下次將不再調用這個節點的此服務,但是還是會調用其他服務。但是【離群摘除】是整個節點都不會去嘗試調用。
9、高可用:注冊中心容災
注冊中心作為承擔服務注冊發現的核心組件,是微服務架構中必不可少的一環。在 CAP 的模型中,注冊中心可以犧牲一點點數據一致性(C),即同一時刻每一個節點拿到的服務地址允許短暫的不一致,但必須要保證的是可用性(A)。因為一旦由于某些問題導致注冊中心不可用,連接他的節點可能會因為無法獲取服務地址而對整個系統出現災難性的打擊。除了常見的高可用手段,注冊中心特有的容災手段還有:
1)推空保護:數據中心網絡抖動或者在發布的過程中,會經常出現批量閃斷的情況,但這種情況其實不是業務服務的不可用,如果注冊中心識別到這是一種異常情況(批量閃斷或地址變空時),應該采取一種保守的策略,以免誤推從而導致全部服務出現"no provider"的問題,所有微服務會因此導致大量的流量損失。
2)客戶端緩存容災:與推空保護一樣,站在客戶端的角度邏輯同樣適用,我們經常遇見客戶端和注冊中心出現網絡問題時將地址更新的情況,客戶端也不能完全相信注冊中心反饋的所有結果,只有明確告知的是正常的結果才能將內存中的地址更新,尤其遇到最后一個地址時采取的策略更要慎重;同時拿到地址之后,也不能完全相信,因為很可能注冊中心推送下來的地址根本就不可達。此時要有類似于心跳保活的策略可動態調整對端服務是否可用,以免將服務直接發往無法連接的地址導致流量損失。
3)本地緩存容災:注冊中心容災了,客戶端也容災了是否足夠?通常情況如果不做變更是足夠的,但是如果有一個應用在進行變更時注冊中心不可用的話,會發生什么事情呢?一個是本身地址注冊不上,另外一個就是如果有服務發生依賴調用時,流量進來后會出現"no provider"而導致流量損失,甚至根本就無法啟動。如果我們把注冊中心的依賴簡化理解為就是對一個服務名字的地址解析的話,其實我們可以將這個解析結果保存在本地做成容災備份,這樣就能有效避免在變更過程中因為注冊中心不可用而導致流量損失。
10、高可用:同城多機房容災
同城的特點是 RT 一般處在一個比較低的延遲(< 3ms 以內),所以在默認情況下,我們可以基于同城的不同機房搭建起來一個大的局域網,然后把我們應用跨機房分布在多個機房中,以此來應對單機房出現故障時的流量受損風險。相比異地多活,這種基礎設施的建設成本較小,架構變動也比較小。不過在微服務體系之下,應用之間的鏈路錯綜復雜,隨著鏈路深度越來越深,治理的復雜度也會隨之增加,如下圖所示的場景就是前端流量很有可能因為在不同的機房相互調用而導致 RT 突增,最終導致流量損失。
要解決上面的問題,關鍵是在服務框架層面需要支持同機房優先路由的能力,即:如果目標服務和自己所在機房相同,則優先將流量路由至和我同機房的節點。要實現這個能力的方法大致是注冊服務時將自身所在的機房信息上報,機房信息也當成元信息推送至調用方,調用方在路由時通過定制服務框架的 Router 的能力,優先選擇和自己相同機房的地址作為目標地址路由。
結語
本篇是整個《如何流量無損的在線應用架構》系列的第二篇,這一系列共三篇,旨在使用最為樸素的語言將影響在線應用流量穩定性的技術問題做一個歸類,這些問題的解決方案有的只是一些代碼層面的細節,有的需要工具進行配合,有的則需要昂貴的解決方案,如果您的應用想在云上有一個【流量無損】的一站式體驗,可以關注阿里云的《企業級分布式應用服務(EDAS)》這個云產品,EDAS 也將會持續向默認接入流量無損的方向演進.下一篇,我們將從數據服務交換的角度進行講解,更重要的是下一章還會點出重點預防的兩把鑰匙。
原文鏈接
本文為阿里云原創內容,未經允許不得轉載。?
總結
以上是生活随笔為你收集整理的如何构建一个流量无损的在线应用架构 | 专题中篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: caffe编译出现 libcudart.
- 下一篇: 直播回顾:准确性提升到 5 秒级,ssa