天天看點

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

作者:京東雲開發者

前言

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

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

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

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,也可以流量切回,産生的影響也控制在較小的範圍内。這樣的系統在面對故障時,具有更強的容錯能力和抗故障能力,才能確定系統整體運作的穩定性和可用性。

繼續閱讀