天天看點

面向失敗的設計-服務能力與依賴調用自我保護

1. 引言

作為一種架構模式,微服務将複雜系統切分為數十乃至上百個小服務,每個服務負責實作一個獨立的業務邏輯。這些小服務易于被小型的軟體工程師團隊所了解和修改,并帶來了語言和架構選擇靈活性,縮短應用開發上線時間,可根據不同的工作負載和資源要求對服務進行獨立縮擴容等優勢。另一方面,當應用被拆分為多個微服務程序後,程序内的方法調用變成了了程序間的遠端調用,服務和服務之間的關系由“流量“來連接配接。

如何從流量的角度出發,來保證整個系統的穩定性,是這篇文章的出發點。下圖是一個常見的完整的服務流量圖。使用者的請求從手機端或者浏覽器發出,經過DNS解釋,到達公司的網關。網關背後的防火牆對惡意流量進行清洗,最後到達公司的網關。最終這個請求到達前端應用。前端應用或者直接傳回請求,或者會把部分請求交給下遊元件處理。這個下遊元件可能會是資料庫,緩存,也可能是下遊服務,甚至第三方應用。每個公司的結構或者會和這個系統有細節上的不同,例如客戶請求是長連接配接還是短連結,應用和應用之間是否需要加設網關等。

面向失敗的設計-服務能力與依賴調用自我保護

圖一:流量在系統裡的流轉

為了保證系統的穩定性,每個使用者的請求能夠得到最好的使用者體驗,我們在需要在運作态的情況下,在這個鍊路的不同的點,進行不同的防護。接下來的内容,我們根據這條鍊路上不同的點,采取不同的原則,依次讨論線上流量防護的最佳實踐。

2. 最佳實踐

從圖一裡我們可以看到,使用者的請求從網關層真正開始進入系統,根據業務鍊路的不同的深度,逐漸由前端應用,後端服務處理,最後到達緩存,資料庫,甚至第三方的應用。接下來,我們會根據鍊路上上的不同位置,逐一介紹不同的原則。

2.1 盡早攔截無效的請求

無效的請求,會占用系統的處理能力,但又無法給業務上帶來任何價值。我們需要盡早的識别并攔截把這些無效的請求。這些請求到達鍊路越下層,對系統帶來的負擔越重。

無效的請求主要分為兩種:

  • 惡意請求:通常是指惡意攻擊,例如DDOS攻擊,黃牛刷單等
  • 對業務不産生實際意義的請求。例如秒殺活動中,必定會引起在一定的時間段,到來大量的請求都是針對秒殺商品的流量。而這些請求,僅僅隻有和商品庫存數量相當的請求是有效請求,其餘都是無效請求。

基于這個原則,我們常常在網關層,對惡意請求進行安全攔截,最常見的惡意流量攔截有DDOS清洗,攔截大量的來自同個ip,同個使用者的請求等等。随着黑産的不斷發展,防護手段也變得越來越專業,到現在,攔截已經能夠快速的根據請求的行為模式,來判斷是否黑産流量了。

對于後者,則有很強的業務屬性。對這種無效請求的攔截,先要梳理出一條完整的業務鍊路,找到這條業務鍊路的有效最大流量,再根據這個流量,在鍊路前端對請求進行收緊。舉一個例子,一個由前端系統,商品服務系統(處理商品具體資訊),交易服務(交易處理),庫存服務(商品扣減庫存)組成的秒殺系統。這個業務的鍊路的最大有效請求量是秒殺商品的最大庫存。超過了這個秒殺商品的庫存的請求都是無效的。為此,我們需要在這個系統的最前端,例如前端系統設定攔截,禁止單個商品超過秒殺商品的請求通過。

2.2 漏鬥原則

剛剛我們讨論了盡早在鍊路入口攔截無效流量的原則,因為越到鍊路的後端,耗費的資源就越多。基于同樣的原因,我們也希望系統後端處理的請求能夠依次形成一個漏鬥。假如處理系統由前端頁面,WEB伺服器,應用伺服器,DB這個鍊路組成,那麼理想的流量模型則是:

前端頁面通過動靜分離,僅僅将動态的部分交給下遊的WEB伺服器處理;靜态的部分由緩存,或者CDN直接傳回。

當請求到達WEB伺服器之後,WEB伺服器能夠根據業務鍊路的短闆,盡早的把無效流量攔截住,僅把有效的流量傳遞給服務方。

同理,下遊的應用伺服器,也應該盡量通過緩存等手段,給下遊的資料庫攔截大部分的流量。

通過使用動靜分離,緩存等手段,讓後端的處理量變得越來越少,形成一個如下圖所示的流量處理漏鬥。

面向失敗的設計-服務能力與依賴調用自我保護

圖二:漏鬥原則

2.3 隔離原則

這個理念是由Hystrix

https://github.com/Netflix/Hystrix)

提出來的。它的核心理念是,能夠快速的識别出請求鍊路上的異常,并且快速的把改異常隔離,進而讓整個服務回到正常。下圖很好的诠釋了它的理念:

面向失敗的設計-服務能力與依賴調用自我保護

圖三:隔離異常

經過多年在阿裡巴巴集團使用的經驗,我們可以把隔離細分到下面幾個原則:

2.3.1 下遊弱依賴服務可降級

弱依賴應用是指整個鍊路中可以被降級,而不影響最後業務效果的下遊應用。打個比方,我們在淘寶浏覽某個商品的時候,這個請求先到某個前端應用,而這個前端應用又會向下遊服務,例如商品,評價中心,推薦中心等詢問,最終傳回商品的基礎資訊,購買過的人對該商品的評價,甚至對同類商品的推薦等等。對于這種場景,當提供商品基礎資訊的服務出現異常的時候,使用者是無法浏覽商品的,這個服務則是一個強依賴;而評價,推薦這兩種服務,如果出現異常,使用者仍然是可以正常的浏覽商品的。對于這兩種服務,我們稱它為弱依賴。

當弱依賴出現異常的時候,最快的方法是對改弱依賴進行降級。當發現下遊應用出現異常(響應時間過長,占用線程池過多,異常比例變大)的時候,迅速拒絕的方式,傳回給上遊系統。

如下圖所示:

面向失敗的設計-服務能力與依賴調用自我保護

圖四:弱依賴降級

通過這種手段,可以防止異常層層下傳,下遊應用拖垮上有應用,最大的把異常圈定在一定的範圍内。這種方式往往用于第三方應用,下遊弱依賴等。

2.3.2 強依賴下遊異常可隔離

上面降級的方法,僅僅适用于弱依賴下遊異常的場景。而對強依賴,即處理鍊路中必須經過的下遊應用,則無法如此處理。舉個常見的例子,應用A在處理某個業務請求的時候,必須對資料庫進行讀寫。這個時候,對資料庫進行讀寫的操作,就是這條業務鍊路的強依賴。而當對資料庫進行讀寫正好是個慢SQL的時候,大量的讀寫,會占用整個資料庫的連結數,導緻其它的資料庫讀寫無法操作,最終拖慢了整個應用。

對于這種強依賴,我們無法簡單的進行降級。但是我們可以通過慢sql的并發數量的方式,将異常操作在一定的範圍之内,而不會搶占其它正常資料庫操作。這個我們稱為錯誤隔離。業内有非常多的隔離的方案,比如通過不同業務邏輯使用不同線程池來隔離業務自身之間的資源争搶(線程池隔離),信号量,并發線程數等。這種隔離方案雖然隔離性比較好,但是代價就是線程數目太多,線程上下文切換的 overhead 比較大,特别是對低延時的調用有比較大的影響。我們更推薦使用信号量或者并發線程數的方式。這種方式無需建立和管理線程池,而是簡單統計目前請求上下文的線程數目,如果超出門檻值,新的請求會被立即拒絕,進而達到将慢SQL控制在一定的并發範圍之内,效果如下所示:

面向失敗的設計-服務能力與依賴調用自我保護

圖五:強依賴下遊可隔離

通過這個方式,把不穩定的内部處理控制在一定的範圍之内,并且不受響應時間的影響。如果處理請求的速度較慢,那麼通過的請求就會響應減少;反之,如果請求的處理速度恢複,通過的請求就會回升。

2.3.3 異常機器可隔離

除了對整個下遊應用進行隔離以外,我們也需要考慮從服務當中的個别機器出現異常的次元,來隔離異常。即我們常說的線上會出現單點、局部問題。

分布式環境中, 大家可能預設會認為我每台機器的服務能力都是一樣的, 但實際上, 我們線上機器會由于軟體、硬體、網絡等因素導緻機器的實時服務能力有差異。大家往往不會太在意單台機器的服務能力, 然而,當我們單機、局部服務能力出現問題時, 帶來的影響, 遠比我們預估的要嚴重。

面向失敗的設計-服務能力與依賴調用自我保護

圖六:隔離異常機器

  • 首先,分布式環境調用鍊路局部問題會被放大到整個鍊路。在今天這麼大流量的情況下, 任何單個系統, 都無法處理如今這複雜的業務邏輯。任意一個請求, 涉及到的決不僅僅是一個系統, 而是一整條鍊路。而鍊路中任何一個單點出現問題, 比如任意一台機器的RT變長、或者調用鍊路上的單點不可用, 會直接導緻我們整個調用鍊路RT變長或者調用鍊路不可用。
  • 其次,單點、局部問題會被放大成面。線上所有的調用鍊路真是的情況其實是網狀結構, 我們的一個應用會有着多個上、下遊應用, 因而一旦我們的單點、局部出現問題, 可能導緻的是下遊的應用都都到影響。1%的機器出現故障, 可能導緻100%的業務出現問題。

是以,我們需要具備在叢集中把出現異常的機器摘取識别出來的能力。這裡的異常機器包括:

  • 超賣問題帶來的資源争搶問題。超賣并不是一件壞事, 淘系成千上萬個應用, 大部分長尾應用, 即使在雙十一零點, 流量也是非常低的。這些應用機器的計算能力其實是有剩餘的, 合理的超賣, 可以提升我們機器的資源使用率。但從目前的部署結構來開, 核心應用其實也是存在部分超賣的機器的。這些超賣的機器, 一旦出現資源争搶, 很可能就導緻故障。
  • 啟動時部分機器LOAD飙高問題。部分應用, 部分機器啟動的時候, 容易出現個别機器load飙高, 導緻這部分機器RT變高。流量排程會根據機器的實際負載情況, 減少這部分機器的流量, 必要時, 遷移這部分剛釋出機器的流量到正常機器。
  • JVM假死、VM假死等問題
  • 受主控端影響, load飙高問題
  • JVM GC 影響RT問題
  • 網絡抖動導緻RT抖動
  • 其它單機、局部問題...

把這些影響因子進行收集,整合,作為篩選的依據。

有了這些依據意外,我們需要對線上的異常機器進行篩選。結合離線線上算法,推斷出可能異常的機器。并且根據一定的業務規則,來保證篩選出來的機器都是有效機器,并且不會對全局造成影響。

面向失敗的設計-服務能力與依賴調用自我保護

圖七:異常機器自動檢測

篩選出了異常機器之後,我們可以把這些機器做下線處理。

最後,涉及到一個異常機器恢複的過程。我們需要根據篩選出異常機器的狀态,進行恢複。這裡涉及到一定的決策算法。以最小的代價,恢複到正常的狀态。如下圖所示。

面向失敗的設計-服務能力與依賴調用自我保護

圖八:異常機器自動恢複政策

2.3.4 正常流量/刷單流量可隔離

我們需要能夠把熱點流量和非熱點流量隔離出來。防止熱點的流量搶占正常流量的處理能力,如下圖所示:

面向失敗的設計-服務能力與依賴調用自我保護

圖九:正常流量/非正常流量隔離

當大量的使用者流量去搶一個熱點商品的時候,這些流量的有效性隻會由熱點商品的庫存量決定。而如果無差別的進行控制,則會把正常的流量搶占。我們需要有手段,能夠把熱點流量和正常流量識别并且隔離出來。不要讓無效的流量搶占了正常流量的處理能力。

2.4 流量錯峰

使用者的請求可能會出現突刺。如下圖所示。如果此時要處理所有請求,很可能會導緻系統負載過高,影響穩定性。但其實可能後面幾秒之内都沒有請求,若直接把多餘的請求丢掉則沒有充分利用系統處理消息的能力。我們希望可以把請求突刺均攤到一段時間内,讓系統負載保持在請求處理水位之下的同時盡可能地處理更多消息,進而起到“削峰填谷”的效果,如下圖所示:

面向失敗的設計-服務能力與依賴調用自我保護

圖十:流量錯峰

最理想的效果是把上圖中紅色的部分推遲到系統空閑的時候處理,如綠色區域所示。

這種場景往往用于消息,異步處理的場景。

我們可以通過勻速器的方式,可以把突然到來的大量請求以勻速的形式均攤,以固定的間隔時間讓請求通過,以穩定的速度逐漸處理這些請求,起到“削峰填谷”的效果,進而避免流量突刺造成系統負載過高。同時堆積的請求将會排隊,逐漸進行處理;當請求排隊預計超過最大逾時時長的時候則直接拒絕,而不是拒絕全部請求。

如下圖所示,通過配置勻速模式下請求 QPS 為 5,系統會每 200 ms 處理一條消息,多餘的處理任務将排隊;同時設定了逾時時間為 5 s,預計排隊時長超過 5 s 的處理任務将會直接被拒絕。示意圖如下圖所示:

面向失敗的設計-服務能力與依賴調用自我保護

圖十一: 勻速器原理

2.5按需配置設定

服務提供方用于向外界提供服務,處理各個消費者的調用請求,我們稱之為Service Provider.對服務提供方的流量控制可分為服務提供方的自我保護能力和服務提供方對服務消費方的請求配置設定能力兩個次元:

服務提供方的自我保護能力:為了保護 Provider 不被激增的流量拖垮影響穩定性,可以給 Provider 配置QPS 模式的限流,這樣當每秒的請求量超過設定的門檻值時會自動拒絕多的請求。限流粒度可以是 服務接口 和 服務方法 兩種粒度。

根據調用方的需求來配置設定服務提供方的處理能力也是常見的限流方式。比如有兩個服務 A 和 B 都向 Service Provider 發起調用請求,我們希望隻對來自服務 B 的請求進行限流

此外,當出現限流的時候,必須要考慮對限流的處理。這個能力我們稱之為Fallback.由使用者自定義限流之後的處理邏輯。

3 總結

流控是保證服務SLA(服務等級協定)的重要措施,也是業務高峰期故障預防和恢複的有效手段。在實踐中,各種流量控制政策需要綜合使用才能起到較好的效果,一個好的流控架構 也不許做到不需要重新開機應用即可線上生效,提升上線服務治理的效率和靈活性。

文章來源:AlibabaTechQA

開發者社群整理