作者:周凱波

本文翻譯自 streaml.io 網站上的一篇博文:「Exactly once is NOT exactly the same」 ,分析了流計算

系統中常說的『Exactly Once』特性,主要觀點是:『精確一次』並不保證是完全一樣
。主要內容如下:
  1. 背景
  • 1.1. 最多一次(At-most-once)
  • 1.2. 至少一次(At-least-once)
  • 1.3. 精確一次(Exactly-once)
  1. 『精確一次』是真正的『精確一次』嗎?
  2. 分散式快照與至少一次事件傳遞和重複數據刪除的比較
  3. 結論
  4. 參考

目前市面上使用較多的流計算系統有 Apache Storm,Apache Flink, Heron, Apache Kafka (Kafka Streams) 和 Apache Spark (Spark Streaming)。關於流計算系統有個被廣泛討論的特性是『exactly-once』語義,很多系統宣稱已經支持了這一特性。但是,到底什麼是『exactly-once』,怎麼樣才算是實現了『exactly-once』,人們存在很多誤解和歧義。接下來我們做下分析。

一、背景

流處理(有時稱為事件處理)可以簡單地描述為是對無界數據或事件的連續處理。流或事件處理應用程序可以或多或少地被描述為有向圖,並且通常被描述為有向無環圖(DAG)。在這樣的圖中,每個邊表示數據或事件流,每個頂點表示運算符,會使用程序中定義的邏輯處理來自相鄰邊的數據或事件。有兩種特殊類型的頂點,通常稱為 sources 和 sinks。sources讀取外部數據/事件到應用程序中,而 sinks 通常會收集應用程序生成的結果。下圖是流式應用程序的示例。

A typical stream processing topology流處理引擎通常允許用戶指定可靠性模式或處理語義,以指示它將為整個應用程序中的數據處理提供哪些保證。這些保證是有意義的,因為你始終會遇到由於網路,機器等可能導致數據丟失的故障。流處理引擎通常為應用程序提供了三種數據處理語義:最多一次、至少一次和精確一次。如下是對這些不同處理語義的寬鬆定義:最多一次(At-most-once)這本質上是一『儘力而為』的方法。保證數據或事件最多由應用程序中的所有運算元處理一次。 這意味著如果數據在被流應用程序完全處理之前發生丟失,則不會進行其他重試或者重新發送。下圖中的例子說明了這種情況。

At-most-once processing semantics

至少一次(At-least-once)應用程序中的所有運算元都保證數據或事件至少被處理一次。這通常意味著如果事件在流應用程序完全處理之前丟失,則將從源頭重放或重新傳輸事件。然而,由於事件是可以被重傳的,因此一個事件有時會被處理多次,這就是所謂的至少一次。下圖的例子描述了這種情況:第一個運算元最初未能成功處理事件,然後在重試時成功,接著在第二次重試時也成功了,其實是沒有必要的。

At-least-once processing semantics精確一次(Exactly-once)即使是在各種故障的情況下,流應用程序中的所有運算元都保證事件只會被『精確一次』的處理。(也有文章將 Exactly-once 翻譯為:完全一次,恰好一次)通常使用兩種流行的機制來實現『精確一次』處理語義。

  • 分散式快照 / 狀態檢查點
  • 至少一次事件傳遞和對重複數據去重

實現『精確一次』的分散式快照/狀態檢查點方法受到 Chandy-Lamport 分散式快照演算法的啟發[1]。通過這種機制,流應用程序中每個運算元的所有狀態都會定期做 checkpoint。如果是在系統中的任何地方發生失敗,每個運算元的所有狀態都回滾到最新的全局一致 checkpoint 點。在回滾期間,將暫停所有處理。源也會重置為與最近 checkpoint 相對應的正確偏移量。整個流應用程序基本上是回到最近一次的一致狀態,然後程序可以從該狀態重新啟動。下圖描述了這種 checkpoint 機制的基礎知識。

Distributed snapshot在上圖中,流應用程序在 T1 時間處正常工作,並且做了checkpoint。然而,在時間 T2,運算元未能處理輸入的數據。此時,S=4 的狀態值已保存到持久存儲器中,而狀態值 S=12 保存在運算元的內存中。為了修復這種差異,在時間 T3,處理程序將狀態回滾到 S=4 並「重放」流中的每個連續狀態直到最近,並處理每個數據。最終結果是有些數據已被處理了多次,但這沒關係,因為無論執行了多少次回滾,結果狀態都是相同的。另一種實現『精確一次』的方法是:在每個運算元上實現至少一次事件傳遞和對重複數據去重來。使用此方法的流處理引擎將重放失敗事件,以便在事件進入運算元中的用戶定義邏輯之前,進一步嘗試處理並移除每個運算元的重複事件。此機制要求為每個運算元維護一個事務日誌,以跟蹤它已處理的事件。利用這種機制的引擎有 Google 的 MillWheel[2] 和 Apache Kafka Streams。下圖說明了這種機制的要點。

At-least-once delivery plus deduplication二、『精確一次』是真正的『精確一次』嗎?

現在讓我們重新審視『精確一次』處理語義真正對最終用戶的保證。『精確一次』這個術語在描述正好處理一次時會讓人產生誤導。

有些人可能認為『精確一次』描述了事件處理的保證,其中流中的每個事件只被處理一次。實際上,沒有引擎能夠保證正好只處理一次。在面對任意故障時,不可能保證每個運算元中的用戶定義邏輯在每個事件中只執行一次,因為用戶代碼被部分執行的可能性是永遠存在的。考慮具有流處理運算符的場景,該運算符執行列印傳入事件的 ID 的映射操作,然後返回事件不變。下面的偽代碼說明了這個操作:Map (Event event) { Print "Event ID: " + event.getId() Return event }每個事件都有一個 GUID (全局惟一ID)。如果用戶邏輯的精確執行一次得到保證,那麼事件 ID 將只輸出一次。但是,這是無法保證的,因為在用戶定義的邏輯的執行過程中,隨時都可能發生故障。引擎無法自行確定執行用戶定義的處理邏輯的時間點。因此,不能保證任意用戶定義的邏輯只執行一次。這也意味著,在用戶定義的邏輯中實現的外部操作(如寫資料庫)也不能保證只執行一次。此類操作仍然需要以冪等的方式執行。那麼,當引擎聲明『精確一次』處理語義時,它們能保證什麼呢?如果不能保證用戶邏輯只執行一次,那麼什麼邏輯只執行一次?當引擎聲明『精確一次』處理語義時,它們實際上是在說,它們可以保證引擎管理的狀態更新只提交一次到持久的後端存儲。上面描述的兩種機制都使用持久的後端存儲作為真實性的來源,可以保存每個運算元的狀態並自動向其提交更新。對於機制 1 (分散式快照 / 狀態檢查點),此持久後端狀態用於保存流應用程序的全局一致狀態檢查點(每個運算元的檢查點狀態)。對於機制 2 (至少一次事件傳遞加上重複數據刪除),持久後端狀態用於存儲每個運算元的狀態以及每個運算元的事務日誌,該日誌跟蹤它已經完全處理的所有事件。提交狀態或對作為真實來源的持久後端應用更新可以被描述為恰好發生一次。然而,如上所述,計算狀態的更新 / 更改,即處理在事件上執行任意用戶定義邏輯的事件,如果發生故障,則可能不止一次地發生。換句話說,事件的處理可以發生多次,但是該處理的效果只在持久後端狀態存儲中反映一次。因此,我們認為有效地描述這些處理語義最好的術語是『有效一次』(effectively once)。那麼,當引擎聲明『精確一次』處理語義時,它們能保證什麼呢?如果不能保證用戶邏輯只執行一次,那麼什麼邏輯只執行一次?當引擎聲明『精確一次』處理語義時,它們實際上是在說,它們可以保證引擎管理的狀態更新只提交一次到持久的後端存儲。三、分散式快照與至少一次事件傳遞和重複數據刪除的比較

從語義的角度來看,分散式快照和至少一次事件傳遞以及重複數據刪除機制都提供了相同的保證。然而,由於兩種機制之間的實現差異,存在顯著的性能差異。

機制 1(分散式快照 / 狀態檢查點)的性能開銷是最小的**,因為引擎實際上是往流應用程序中的所有運算元一起發送常規事件和特殊事件,而狀態檢查點可以在後台非同步執行。但是,對於大型流應用程序,故障可能會更頻繁地發生,導致引擎需要暫停應用程序並回滾所有運算元的狀態,這反過來又會影響性能。流式應用程序越大,故障發生的可能性就越大,因此也越頻繁,反過來,流式應用程序的性能受到的影響也就越大。然而,這種機制是非侵入性的,運行時需要的額外資源影響很小。機制 2(至少一次事件傳遞加重複數據刪除)可能需要更多資源,尤其是存儲**。使用此機制,引擎需要能夠跟蹤每個運算元實例已完全處理的每個元組,以執行重複數據刪除,以及為每個事件執行重複數據刪除本身。這意味著需要跟蹤大量的數據,尤其是在流應用程序很大或者有許多應用程序在運行的情況下。執行重複數據刪除的每個運算元上的每個事件都會產生性能開銷。但是,使用這種機制,流應用程序的性能不太可能受到應用程序大小的影響。對於機制 1,如果任何運算元發生故障,則需要發生全局暫停和狀態回滾;對於機制 2,失敗的影響更加局部性。當在運算元中發生故障時,可能尚未完全處理的事件僅從上游源重放/重傳。性能影響與流應用程序中發生故障的位置是隔離的,並且對流應用程序中其他運算元的性能幾乎沒有影響。從性能角度來看,這兩種機制的優缺點如下。分散式快照 / 狀態檢查點的優缺點:
  • 優點:
  • 較小的性能和資源開銷
  • 缺點:
  • 對性能的影響較大
  • 拓撲越大,對性能的潛在影響越大

至少一次事件傳遞以及重複數據刪除機制的優缺點:

  • 優點:
  • 故障對性能的影響是局部的
  • 故障的影響不一定會隨著拓撲的大小而增加
  • 缺點:
  • 可能需要大量的存儲和基礎設施來支持
  • 每個運算元的每個事件的性能開銷

雖然從理論上講,分散式快照和至少一次事件傳遞加重複數據刪除機制之間存在差異,但兩者都可以簡化為至少一次處理加冪等性。對於這兩種機制,當發生故障時(至少實現一次),事件將被重放/重傳,並且通過狀態回滾或事件重複數據刪除,運算元在更新內部管理狀態時本質上是冪等的。

四、結論在這篇博客文章中,我希望能夠讓你相信『精確一次』這個詞是非常具有誤導性的。提供『精確一次』的處理語義實際上意味著流處理引擎管理的運算元狀態的不同更新只反映一次。『精確一次』並不能保證事件的處理,即任意用戶定義邏輯的執行,只會發生一次。我們更喜歡用『有效一次』(effectively once)這個術語來表示這種保證,因為處理不一定保證只發生一次,但是對引擎管理的狀態的影響只反映一次。兩種流行的機制,分散式快照和重複數據刪除,被用來實現精確/有效的一次性處理語義。這兩種機製為消息處理和狀態更新提供了相同的語義保證,但是在性能上存在差異。這篇文章並不是要讓你相信任何一種機制都優於另一種,因為它們各有利弊。五、參考
  1. Chandy, K. Mani and Leslie Lamport.Distributed snapshots: Determining global states of distributed systems. ACMTransactions on Computer Systems (TOCS) 3.1 (1985): 63-75.
  2. Akidau, Tyler, et al. MillWheel:Fault-tolerant stream processing at internet scale. Proceedings of the VLDBEndowment 6.11 (2013): 1033-1044.

推薦閱讀:
相关文章