架構雜談《六》

AjuPrince發表於2019-07-22

架構雜談《六》

超時處理模式

  在服務化或者微服務架構裡,傳統的整體應用拆分成多個職責單一的微服務,微服務之間通過某種網路通訊協議互相通訊和互動,完成特定的功能,然而由於網路通訊的不穩定,在設計系統時必須考慮到對網路通訊的容錯,特別是對呼叫超時問題的處理。

一、微服務的互動模式

  1、同步呼叫模式

    在同步呼叫模式中,服務A呼叫服務B,服務A的執行緒阻塞等待服務B的處理結果,如果服務B一直不返回處理結果,則服務A一直處於等待狀態中一直到超時為止。

(同步呼叫模式圖)

  2、介面非同步呼叫模式

    在介面非同步呼叫模式中,服務A請求服務B處理某項任務,服務B處理完後即刻返回給服務A處理結果,如果處理成功,則服務A繼續幹其他任務,而服務B非同步處理這項任務,直到服務B處理完這項任務後,才反向通知服務A任務已完成,服務A再做後續的工作。

 

(介面非同步呼叫模式)

    介面非同步呼叫模式適用於非核心鏈路上負載較高的處理環節,這個環節經常耗時較長並且對時效性要求不高。

  3、訊息佇列非同步處理模式

    訊息佇列非同步處理模式利用訊息佇列作為通訊機制,在這種互動模式中,通常服務A只需將某種事件傳遞給服務B,而不需要等待服務B返回結果。在這種情況下,服務A與服務B可以充分解耦,並且在大規模、高併發的微服務系統中,訊息佇列對流量具有消峰的功能。

 

(訊息佇列非同步處理模式)

    訊息佇列非同步處理模式與介面非同步呼叫模式類似,多應用於非核心鏈路上負載較高的處理環節中,並且服務的上游不關心下游的處理結果,下游也不需要向上遊返回處理結果。

  以上這三種互動模式普遍應用於服務化和微服務架構中,它們之間沒有絕對的好壞,只需要在特定場景下做出合適的選擇。

二、同步於非同步的抉擇

  1、儘量使用非同步來替換同步操作

  2、能用同步解決的問題,不要引入非同步。

  第一條原則是從業務功能的角度出發的,業就是從與使用者或者使用方的互動模式出發的。如果業務邏輯允許,使用者對產品的互動形態沒有異議,則我們可以將一些耗時較長的、使用者對響應沒有特別要求的操作非同步化,以此來減少核心鏈路的層級,釋放系統的壓力。

  第二條原則是從技術和架構的角度出發的,這條原則應用的前提是同步能夠解決問題,這隱含了一個含義:如果效能不是問題,或者所處理的操作是短小的輕量級處理邏輯,那麼同步呼叫方式是最理想不過的,因為這樣不需要引入非同步化的複雜處理流程。

三、互動模式下超時問題的解決方案

  1、同步呼叫模式下的解決方案

    在同步模式下,對外的介面會提供服務契約,契約定義了服務的處理結果會通過返回值返回給對方,對返回的狀態定義分為以下兩種:

      A)成功和失敗(兩狀態的同步介面)

      B)成功、失敗和處理中(三狀態的同步介面)

    1)兩狀態的同步介面

      服務契約中只規定了兩種互斥的狀態:成功和超時,服務處理結果必須是成功的或者失敗的。在這種情況下可能發生兩種同步呼叫超時。

      第一種同步呼叫超時發生在使用方呼叫此同步介面的過程中 

      針對這個問題,我們需要服務的使用方使用前面架構雜談中提到的查詢模式,非同步查詢處理結果,在獲得明確的處理結果後,得知處理結果是成功還是失敗,然後做相應的處理。如果處理結果為成功,那麼使用方可以繼續下面的操作;如果結果為失敗,那麼呼叫方可以發起重試,請求再次進行處理。然而,這裡有一個問題,如果查詢模式的返回狀態是未知請求,那麼在這種情況下使用方超時,服務 1 實際上沒有接收到或者還沒有接收到一開始的處理請求,服務使用方需要使用同一個請求 ID 進行重試,服務 1 也必須實現請求處理的幕等性。

    第二種同步呼叫超時發生在內部服務1呼叫服務2的過程中

 

    

   在使用方呼叫服務 1,且服務 1 接收到請求後,同步呼叫服務 2,由於通訊出現了問題, 所以服務 1得到超時的結果。這時服務 1 應該怎麼做呢?是重試、取消還是快速失敗?
   我們看到上圖的左面服務 1 對外介面的契約中包含兩個返回狀態 : 成功或者失敗,也就是對於使用方來講,不允許有中間的處理中的狀態,對於這種服務內部超時的場景,必須使用快速失敗的策略 : 針對這個超時錯誤,服務快速返回失敗,同時在內部呼叫服務 2 的衝正介面,服務 2 的衝正介面可以判斷之前是否接收到請求,如果接收到請求井做了處理,則應該做反向的回攘操作。如果服務 2 之前沒有接收到處理請求,則忽略衝正請求,以此來實現服務的幕等性。

   2)三狀態的同步介面    

    對於上面的第 2 種定義,服務契約中規定了三種處理結果,狀態值為:成功、失敗和處理中,對於超時等系統錯誤的請求,其實可以認為是處理中狀態的一個特例,在這種場景的應用裡,超時被視為內部暫時的問題,隨後可能被修復,因此,可能在一定的時間視窗內告知使用方在處理中,隨後修復問題井補償執行,達到最大化請求處理成功的目標,不至於讓使用方重試,以提升使用者體驗 。
    服務處理結果可能是成功或者失敗,也可能是處理中,在這種情況下可能發生兩種同步呼叫超時。

    第1種同步呼叫超時發生在使用方呼叫此同步介面過程中,如下圖所示:

    這種場景和兩狀態同步呼叫的介面超時場景類似,使用方呼叫服務 1 的介面,由於網路等原因獲得超時的結果,這時使用方應該將超時看作處理中的一個特例,使用服務 1 的查詢介面後續補齊上一個請求的處理狀態,可參照兩狀態同步呼叫的介面超時場景的方案。
    第 2 種同步呼叫超時發生在內部服務 l 呼叫服務 2 的過程中,如下圖所示。

     在使用方呼叫服務 1, 且服務 1 接收到請求後,同步呼叫服務 2,由於通訊出現了問題,所以服務 l 得到超時的結果,這時服務 1 又應該怎麼做呢?
     這和兩狀態同步呼叫 的內部超時場景不一樣,兩狀態設計由於與使用方約定了契約,不是成功就是失敗,所以必須在同步呼叫時給予一個明確的結果,然而,在三狀態同步呼叫的內部超時場景下,可以返回給使用方一箇中間狀態,也就是處理中的結果,變相地把同步介面變成非同步介面 ,達到最終一致的效果。
     在這種場景下,我們更傾向於給使用者更好的體驗,盡最大努力成功處理使用者發來的請求 。因此,針對在服務 1 呼叫服務 2 時超時,我們會返回給使用者處理中的狀態,隨後系統盡最大努力補償執行出錯的部分,服務 1 需要通過服務 2 的查詢介面得到最新的請求處理狀態,如果服務 2 沒有明確回覆, 則可以嘗試重新傳送請求,當然,這裡需要服務 2 也實現了操作的幕等性 。

  2. 非同步呼叫模式下的解決方案  

    在非同步呼叫模式下,對外的介面也會提供服務契約,契約定義了服務的處理結果會通過返回值返回給使用方,返回的狀態通常為兩個:處理和未處理。和三狀態同步呼叫介面不同的是非同步呼叫模式還有非同步處理返回結果的通知,狀態包括處理成功和處理失敗。
    不同階段的網路通訊產生的超時和處理方案如下。

    1)非同步呼叫介面超時

  

      非同步呼叫介面超時發生在使用方呼叫服務 1 的受理介面時,同兩狀態同步呼叫介面超時及三狀態同步呼叫介面超時的場景是一樣的,需要通過查詢來補齊狀態,並根據狀態來判斷後續的操作,具體的解決方案參考兩狀態同步呼叫介面超時和三狀態同步呼叫介面超時的解決方案。

    2)非同步呼叫內部超時

 

 

       非同步呼叫內部超時發生在服務 1 受理了使用方的請求後 ,服務 1 在處理請求時,在呼叫服務 2 的過程中超時,這和三狀態同步呼叫內部超時的場景相似,由於非同步呼叫模式使用的是受理模式,所以一旦受理,我們便應該盡最大努力將使用者請求的操作處理成功,因此,在服務 1 呼叫服務 2 超時的場景下,服務 1需要根據服務 2 的查詢介面獲得最新狀態,根據狀態補償後續的操作,這和三狀態同步呼叫內部超時的解決方案一致,不同的是此場景下一旦處理成功,則需要非同步回撥通知使用方,而在三狀態同步呼叫內部超時的場景下,只需要等待使用方查詢,不需要通知,也無法實現通知。

    3)非同步呼叫回撥超時

 

       回撥超時的問題在生產中經常出現,通常發生於這樣的場景下:服務 1 受理後成功地呼叫了依賴服務 2,獲得了明確的處理結果,但是在將處理結果通知使用方時出現超時。由於使用方有可能是公司內部的也可能是外部的 ,網路環境複雜多變,發生超時的概率很大,因此,大多數公司都會開發一個通知子系統,用來專門處理回撥通知。

      由於服務 1通過回撥通知使用方,所以服務 1需要保證通知一定可送達,如果遇到超時,則服務 1 負責重新繼續補償,通常會設計一個通知時間按一定間隔遞增的策略,例如 :指數回退,直到通知成功為止,通知是否成功以對方的回寫狀態為準。

  3、訊息佇列非同步處理模式的解決方案  

    訊息佇列非同步處理模式多用於疏鬆禍合的專案,這些專案通常是在主流程中無法處理耗時的任務,恰好耗時的任務又不是核心流程的一部分,比如 : 電商平臺的物流、配送等。

    這類互動使用訊息佇列進行解耦,電商交易系統成功處理交易後,需要傳送訊息到訊息佇列伺服器,後續的流程由物流平臺處理,也不需要將處理結果反饋給交易平臺。

    使用訊息佇列解耦後,處理流程被分為兩個階段:生產者投遞和消費者處理,在不同的階段會產生不同的超時問題,解決方案如下。

    1)訊息佇列的生產者超時

    2)訊息佇列的消費者超時

    對於訊息佇列的處理機與訊息佇列之間的超時或者網路問題,通常可以通過訊息佇列提供的機制來解決。
    一般訊息佇列會提供如下兩種方式來消費訊息。
      1)、自動增長消費的偏移量:在一個消費者從訊息伺服器中取走訊息後,訊息佇列的訊息偏移量自動增加,即訊息一旦被從訊息佇列中取走,則不再存在於伺服器中,假如訊息處理機對此訊息處理失敗,則也無法從訊息伺服器中找回。
      2)、手工提交消費的偏移量 :在一個消費者從訊息伺服器中取走訊息後,處理機先把訊息持久到本地資料庫中,然後告訴訊息伺服器己經消費訊息,訊息伺服器才會移除訊息,如果在沒有告訴訊息伺服器己經消費訊息之前,持久失敗或者發生了其他問題,則訊息仍然存在於訊息伺服器中,訊息處理器下次還可以繼續消費訊息。
    如果允許丟訊息,則我們使用第1種處理方式,這種方式的併發量高、效能好,但是如果我們對訊息處理的準確性要求較高,則必須採用第 2 種方式。

 四、超時補償的原則

  1)服務1呼叫服務2,如果服務2響應服務1並且告訴服務1訊息己接收,那麼服務1的任務就結束了;如果服務2處理失敗,那麼服務2應該負責重試或者補償。在這種情況下,服務2通常接收訊息後先持久再告訴服務1接收成功,隨後服務2才開始處理持久的訊息,避免服務程式被殺掉而導致訊息丟失。

  2)服務1呼叫服務2,如果服務2沒有給出明確的接收響應,例如網路超時,那麼服務1應該持續進行重試,直到服務2明確表示己經接收訊息。在這種情況下容易出現重複的訊息,因此在服務2中通常要保證濾重或者幕等性。

說明:

  1、參考書籍:《分散式服務架構:原理、設計與實戰》

  2、如有不合適的地方請反饋。綜合後更改。

相關文章