為什麼正好一次(Exactly-Once)傳遞是不可能的?

banq發表於2016-11-27
這是分佈是系統領域很重要的一篇文章,主要論述在訊息傳遞中"最多一次"、"最少一次"和"正好一次"三者中正好一次傳遞是不可能的,也就是透過網路兩個伺服器之間的呼叫恰好透過一次就完成正確通訊是不可能的。至少一次意思是一個訊息至少傳遞一次以上,當然會造成訊息內容重複冗餘,但是可靠性提高了;而至多一次是伺服器的訊息最多傳遞一次,如果再傳遞一次,就會造成負面影響。正好一次是透過訊息接收方傳送確認收到的方式試圖保障每次訊息傳遞都能可靠傳遞完成,這是不可能的,因為這個傳送、收到和確認的過程中一旦出現問題,就無法保證傳遞完成。

從另外一個角度看,這是針對網路故障的不同策略,至少一次是系統會在故障時重試請求,直至呼叫成功;而至多一次不會重試。

至多一次實際中案例是信用卡扣款,最多隻能發出一次扣款訊息,如果不成功,可以讓客戶重新發出扣款請求,但是我們不能在分散式系統內部作出扣款一次以上的動作,會帶來負面影響,客戶鈔票多扣了。

原文大意翻譯如下:

在分散式系統環境中, 你不能僅靠一次精確exactly-once的訊息傳送 。 無論是Web瀏覽器和伺服器之間, 這是分佈的。 伺服器和資料庫也屬於分佈的。 伺服器和訊息佇列也是分佈的。在任何這些情況下,您不能依靠exactly-once傳遞語義。

正如我在過去的描述 , 分散式系統都是關於取捨平衡。基本上存在三種型別的傳遞語義:最多一次at-most-once,至少一次at-least-once和正好一次exactly-once。 在三個中,前兩個是可行的並且被廣泛使用。

你可能會極端說至少一次傳遞也是不可能的,因為,技術上來說,網路分割槽不遵循嚴格的時間界定。 如果你和伺服器之間連線無限期中斷,你就不能傳遞任何東西,無論你一次或N多次傳遞都不行。請注意,我們考慮至少一次傳遞時,是假定網路分割槽在時間上是有界,類似這種連線無限期中斷有另外解決辦法,比如致電ISP。

為什麼正好一次傳送不可能?答案就在“拜占庭將軍問題”。(拜占庭時代兩位相隔很遠的將軍希望聯合一起打仗,結果是他們永遠不可能約在一起打仗,問題出在通訊線路上等各種環節,但是在現代戰爭中,這是可能的,因為兩隻部隊在出發之前會對一下各自手錶,也就是同步一下時間,約定從對錶時刻以後的未來某個時間點同時進攻,但是手錶時間裝置在牛頓以後才變得精確,羅馬拜占庭時代不具備這個條件)。比如:在我寄給你的信中,我請你一旦收到就打電話給我。你永遠不會打電話給我。因為要麼你不在意我的信件,沒有看我的信件,要麼在郵寄過程中我的信件丟失了,這是溝通的成本問題。我可以傳送一封信,希望你收到它,或者我可以傳送10封信,並假設你會在收到其中至少一個。但傳送10封信並不真正提供任何額外的保證。

在分散式系統中,我們嘗試透過等待接收到訊息的確認來保證訊息準確傳遞到,但是各種型別的事情都可能出錯。 訊息被刪除了? Ack確認資訊被丟棄了嗎? 接收者是否崩潰? 他們只是變慢嗎? 還是網路變慢?這些都無法確定,“拜占庭將軍問題”不是設計的複雜性,他們是不可能的結果 。

人們經常扭曲“傳遞”的意思,以使它們的系統適合正好一次的語義,或者在其他情況下,該術語被過載以意味著完全不同的東西。 狀態機複製就是一個很好的例子。原子廣播協議確保訊息可靠地按順序傳送。事實是不能可靠傳遞訊息,在網路分割槽中,如果沒有高度協調就會遭遇崩潰衝突。這種協調當然是有代價的(延遲和可用性),同時還依靠至少一次傳遞的語義。Zab是ZooKeeper原子廣播協議 ,實施冪等操作:

狀態更改是冪等的,並且多次應用相同的狀態更改不會導致不一致,只要應用程式順序與傳遞順序一致即可。 因此,保證至少一次語義是足夠的,並且簡化了實現。

“簡化實現”是作者微妙嘗試。狀態機複製就是這樣,複製狀態。如果我們的訊息有副作用,所有這些都會出問題了。

看看最多一次交付:當訊息傳遞時,它在接受者處理它之前立即被確認。傳送者終會接收到確認ack。但是,如果接收者在其處理這個訊息之前或期間崩潰,則該資料將永遠丟失。客戶交易丟了? 只能對客戶說,很抱歉,似乎沒有收到您的訂單。這是最多一次交付的世界觀。說實話,根據具體情況,實現最多一次的語義比這更復雜。 如果有多個工作程式處理任務或工作佇列被複制,代理必須保證強一致性(或CAP在CAP定理中的CP),以確保一旦被確認的任務不會被傳遞給任何其他工作程式。 Apache Kafka使用ZooKeeper來處理這種協調。

另一方面,我們可以在訊息被處理後確認訊息。如果這個處理在接受訊息之後但在確認之前崩潰(或者確認沒有被遞送),則傳送者將重新傳送。 您好,請至少一次傳遞吧。 此外,如果你想以郵件傳遞到多個站點,你需要一個原子廣播,但吞吐量帶來巨大的負擔。快速或一致性,你就慢慢權衡吧,歡迎來到分散式系統的世界。

現在每個主要訊息佇列提供至少一次傳送作為保證。如果它宣稱正好僅一次傳遞,那他們就在撒謊,希望你不會購買或他們自己都不明白的分散式系統。 不管怎樣,這不是一個好的指標。

RabbitMQ的嘗試提供擔保沿著下面這些路徑:

當使用確認時,從通道或連線失敗中恢復的生產者應該重傳任何沒有被接收確認的訊息。 這裡存在訊息重複的可能性,因為代理可能已經傳送了一個確認,但是從未到達生產者(可能由於網路故障等)。 因此,消費者應用程式將需要執行重複資料消除或以冪等方式處理傳入的訊息。

我們在實踐中實現正好一次性傳遞的方式是模擬它。 訊息本身應該是冪等的,這意味著它們可以被多次應用而沒有不利影響,或者我們透過重複資料刪除來消除對冪等性的需求。理想情況下,我們的訊息不需要嚴格的排序,而是交換就可以。無論你採取什麼路線都有設計意義和權衡,但這是我們必須生活的現實。

重新考慮將操作作為冪等行為說說很容易,但是做起來很難,大多數需要改變我們對狀態的思考方式。 這最好透過重溫複製狀態機來描述。相對於釋出操作到各個節點並應用執行它們,如果我們只是分佈狀態改變state changes它們自己本身呢?而不是將狀態釋出到各個節點,,這樣我們只是及時報告各個點的發生事實 。 這是Zab工作原理。

想象一下,我們想告訴朋友來接我們。我們向他傳送一系列帶有轉彎路線的簡訊,但其中一則訊息會傳送兩次!我們的朋友會不太高興,因為他發現自己在城市裡兜圈了。相反,我們只是告訴他,我們在哪裡 ,讓他看著辦吧。如果訊息被傳遞多次,這沒關係。

這個意義(冪等操作)是非常深遠,因為我們仍然在關注訊息的順序,這就是為什麼像交換commutative和收斂convergent複製的資料型別的解決方案正在變得越來越受歡迎。 也就是說,我們可以透過外在手段如sequencing傳統順序、向量時鐘或其他部分排序機制等方式解決這個問題。它通常是因果順序,雖然不是立即實時,但是反正以後一直都是這樣。那些否定因果順序的人實際不明白在分散式系統沒有“現在now”這個精確概念或這個時間點 ,(需要各個伺服器精確對錶,也就是校對時鐘)。

重申一下,沒有正好一次傳遞這樣的東西。我們必須在兩種罪惡之中進行選擇,在大多數情況下選擇至少是一次傳遞。 可以透過確保冪等性或以其他方式消除操作的副作用來模擬一次性語義。同樣,重要的是瞭解設計分散式系統時所涉及的權衡。非同步比比皆是,這意味著你不能指望同步機制來保證行為。 針對這種非同步自然屬性為故障恢復和彈性進行設計。


You Cannot Have Exactly-Once Delivery – Brave New

[該貼被banq於2016-11-27 19:07修改過]

相關文章