彈力設計之異步通訊設計

前面所說的隔離設計通常都需要對系統做解耦設計,而把一個單體系統解耦,不單單是把業務功能拆分出來,正如上面所說,拆分完後還會面對很多的問題。其中一個重要的問題就是這些系統間的通訊。

通訊一般來說分同步和異步兩種。同步通訊就像打電話,需要實時響應,而異步通訊就像發郵件,不需要馬上回復。各有千秋,我們很難說誰比誰好。但是在面對超高吐吞量的場景下,異步處理就比同步處理有比較大的優勢了,這就好像一個人不可能同時接打很多電話,但是他可以同時接收很多的電子郵件一樣。

同步調用雖然讓系統間只耦合於接口,而且實時性也會比異步調用要高,但是我們也需要知道同步調用會帶來如下幾個問題。

1、同步調用需要被調用方的吞吐不低於調用方的吞吐。否則會導致被調用方因爲性能不足而拖死調用方。換句話說,整個同步調用鏈的性能會由最慢的那個服務所決定。
2、同步調用會導致調用方一直在等待被調用方完成,如果一層接一層地同步調用下去,所有的參與方會有相同的等待時間。這會非常消耗調用方的資源。因爲調用方需要保存現場(Context)等待遠端返回,所以對於併發比較高的場景來說,這樣的等待可能會極度消耗資源。
3、同步調用只能是一對一的,很難做到一對多。
4、同步調用最不好的是,如果被調用方有問題,那麼其調用方就會跟着出問題,於是會出現多米諾骨牌效應,故障一下就蔓延開來。

所以,異步通訊相對於同步通訊來說,除了可以增加系統的吞吐量之外,最大的一個好處是其可以讓服務間的解耦更爲徹底,系統的調用方和被調用方可以按照自己的速率而不是步調一致,從而可以更好地保護系統,讓系統更有彈力。

異步通訊的三種方式

請求響應式

在這種情況下,發送方(sender)會直接請求接收方(receiver),被請求方接收到請求後,直接返回——收到請求,正在處理。

對於返回結果,有兩種方法,一種是發送方時不時地去輪詢一下,問一下乾沒幹完。另一種方式是發送方註冊一個回調方法,也就是接收方處理完後回調請求方。這種架構模型在以前的網上支付中比較常見,頁面先從商家跳轉到支付寶或銀行,商家會把回調的 URL 傳給支付頁面,支付完後,再跳轉回商家的 URL。

很明顯,這種情況下還是有一定耦合的。是發送方依賴於接收方,並且要把自己的回調發送給接收方,處理完後回調。

通過訂閱的方式

這種情況下,接收方(receiver)會來訂閱發送方(sender)的消息,發送方會把相關的消息或數據放到接收方所訂閱的隊列中,而接收方會從隊列中獲取數據。

這種方式下,發送方並不關心訂閱方的處理結果,它只是告訴訂閱方有事要幹,收完消息後給個 ACK 就好了,你幹成啥樣我不關心。這個方式常用於像 MVC(Model-View-Control)這樣的設計模式下,如下圖所示。



這就好像下訂單的時候,一旦用戶支付完成了,就需要把這個事件通知給訂單處理以及物流,訂單處理變更狀態,物流服務需要從倉庫服務分配相應的庫存並準備配送,後續這些處理的結果無需告訴支付服務。

爲什麼要做成這樣?好了,重點來了!前面那種請求響應的方式就像函數調用一樣,這種方式有數據有狀態的往來(也就是說需要有請求數據、返回數據,服務裏面還可能需要保存調用的狀態),所以服務是有狀態的。如果我們把服務的狀態給去掉(通過第三方的狀態服務來保證),那麼服務間的依賴就只有事件了。

你知道,分佈式系統的服務設計是需要向無狀態服務(Stateless)努力的,這其中有太多的好處,無狀態意味着你可以非常方便地運維。所以,事件通訊成爲了異步通訊中最重要的一個設計模式。

就上面支付的那個例子,商家這邊只需要訂閱一個支付完成的事件,這個事件帶一個訂單號,而不需要讓支付方知道自己的回調 URL,這樣的異步是不是更乾淨一些?

但是,在這種方式下,接收方需要向發送方訂閱事件,所以是接收方依賴於發送方。這種方式還是有一定的耦合。

通過 Broker 的方式

所謂 Broker,就是一箇中間人,發送方(sender)和接收方(receiver)都互相看不到對方,它們看得到的是一個 Broker,發送方向 Broker 發送消息,接收方向 Broker 訂閱消息。

這是完全的解耦。所有的服務都不需要相互依賴,而是依賴於一箇中間件 Broker。這個 Broker 是一個像數據總線一樣的東西,所有的服務要接收數據和發送數據都發到這個總線上,這個總線就像協議一樣,讓服務間的通訊變得標準和可控。

在 Broker 這種模式下,發送方的服務和接收方的服務最大程度地解耦。但是所有人都依賴於一個總線,所以這個總線就需要有如下的特性:

必須是高可用的,因爲它成了整個系統的關鍵;
必須是高性能而且是可以水平擴展的;
必須是可以持久化不丟數據的。

要做到這三條還是比較難的。當然,好在現在開源軟件或雲平臺上 Broker 的軟件是非常成熟的,所以節省了我們很多的精力。

事件驅動設計

上述的第二種和第三種方式就是比較著名的事件驅動架構(EDA – Event Driven Architecture)。正如前面所說,事件驅動最好是使用 Broker 方式,服務間通過交換消息來完成交流和整個流程的驅動。

每個服務都是“自包含”的。所謂“自包含”也就是沒有和別人產生依賴。而要把整個流程給串聯起來,我們需要一系列的“消息通道(Channel)”。各個服務做完自己的事後,發出相應的事件,而又有一些服務在訂閱着某些事件來聯動。

事件驅動方式的好處至少有五個。

1、服務間的依賴沒有了,服務間是平等的,每個服務都是高度可重用並可被替換的。
2、服務的開發、測試、運維,以及故障處理都是高度隔離的。
3、服務間通過事件關聯,所以服務間是不會相互 block 的。
4、在服務間增加一些 Adapter(如日誌、認證、版本、限流、降級、熔斷等)相當容易。
5、服務間的吞吐也被解開了,各個服務可以按照自己的處理速度處理。

我們知道任何設計都有好有不好的方式。事件驅動的架構也會有一些不好的地方。

1、業務流程不再那麼明顯和好管理。整個架構變得比較複雜。解決這個問題需要有一些可視化的工具來呈現整體業務流程。
2、事件可能會亂序。這會帶來非常 Bug 的事。解決這個問題需要很好地管理一個狀態機的控制。
3、事務處理變得複雜。需要使用兩階段提交來做強一致性,或是退縮到最終一致性。

異步通訊的設計重點

首先,我們需要知道,爲什麼要異步通訊。

1、異步通訊最重要的是解耦服務間的依賴。最佳解耦的方式是通過 Broker 的機制。
2、解耦的目的是讓各個服務的隔離性更好,這樣不會出現“一倒倒一片”的故障。
3、異步通訊的架構可以獲得更大的吞吐量,而且各個服務間的性能不受干擾相對獨立。
4、利用 Broker 或隊列的方式還可以達到把抖動的吞吐量變成均勻的吞吐量,這就是所謂的“削峯”,這對後端系統是個不錯的保護。
5、服務相對獨立,在部署、擴容和運維上都可以做到獨立不受其他服務的干擾。

但我們需要知道這樣的方式帶來的問題,所以在設計成異步通信的時候需要注意如下事宜。

1、用於異步通訊的中間件 Broker 成爲了關鍵,需要設計成高可用不丟消息的。另外,因爲是分佈式的,所以可能很難保證消息的順序,因此你的設計最好不依賴於消息的順序。
2、異步通訊會導致業務處理流程不那麼直觀,因爲像接力一樣,所以在 Broker 上需要有相關的服務消息跟蹤機制,否則出現問題後不容易調試。
3、因爲服務間只通過消息交互,所以業務狀態最好由一個總控方來管理,這個總控方維護一個業務流程的狀態變遷邏輯,以便系統發生故障後知道業務處理到了哪一步,從而可以在故障清除後繼續處理。
這樣的設計常見於銀行的對賬程序,銀行系統會有大量的外部系統通訊,比如跨行的交易、跨企業的交易,等等。所以,爲了保證整體數據的一致性,或是避免漏處理及處理錯的交易,需要有對賬系統,這其實就是那個總控,這也是爲什麼銀行有的交易是 T+1(隔天結算),就是因爲要對個賬,確保數據是對的。
4、消息傳遞中,可能有的業務邏輯會有像 TCP 協議那樣的 send 和 ACK 機制。比如:A 服務發出一個消息之後,開始等待處理方的 ACK,如果等不到的話,就需要做重傳。此時,需要處理方有冪等的處理,即同一件消息無論收到多少次都只處理一次。

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