微服務架構下如何通過弱依賴原則保障系統高可用

前言

當我初次接觸高可用這個概念的時候,對高可用的【少依賴原則】和【弱依賴原則】的邊界感模糊,甚至有些“傻傻分不清楚”。這兩個原則都關注降低模塊之間的依賴關係,但它們之間的確存在某些差異。

那麼,「少依賴原則」和「弱依賴原則」它們之間本質的區別究竟是啥?

少依賴原則和弱依賴原則都是旨在提高系統的可靠性和穩定性,但是它們之間的本質區別在於對依賴關係的管理控制

1. 少依賴原則(Less Dependency Principle):這一原則強調系統設計階段的模塊獨立性,目的是從源頭上降低故障傳播的風險,通過降低模塊之間的耦合度,讓各個模塊獨立完成特定功能,減少不必要的依賴。
2. 弱依賴原則(Weak Dependency Principle):弱依賴原則關注的是在系統運行過程中,如何管理和控制模塊之間的依賴關係,以便在某個模塊出現故障時,其他模塊仍能正常運行。通過實現弱依賴,使得系統具備更好的容錯能力高可用性

當然,這兩個原則並不是絕對的,兩者之間有一定的關聯性,我們可以根據系統的複雜性和實際需求,靈活地調整模塊之間的依賴關係。在實際應用中,少依賴原則和弱依賴原則也可以相互配合,共同提高系統的高可用性。

一、基於弱依賴原則的架構策略

弱依賴原則 一定要依賴的,儘可能弱依賴,越弱越好 事物a強依賴事物b,一旦b出問題時,那麼a也會出問題,一損俱損。 所以任何強依賴都要儘可能的轉化成弱依賴,可以直接降低出問題的概率。

1、微服務架構

模塊拆分:微服務架構將複雜的應用程序拆分爲多個獨立的、可組合的服務模塊。每個服務具有明確的功能邊界和職責,相互之間具備松耦合的特點。這樣,當某個服務出現故障時,其他服務可以繼續獨立運行,保證系統的整體可用性

獨立部署:各個模塊有自己獨立的代碼倉庫,可以獨立進行分組部署和升級,無需其他模塊的配合。當某個服務發生故障或需要升級時,不會影響到其他服務的正常運行。特別針對黃金交易鏈路上的系統,有條件的話儘量考慮提供獨立的數據資源(DB、redis),並進行垂直分組部署。

需要注意的是:部署隔離需要充足的部署資源,以及上下游配合,儘量提前做好該工作。當然,模塊隔離必須建立在低耦合的基礎上進行纔有意義。如果組件之間的耦合關係千頭萬緒、混亂不堪,模塊隔離只會讓這種混亂雪上加霜。

2、異步通信

異步通信可以認爲是在模塊隔離基礎上的進一步解耦,將物理上已經分割的模塊之間的強依賴關係進一步削弱,使故障無法傳播擴散,提高系統可用性。異步在架構上的實現手段主要是使用消息隊列,當一個模塊發生故障時,另一個模塊可以繼續處理任務,不會受到太大影響。

以我正在做的職能架構升級項目爲例,其中創建員工賬號的場景:新員工在PC頁面提交創建賬號的請求後,需要將員工信息進行數據持久化,併發送創建成功的短信和郵件給員工,此外,還需將員工信息同步給人資系統。

如果用微服務同步調用的方式,那麼後續操作任何一個故障,都會導致業務處理失敗,員工無法創建成功。我們通過使用消息隊列的異步架構,新員工創建時發mq後就立即響應「創建成功」,後續的操作通過消費消息來完成,即使某個操作發生故障,後續再補償也不會影響員工的創建流程。





 

圖1.1 創建員工業務流程圖

3、接口抽象

耦合度過高是軟件設計的萬惡之源,也是造成系統可用性問題的罪魁禍首。一個高度耦合的系統,可謂“牽一髮而動全身”,任何微小的改動都可能會引發意想不到的bug和系統崩潰。連最基本的功能維護都已經勉爲其難,更不用奢談什麼高可用了。

我們可以通過定義抽象的策略接口這個抽象接口通常是從多個具有共同特徵行爲的類中抽象出來的,而具體實現類的指定都交給工廠類去完成,從而實現模塊之間的松耦合。這樣,當某個模塊發生變化時,也不會影響其他模塊的正常運行。接口抽象的方式,我將在後文的「實際場景分析」,針對具體案例展開詳細的討論。

4、故障切換與容錯

設置完善的故障處理機制,包括故障檢測、故障切換和故障恢復。當檢測到故障時,系統可以快速切換到備用組件或恢復服務,保證系統的可用性。

數據分片:存儲數據時,將其分佈在多個存儲節點上。當某個存儲節點發生故障時,數據可以從其他節點恢復,從而提高數據的可用性和容錯性。

讀寫分離:對於接受弱一致性的場景,將讀操作分配給從數據庫,寫操作分配給主數據庫,以提高系統的性能和穩定性,並支持主從切換;數據量大時,也可以進行分庫分表處理。

兜底降級:當一個系統中的強依賴服務數量較少時,其整體基礎穩定性便會越高。對於那些特殊數據依賴較多而邏輯依賴較少的系統,我們可以採取去依賴的架構設計策略。具體來說,就是將依賴服務數據持久化異構到自己的數據庫,並通過異步方式進行同步更新維護,從而降低對其他系統的依賴程度,進一步提升系統的穩定性。然而,這種方法也存在一定的弊端:數據冗餘可能導致在特定時間窗口內出現數據不一致的情況。

5、松耦合的業務邏輯

將業務邏輯進行解耦,使其相互獨立。例如:目前線下店倉系統中專賣店、大商超、超級大店三個業態是公用一套代碼,各個業態之間設計上是松耦合的,不同業態擴展點的實現相互隔離,這樣當某個業務邏輯出現故障時,其他業務邏輯可以繼續運行,降低故障影響範圍。

二、實際場景分析

1、case 1:中間件弱依賴

i、消息隊列弱依賴

日常開發中經常會遇到分佈式事務的場景,同一個事務內涉及「RPC調用、寫DB、對外消息發送」等一系列原子操作,可能存在某一個環節請求異常的情況,爲了保證事務的最終一致性,需要採用失敗重試策略。對一個簡單的應用流程來說,拋出異常業務中斷回滾即可;但是對於複雜業務流程是不可行的,發生請求異常時,上游應用可能已經執行完畢,尤其是多個異步流程組合一個整體流程的場景,其他前置的流程可能已經執行完畢,無法回滾。

以店倉生產缺貨取消單據的場景爲例,店倉揀貨環節,若商品全部缺貨、或者用戶選擇“缺貨取消訂單”的發貨策略時,此時揀貨缺貨,會調用下游接口取消訂單。經常會出現前置操作(如RPC調用、寫DB等)完成後,調用訂單取消接口失敗或者異常的情況。調用失敗會觸發UMP報警,需人工干預進行處理。





 

圖2.1 生產缺貨取消回寫opc流程

爲了避免以上問題,保障數據的最終一致性,初期優化採用mq自產自消的方式,進行重試。





 

圖2.2 JMQ解決回調取消接口失敗

這樣一來,業務系統的穩定性與JMQ中間件的穩定性就強關聯了,自然對JMQ的穩定性有較高要求。爲了降低對JMQ的強依賴,保證業務的順利執行,通過技術手段提升用戶體驗,減輕研發值班人員壓力,最終形成了任務重試工具。

其核心思想是將分佈式事務拆分成本地事務進行處理,具體實現方式是:將任務落庫,保證業務操作表與純任務表在同一個數據庫,通過數據庫事務保證業務操作與任務持久化的強一致性。在一定程度上,將業務操作與中間件依賴解耦。

採用回調函數的機制實現調用者和底層驅動的解耦,提升了組件的靈活性,對業務侵入性小。





 

圖2.3 任務重試組件工作流程

ii、數據庫弱依賴

第二個涉及中間件弱依賴的場景是數據庫弱依賴,在日常開發中,數據庫操作異常的情況屢見不鮮,比如網絡鏈路問題、慢SQL導致的性能下降,以及引發的故障等。這些問題往往會導致交易黃金鍊路在短時間內無法正常工作,給業務帶來不小的損失。爲了應對這些情況,我們考慮引入災備機制,以確保在異常情況下仍能維持較高的交易成功率,保障訂單履約時效。

該方案的核心思路是:在DB操作出現故障的時間段內,通過其他存儲介質(如redis)臨時存儲數據。然後,通過MQ異步補償還原DB操作,從而保障數據的最終一致性。





 

圖2.4 數據災備方案

這個方案顯著提升了我們應對數據庫操作異常的處理能力,確保了黃金交易鏈路的平穩運行。通過實施災備策略,我們在確保數據最終一致性的同時,也有效減小了故障對業務的影響。

2、case 2:依賴倒置解耦業務邏輯





 

圖2.5 定義抽象接口進行解耦

i、背景

代碼層面的依賴優化案例,是基於依賴倒置的設計原則,將業務模塊進行解耦。在需求迭代的過程中,我們經常爲了圖方便,將具體類直接依賴於具體類,也就是所謂的高層模塊依賴於低層模塊。但是這樣是極其不利於擴展的,隨着新功能的不斷追加,系統的功能會越來越臃腫,核心功能也會越來越模糊,這種情況下,系統的高可用性會受到影響。

請看下面這個案例:歷史代碼中,執行發貨單取消邏輯十分複雜,不同類型、不同來源的單據在不同生產環節取消處理邏輯存在差異,這裏的doCandel方法就是高層模塊,而調用取消接口和發送取消消息是低層模塊,這是一個典型的高層模塊依賴於低層模塊的編碼形式。

ii、優化前的實現方式

以下一個代碼片段是系統中的歷史代碼負債,耦合性強,可讀性和可擴展性差。





 

圖2.6 歷史代碼

iii、優化後的實現方式

弱依賴原則強調應該儘量讓模塊之間的依賴關係變得弱化。這意味着模塊之間的相互作用應該儘量簡單,避免複雜的依賴關係。我們優化的核心思想是採用 工廠模式+模板模式 去抽象接口,實現不同環節單據取消後續處理邏輯差異。

a.首先,我們通過定義一個抽象的策略類AbstractDoCancelNodeStrategy,將取消單據後的核心流程進行拆解,最終將拆解的四個步驟定義爲四個抽象方法。





 

圖2.7 抽象策略類定義

b.然後,我們創建了4個具體實現策略類,分別用於處理不同環節取消單據的邏輯。主要是提供相同行爲的不同實現,業務上可以根據不同條件選擇進入不同的實現類。





 





 

圖2.8 不同策略實現

c.其次,創建獲取不同生產環節取消單據的策略工廠:採用啓動時加載策略的方式,在項目啓動時,把接口的實現類的實例放在Map裏,系統運行過程中,可以通過取消節點對應的key,找到這個實現類的標識,進行相應的邏輯處理。





 

圖2.9 策略工廠

d.這樣一來,高層模塊可以依賴於這個策略類,而不是具體的策略實現。這樣,當業務需求發生變更,需要對新的單據類型進行取消消息廣播,只需實現一個新的處理策略類,而無需修改高層模塊的代碼。





 

圖2.10 高層模塊代碼

這樣優化後,相當於把高層模塊和具體的RPC調用的底層模塊邏輯進行了間接解耦。並且提供了對開閉原則的完美支持,可以在不修改主流程代碼的情況下,靈活增加新算法。總之,以抽象爲基準比以細節爲基準搭建起來的架構要穩定得多,因此咱們在日常開發中,要多嘗試面相接口編程,採用先頂層設計再細節的去設計代碼結構。

三、強弱依賴治理

服務依賴是決定系統複雜度的一個重要因素,隨着業務的不斷迭代,服務依賴可能會變得越來越複雜,導致系統難以維護和擴展。在沒有明確強弱依賴的前提下,我們很難進行熔斷、降級、限流的相關操作,也不能有效的對系統進行相關優化改造、持續推進系統穩定性提升。因此,服務依賴治理變得至關重要。我們需要定期檢查我們的依賴模型是否合理,識別不合理的依賴將其合理化。具體的治理流程包括:

1、依賴標記:通過人工梳理代碼的形式,對系統核心鏈路上的所有依賴進行梳理,分析並標註依賴關係及強弱。

2、強弱依賴驗證:採用混沌工程等方式模擬鏈路故障,核心思路就是不斷給系統“找麻煩”來驗證系統能力,模擬某個依賴服務出現故障的場景,從而驗證人工標註的有效性。

3、依賴治理:依賴治理的目標體現在如下幾個方面:

篩選出那些不是真的強依賴的部分,將其轉換爲弱依賴,實現強依賴最少化。
對強依賴進行解耦合,建立核心鏈路的降級預案,並對預案持續保活。
對弱依賴做合理的異常捕獲邏輯,配置合理的超時、熔斷以及限流。針對具體的業務場景,以場景爲最小單位,編寫止損可控的兜底邏輯,並配置相應的動態切換開關,當異常發生時,可一鍵切換至兜底邏輯。
弱依賴支持平滑停用,支持突發場景下舍軍保帥。

結語

當然,除了通過以上措施去構建遵循弱依賴原則的高可用系統,還有一些高可用的架構方案:

比如,在架構設計時,我們還需要考慮到不同層級的異常監控(業務層、應用層、中間件層、基礎層),數據採集包含日誌、埋點、鏈路追蹤等,數據告警通過電話、短信、郵件、京me等方式通知到值班人員。通過建立完善的監控體系,實時收集系統運行狀態,並進行預警。這樣,當系統出現潛在故障時,可以及時發現並採取措施進行修復。

此外,對於改造量比較大的新業務上線後,可以通過ducc控制灰度切流的方式,降低軟件編碼錯誤帶來的影響。觀察沒有問題,再全量切流,保證即使程序有Bug,也可以流量切回,產生的影響也控制在較小的範圍內。這樣的系統在面對故障時,具有更強的容錯能力和抗故障能力,才能確保系統整體運行的穩定性和可用性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章