架構雜談《六》 超時處理模式 在服務化或者微服務架構里,傳統的整體應用拆分成多個職責單一的微服務,微服務之間通過某種網路通信協議互相通信和交互,完成特定的功能,然而由於網路通信的不穩定,在設計系統時必須考慮到對網路通信的容錯,特別是對調用超時問題的處理。 一、微服務的交互模式 1、同步調用模式 在同 ...
架構雜談《六》
超時處理模式
在服務化或者微服務架構里,傳統的整體應用拆分成多個職責單一的微服務,微服務之間通過某種網路通信協議互相通信和交互,完成特定的功能,然而由於網路通信的不穩定,在設計系統時必須考慮到對網路通信的容錯,特別是對調用超時問題的處理。
一、微服務的交互模式
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、如有不合適的地方請反饋。綜合後更改。