分佈式系統與消息的投遞¶

消息是一個很是有趣的概念,它是由來源發出一個離散的通訊單元,被髮送給一個或者一羣接受者,不管是單體服務仍是分佈式系統中都有消息的概念,只是這兩種系統中傳輸消息的通道方法或者通道不一樣;單體服務中的消息每每能夠經過 IO、進程間通訊、方法調用的方式進行通訊,而分佈式系統中的遠程調用就須要經過網絡,使用 UDP 或者 TCP 等協議進行傳輸。git

然而網絡在計算機的世界中是最不可控的,若是咱們經過網絡請求調用其餘服務的接口,可能就會因爲種種緣由沒有將消息送達至目標的服務,對於當前服務咱們並不能控制網絡的傳輸,在不少時候也很難控制網絡通訊的質量,這也就是爲何『網絡是穩定、可信賴的』分佈式系統中常見的謬論之一。安全

  • 通訊渠道的不可靠是形成構建大規模分佈式系統很是複雜而且困難的重要緣由。

網絡請求

做爲分佈式系統之間各個節點的通訊渠道,網絡實際上是很是不可靠通訊方式,若是咱們想要保證節點狀態的一致性,這種通訊方式的複雜性使得咱們在進行跨服務調用時須要處理很是多的邊界條件,在以前的文章 分佈式系統 · 分佈式事務的實現原理 中簡單介紹過,網絡通訊可能會包含,成功、失敗以及超時三種狀況。網絡

每一次網絡請求其實都是一次信息的投遞,因爲當前的節點沒法得知其餘節點信息,只能經過網絡請求的響應來得知此次信息投遞的結果。架構

成功與失敗

雖然網絡的狀況比較不穩定,可是咱們在大多數時候經過網絡傳輸一些信息時,不管是返回的結果是成功仍是失敗,其實都能獲得肯定的結果:分佈式

每一次肯定的響應都須要此次請求在一個往返以及被調用節點中正確處理,流量既不能被中間代理丟包,也不能因爲目標節點的錯誤致使沒法發出響應,只有在同時知足了這兩個條件的狀況下,咱們才能獲得肯定的響應結果。對於節點來講,此次請求返回成功仍是失敗都比較好處理,由於只要有肯定的結果,網絡請求這種通訊方式與進程間通訊或者方法調用這些更可靠的途徑在處理上都沒有太多的區別,可是在通訊的過程當中出現其餘的問題時就比較棘手了。性能

超時

在分佈式系統中,不是任何的網絡請求都可以獲得肯定的響應,若是網絡請求在往返以及被調用節點處理的過程當中出現了丟包或者節點錯誤,發出請求的節點就可能永遠也沒法獲得此次請求的響應。ui

每個節點在發出請求以後,都對此次請求如何路由以及被處理一無所知,因此節點須要設置一個合適的超時時間,若是請求沒有在規定的時間內返回,就會認爲當前請求已經超時,也就是網絡請求失敗了。線程

超時的網絡請求是致使分佈式系統難以處理的根本緣由之一,在這種問題發生時節點並不知道目標節點是否收到了當前請求,對於冪等的網絡請求還好,一旦請求可能會改變目標節點的狀態就很是棘手了,由於咱們並不能肯定上一次網絡請求是在哪一步失敗的,若是是響應返回的過程當中發生了故障,那麼若是重試一些請求就會出現問題,可能會觸發銀行的兩次轉帳,這是咱們不管如何也沒法接受的;總而言之,網絡通訊的不穩定迫使咱們處理因爲超時而出現的複雜問題,這也是在開發分佈式系統時不得不考慮的。代理

消息投遞語義

在分佈式系統中使用網絡進行通訊確實是一種不可靠的方式,消息的發送者只能知道掌控當前節點,因此沒有辦法保證傳輸渠道的可靠性,網絡超時這種常見的通訊錯誤極大地增長了分佈式系統通訊的複雜度,咱們能夠對網絡提供的基本傳輸能力進行封裝,保證數據通訊的可靠性。orm

網絡請求因爲超時的問題,消息的發送者只能經過重試的方式對消息進行重發,可是這就可能會致使消息的重複發送與處理,然而若是超時後不從新發送消息也可能致使消息的丟失,因此如何在不可靠的通訊方式中,保證消息不重不漏是很是關鍵的。

咱們通常都會認爲,消息的投遞語義有三種,分別是最多一次(At-Most Once)、最少一次(At-Least Once)以及正好一次(Exactly Once),咱們分別會介紹這三種消息投遞語義到底是如何工做的。

最多一次

最多一次其實很是容易保證的,UDP 這種傳輸層的協議其實保證的就是最多一次消息投遞,消息的發送者只會嘗試發送該消息一次,並不會關心該消息是否獲得了遠程節點的響應。

不管該請求是否發送給了接受者,發送者都不會從新發送這條消息;這其實就是最最基本的消息投遞語義,然而消息可能因爲網絡或者節點的故障出現丟失。

最少一次

爲了解決最多一次時的消息丟失問題,消息的發送者須要在網絡出現超時從新發送相同的消息,也就是引入超時重試的機制,在發送者發出消息會監聽消息的響應,若是超過了必定時間也沒有獲得響應就會從新發送該消息,直到獲得肯定的響應結果。

對於最少一次的投遞語義,咱們不只須要引入超時重試機制,還須要關心每一次請求的響應,只有這樣才能確保消息不會丟失,可是卻可能會形成消息的重複,這就是最少一次在解決消息丟失後引入的新問題。

正好一次

雖然最少一次解決了最多一次的消息丟失問題,可是因爲重試卻帶來了另外一個問題 - 消息重複,也就是接受者可能會屢次收到同一條消息;從理論上來講,在分佈式系統中想要解決消息重複的問題是不可能的,不少消息服務提供了正好一次的 QoS 實際上是在接收端進行了去重。

消息去重須要生產者生產消息時加入去重的 key,消費者能夠經過惟一的 key 來判斷當前消息是不是重複消息,從消息發送者的角度來看,實現正好一次的投遞是不可能的,可是從總體來看,咱們能夠經過惟一 key 或者重入冪等的方式對消息進行『去重』。

消息的重複是不可能避免的,除非咱們容許消息的丟失,然而相比於丟失消息,重複發送消息實際上是一種更能讓人接受的處理方式,由於一旦消息丟失就沒法找回,可是消息重複卻能夠經過其餘方法來避免反作用。

投遞順序

因爲一些網絡的問題,消息在投遞時可能會出現順序不一致性的狀況,在網絡條件很是不穩定時,咱們就可能會遇到接收方處理消息的順序和生產者投遞的不一致;想要知足絕對的順序投遞,其實在生產者和消費者的單線程運行時是相對比較好解決的,可是在市面上比較主流的消息隊列中,都不會對消息的順序進行保證,在這種大前提下,消費者就須要對順序不一致的消息進行處理,常見的兩種方式就是使用序列號或者狀態機。

序列號

使用序列號保證投遞順序的方式其實與 TCP 協議中使用的 SEQ 很是類似,由於網絡並不能保證全部數據包傳輸的順序而且每一個棧幀的傳輸大小有限,因此 TCP 協議在發送數據包時加入 SEQ,接受方能夠經過 SEQ 將多個數據包拼接起來並交由上層協議進行處理。

在投遞消息時加入序列號其實與 TCP 中的序列號很是相似,咱們須要在數據以外增長消息的序列號,對於消費者就能夠根據每一條消息附帶的序列號選擇如何處理順序不一致的消息,對於不一樣的業務來講,常見的處理方式就是用阻塞的方式保證序列號的遞增或者忽略部分『過時』的消息。

狀態機

使用序列號確實可以保證消息狀態的一致,可是卻須要在消息投遞時額外增長字段,這樣消費者才能在投遞出現問題時進行處理,除了這種方式以外,咱們也能夠經過狀態機的方式保證數據的一致性,每個資源都有相應的狀態遷移事件,這些事件其實就是一個個消息(或操做),它們可以修改資源的狀態:

在狀態機中咱們能夠規定,狀態的遷移方向,全部資源的狀態只能按照咱們規定好的線路進行改變,在這時只要對生產者投遞的消息狀態作必定的約束,例如:資源一旦 completed 就不會變成 failed,由於這兩個狀態都是業務邏輯中定義的最終狀態,因此處於最終狀態的資源都不會繼續接受其餘的消息。

假設咱們有以下的兩條消息 active 和 complete,它們分別會改變當前資源的狀態,若是一個處於 pending 狀態的資源先收到了 active 再收到 complete,那麼狀態就會從 pending 遷移到 active 再到 completed;可是若是資源先收到 complete 後收到 active,那麼當前資源的狀態會直接從 pending 跳躍到 completed,對於另外一條消息就會直接忽略;從整體來看,雖然消息投遞的順序是亂序的,可是資源最終仍是經過狀態機達到了咱們想要的正確狀態,不會出現不一致的問題。

協議

消息投遞其實有很是多相關的應用,最多見的組件就是消息隊列了,做爲一種在各個 Web 項目中經常使用的組件,它提供了不少能力,包括消息的持久存儲、不一樣的投遞語義以及複雜的路由規則等等,可以顯著地增長系統的可用性、起到比較比明顯的削峯效果。

在這裏將介紹幾種比較常見的消息隊列協議,咱們將簡單說明各個協議的做用以及它們的實現原理和關鍵特性,也會簡單說起一些遵循這些協議實現的消息隊列中間件。

AMQP 協議

AMQP 協議的全稱是 Advanced Message Queuing Protocol,它是一個用於面向消息中間件的開放標準,協議中定義了隊列、路由、可用性以及安全性等方面的內容。

該協議目前可以爲通用的消息隊列架構提供一系列的標準,將發佈訂閱、隊列、事務以及流數據等功能抽象成了用於解決消息投遞以及相關問題的標準,StormMQ、RabbitMQ 都是 AMQP 協議的一個實現。

在全部實現 AMQP 協議的消息中間中,RabbitMQ 實際上是最出名的一個實現,在分佈式系統中,它常常用於存儲和轉發消息,當生產者短期內建立了大量的消息,就會經過消息中間件對消息轉儲,消費者會按照當前的資源對消息進行消費。

RabbitMQ 在消息投遞的過程當中保證存儲在 RabbitMQ 中的所有消息不會丟失、推送者和訂閱者須要經過信號的方式確認消息的投遞,它支持最多一次和最少一次的投遞語義,當咱們選擇最少一次時,須要冪等或者重入機制保證消息重複不會出現問題。

MQTT 協議

另外一個用於處理髮布訂閱功能的常見協議就是 MQTT 了,它創建在 TCP/IP 協議之上,可以在硬件性能底下或者網絡狀態糟糕的狀況下完成發佈與訂閱的功能;與 AMQP 不一樣,MQTT 協議支持三種不一樣的服務質量級別(QoS),也就是投遞語義,最多一次、最少一次和正好一次。

從理論上來看,在分佈式系統中實現正好一次的投遞語義是不可能的,這裏實現的正好一次實際上是協議層作了重試和去重機制,消費者在處理 MQTT 消息時就不須要關係消息是否重複這種問題了。

總結

在分佈式系統中想要保證消息的送達確實是一件比較複雜的事情,通訊方式的不肯定使得咱們須要處理不少問題,咱們既須要在網絡錯誤或者超時時進行重試,還須要對一些請求支持重入和冪等,保證不會出現一致性的錯誤;這其實都是由於在分佈式系統中,正好一次的消息投遞語義是不存在的,消息要麼可能會丟失,要麼就可能會重複。

相關文章
相關標籤/搜索