流計算引擎數據一致性的本質
流計算的應用與實踐在大數據領域越來越常見,其重要性不言而喻,常見的流計算引擎有 Google DataFlow、Apache Flink,Apache Kafka Streams,Apache Spark Streaming 等。流計算系統中的數據一致性一般是用消息處理語義來定義的,如某引擎聲稱可以提供「恰好一次(Exactly-once Processing Semantics)流處理語義,表示(或暗示)引擎具備保證數據一致性的能力。事實上,「恰好一次(Exactly-Once)」并不等價于流計算的輸出數據就符合一致性的要求,該術語存在很多理解和使用上的誤區。
本篇文章從流計算的本質出發,重點分析流計算領域中數據處理的一致性問題,同時對一致性問題進行簡單的形式化定義,提供一個一窺當下流計算引擎發展脈絡的視角,讓大家對流計算引擎的認識更為深入,為可能的流計算技術選型提供一些參考。文章主要分為三個部分:第一部分,會介紹流計算系統和一致性難題的本質;第二部分,會介紹一致性難題的通用解法以及各種方案間的取舍;第三部分,會介紹主流的流計算引擎是如何對通用解法進行泛化以實現一致性。
一 流計算中的一致性
在認識流計算系統一致性之前,我們需要精確定義流計算。流(Streaming)計算是一種在無邊界數據(unbounded data)上進行低延遲計算的數據處理過程。相應的,批計算更準確的說法是有界數據(bounded data)的處理,亦即有明確邊界的數據處理,流和批只是兩種不同數據集的傳統數據計算方法,它們并不是涇渭分明的,譬如也可以通過批量的方式(e.g. Spark Streaming 中的 micro-batch)來實現無界數據上的流處理過程。
1 一致性定義及挑戰
如果我們將流計算的過程(獲取輸入數據、處理數據、輸出計算結果)視為數據庫的主從同步過程,抑或視為一種從流數據生成衍生數據集(表)的過程,則流計算中的數據一致性同關系型數據庫事務 ACID 理論中的 Consistency 有異曲同工之妙,后者指的是在事務開始或結束時,數據庫中的記錄應該在一致狀態,相應地,流計算中的一致性可以定義為:流計算系統在計算過程中,或是出現故障恢復計算后,流系統的內部狀態和外部輸出的數據應該處在一致的狀態。譬如,當故障恢復后開始重新計算,計算的結果是否滿足數據的一致性(即用戶無法區分恢復前和恢復后的數據)?記錄是否會重復/丟失,第三方系統對同一條計算結果的多次獲取,是否會存在值上的不一致?對一致性有了清晰的認知和定義后,我們來看看為什么實現一致性這么難。
在定義一中我們可以看到,流計算輸入的數據是無邊界的,所以系統中會存在消息抵達流計算系統延遲、順序錯亂、數量/規模未知等不確定因素,這也是流計算系統一致性復雜性遠遠大于批處理系統的原因:批處理系統中的輸入是確定的,計算過程中可以通過計算的原子性來保證數據的一致性(如 Spark 中的 RDD 血緣)。此外,同其他分布式應用一樣,流計算系統經常也會受到各類意外因素的影響而發生故障,比如流量激增、網絡抖動、云服務資源分配出現問題等,發生故障后重新執行計算,在存在不確定輸入的前提下設計健壯的容錯機制難度很大。
除了數據輸入帶來的挑戰,流計算輸出的數據會被實時消費,類似這樣不同于批處理的應用場景,也給數據的一致性帶來的諸多挑戰,如出現 FO 后,是撤回之前發出的數據,還是是同下游進行協商實現一致性,都是需要考慮的。
2 一致性相關概念祛魅
正確認識流計算系統一致性的內在含義和其能力范疇,對我們構建正確且健壯的流計算任務至關重要。下面我會介紹幾組概念,以便于大家更好地理解流計算系統的一致性。
恰好一次≠恰好一致
今天大多數流計算引擎用「Exactly-Once」去暗示用戶:既然輸入的數據不是靜態集合而是會連續變化的,那對每一條消息「恰好處理」了一次,輸出的數據肯定是一致的。上述邏輯的推導過程是沒問題的,但并不嚴謹,因為 Exactly-Once 作為一個形容詞,后面所連接的動詞或者賓語被故意抹去了,不同的表達含義也會大相徑庭。
例子1,后接不同的動(名)詞:Exactly-once Delivery 和 Exactly-once Process 。前者是對消息傳輸層面的語義表達,和流計算的一致性關系不是很大,后者是從流計算的應用層面去描述數據處理過程。
例子2,后接不同的名詞:Exactly-once State Consistency 和 Exactly-once Process Consistency。前者是 Flink 在官網中對其一致性的敘述,后者是 Kafka Streaming 的一致性保證,前者的語義約束弱于后者。Exactly-once State Consistency 只是表達了:流計算要求對狀態的更新只提交一次到持久后端存儲,但這里的狀態一般不包括「輸出到下游結果」,而僅指引擎內部的狀態,譬如各個算子的狀態、實時流的消費偏移等,流計算引擎內部狀態變更的保證,并不能等價于從輸入到輸出的一致性,端到端一致性需要你自己關心。
總之,如何我們后面再看到 Exactly-once XXX,一定要警惕引擎想要透露出什么信息。
端到端的數據一致性
端到端一致性(End-To-Ene Consistency),即將數據的輸出也作為流計算引擎的一致性設計的一部分,正確的結果貫穿著這整個流計算應用的始終:從輸入、處理過程、輸出,每一個環節都需要保證其自身的數據一致性,同時在整個流計算流程中,作為整體實現了端到端的一致性。
下面敘述中,如果不是特意說明,一致性指的是引擎自身狀態的一致性,端到端一致指的是包含了輸出的一致性。
二 流計算系統的本質
前面我們定義了流計算一致性的概念,這一部分將會從概念出發將問題進行形式化拆解,以便得到通用化的解法。
1 再次認識流計算
上面提到,流計算的輸入數據是沒有邊界的,這符合我們傳統上對流計算認知。在《System Streaming》一書中,作者提出了一個將流批統一考慮的流計算理論抽象,即,任意的數據的處理都是「流(Stream)」 和「表(Table)」間的互相轉換,其中流用來表征運動中的數據,表用來表征靜止的數據:
流 -> 流:沒有聚合操作的數據處理過程;
流 -> 表:存在聚合操作的數據處理過程;
表 -> 流:觸發輸出表數據變化的情況;
表 -> 表:不存在這樣的數據處理邏輯。
在這個統一的理論框架下,批處理過程的一致性也可以納入本文討論的范疇中來。但無論是純粹的流計算,還是上面統一的數據處理模型,我們都可以將流(批)數據處理的過程抽象為「讀取數據-處理數據-輸出數據」這樣的三個部分,可用下面的無向圖來表達,其中點代表數據加工邏輯,邊表示數據流向,數據處理過程中的中間狀態(State)一般需要做持久化存儲。
2 確定性/非確定性計算
流計算中的確定性指的是,給定相同的一組數據,重復運行多次或者打亂數據進入引擎的順序,計算完成后將會輸出相同的結果,否則就是非確定性計算。常見的非確定性計算包括使用了隨機數、使用系統時間、字符串拼接等。如果流計算中存在非確定性的計算,則會給端到端一致性的實現造成很多困難,部分引擎并不能很好地支持此類場景。
3 一致性問題的形式化定義
在存在不確定性計算的流計算中,不確定性計算的(中間)結果可視為流計算引擎狀態的一部分。從整體上看,任何一個時間點的引擎狀態等于之前所有事件計算結果(中間結果和輸出結果)的累計。如果定義流計算的輸入集合為:E,t 時刻以來的輸入集合為 E(t),輸出集合為 Sink(t),引擎此時狀態為 State(t),State(t) 包括各個算子的狀態(包括上面提到的不確定性計算)、數據源的消費偏移量(或文件讀取偏移等)等:
State(t) = OperatorState(t) + SourceState(t)
則定義流計算引擎的計算過程為,存在計算計算邏輯 F 使得:
F(E(t), Sink(t), State(t)) = Sink(t+1) + State(t)
令 O(t) = Sink(t) + State(t),即將計算對引擎狀態的更新視為一種特殊的輸出,則流計算過程可簡化為:
F(E(t), O(t)) = O(t+1)
結合流計算上面流計算一致性的定義,我們希望在引擎發生故障 FailOver 時,存在一種恢復函數 R 使得
R(E(t), O(t)) = O'(t+1),且 O'(t+1) = O(t+1)
我們在這里將引擎狀態作為一種特殊輸出的考慮有兩點。其一,引擎的狀態一般也是輸出到外部存儲如 RocksDB/HDFS,這和計算下游的輸出別無二致。其二,通過屏蔽引擎內部的容錯機制實現,簡化端到端一致性問題的抽象過程,便于更好地理解問題本身。
三 一致性的通用解法
1 通用解法的推導
我們在上面定義了端到端一致性難題:R(E(t), O(t)) = O(t+1)。從輸出結果的使用方(引擎內部和引擎下游數據消費方)的視角來看:對于記錄 O(t+1),當在故障發生的時間小于 t (數據沒有輸出)或者 大于 t + 1(數據已經輸出了),數據肯定是一致的。
當在 t ~ t + 1 時刻發生故障,恢復函數 R 可以屏蔽此次故障產生的副作用,讓使用方認為沒有故障發生,可以得到正確的 O(t+1),顯然,解決的思路是:將 E(t) 和 O(t) 作為輸入,重新執行計算 F,則可以得到正確的 O(t+1),具體地,E(t) 可以通過回撥數據偏移量得到,O(t) 需要從持久化存儲中獲取。O(t) 是否可以通過遞歸重算得到呢,即 O(t) = F(E(t-1), O(t-1)) ,答案是不可以,因為計算過程中可能存在不確定的計算邏輯,如果重算,則有一定概率 O(t) ≠ F(E(t-1), O(t-1)) 。
因此,我們得到流計算引擎要實現端到端一致性數據處理語義的充分必要條件:在流計算過程中,需要實時存儲每一條中間和最終計算結果,如果考慮吞吐率不能存儲每一條,則需定期以事務的方式進行批量存儲。對于每一個 O(t) 存儲后, 恢復函數 R 的實現就簡單多了:任務恢復時,將 O(t) 重新加載,使用 F 執行重算操作。
2 通用解法的工程實現
我們將端到端一致性問題的解法結合工程實踐,分析一下通用解法下的若干實現場景。
在通用解法中,我們需要存儲每一次計算的中間結果,這對引擎的架構設計、配套基建能力有著很高的要求,如需要高可用、高吞吐的存儲后端用于狀態存儲。因此,我們將條件退化為可以通過事務的方式進行批量存儲,這是因為事務的 ACID 特性能保證結果能以原子提交的方式作用于下游算子或者是外部的消息系統/數據庫,在保證了結果(狀態)一致性的前提下,能達到較高的吞吐率。
進一步分析,每一次存儲或者批量事務存儲 O(t) 時,引擎到底做了什么?前面我們定義了 O(t) = Sink(t) + State(t) -> O(t) = Sink(t) + OperatorState(t) + SourceState(t) ,對于引擎來說,當出現 FailOver 時,都會通過 SourceState(t) 回撥數據源偏移量進行部分重算,即消息讀取語義是 At-Least-Once 的,當重復計算時,前面存儲的結果(每一次計算)或者空的結果(批量事務)可以實現冪等變更的效果:如果結果已經存在了, 則使用已有的結果,消除不確定性計算帶來的副作用,如果之前的結果不存在,就更不會對外部系統有影響了。
如果我們的計算過程都是確定性的,那么上述的充分必要條件會有什么變化呢?在確定性計算的前提下,如果引擎輸出結果的接受端是可以實現為冪等,則很多約束條件會有所簡化。由于 O(t) = Sink(t) + State(t) ,引擎內部很好實現冪等狀態更新,若引擎下游系統也實現了數據冪等,當在 t ~ t + n 間內出現 FailOver 時,引擎可以通過重新計算 t ~ t + n 之間的所有值,直接輸出給下游使用。
因此,在僅有確定性計算的流計算系統中,實現端到端的充分必要條件可退化為:在流計算過程中,需要外部的最終結果接受端實現冪等,實時存儲每一條中間和最終計算結果,如果考慮吞吐率不能存儲每一條,則需定期批量存儲,上述條件中去掉了對「事務」的要求的原因:如果在提交這一批數據的提交過程中又發生了異常,譬如只有部分節點的結果輸出了,其他節點發生了故障結果丟失,則可以通過回到上個批次提交的狀態,重算此批次數據,重算過程中,由于僅存在確定性計算,所以無論是引擎內還是引擎外,是可以通過冪等來保證數據的的一致性的。
在實際的流計算引擎實現中,對于結果內容的定義大都是一致的,主要包括輸入源的消費偏移 SourceState(t),e.g. Kafka Offset,算子狀態 OperatorState(t),e.g. Spark RDD 血緣,輸出的結果 Sink(t),e.g. Kafka 事務消息,但是在結果的存儲方式上各有所不同,下面我們來看一看目前業界主流的幾個流計算引擎的設計考量。
四 一致性的引擎實現
目前流計算引擎的種類非常多,不是所有的引擎都可以實現端到端一致的流處理,在具備此能力的引擎中,從技術成本、引擎架構、能力范圍考慮,會有不同的取舍和實現,如 Flink 中使用了輕量級的「分布式一致性快照」用于狀態管理,Kafka Streams 為何沒有使用呢?實現了冪等輸出就一定能實現端到端一致么?本章節會一一解答上述問題。
1 Google MillWheel
Google在2013年發了一篇名為《MillWheel: Fault-Tolerant Stream Processing at. Internet Scale》的文章,論述了在 Google 內部實現低延遲數據處理的編程模型和工程實現,后面 Google 在此基礎上抽象出了 DataFlow 流處理模型(具體參考論文《The Dataflow Model: A Practical Approach to Balancing Correctness, Latency, and Cost in Massive-Scale,Unbounded, Out-of-Order Data Processing》),后者對流計算流域的影響堪比20世紀初 GFS,BigTable 以及MapReduce 三篇論文對大數據的影響,后面 Google 又在 MillWheel 之上繼續發展,開源了 Apache Bean 這個系統級的流批一體數據解決方案,因為 MillWheel 是更純粹的「流計算」,所以我們重點來分析 MillWheel。
MillWheel 使用了一種名為「Strong production」的機制將每個算子的輸出在發送至下游之前都進行了持久化存儲,一旦發生了故障,當需要恢復時,引擎可以直接將存儲后的結果發出去。回頭再看端到端一致性數據處理語義的充分必要條件,顯然 MillWheel 是符合「實時存儲每一條中間和最終計算結果」這個條件的。對于存在不確定性計算的流計算場景,當 FailOver 時,引擎會從源頭重新發送消息進行重算,多次計算可能會產生的不一致的結果,但由于「Strong Production」會對計算進行去重,因此即便進行了多次重算,但有且僅有一次重算的結果被輸出給下游(下游算子或結果接受端),從整體上來看數據是滿足一致性的,這也被稱之為「Effective Determinism」。
MillWheel 會對每一條記錄賦予一個唯一 ID,同時基于此 ID 維護一份是否處理過當前記錄的目錄。對于每一條流入當前算子的記錄,引擎查找此 ID 目錄以確定此記錄是否是已經處理過。這里會有很多技術上的挑戰,這里稍微舉幾個例子。
譬如,需要有穩定且高吞吐的存儲后端用于結果存儲,Google 內部的 BigTable 發揮了其作用。流任務執行前后,引擎會對執行流做若干優化,如合并多個邏輯算子至單個算子(類似 Flink 中的 chain 化)、節點內先執行部分合并(count / sum)后再 shuffle等等,種種手段均是為了降低算子間 IO 的數據規模。
此外,在判斷「當前記錄」是否已被處理時,MillWheel 使用了布隆過濾器用于前置過濾,因為在一個正常運行的流計算任務中,記錄絕大多數的時間都是不重復的,這剛好契合布隆過濾器的使用場景(如過濾器返回不存在則記錄一定不存在),引擎中的每個節點都維護了以記錄 ID 為主鍵的布隆過濾器,計算前都會通過此過濾器進行判斷,若提示不存在則進行數據處理,如果存在,則需要二次校驗。當然,MillWheel 在實際使用布隆過濾器,是做了若干改造的,這里就不具體展開了。
2 Apache Flink
MillWheel 作為一個內部系統可以存儲每一個中間結果,但是對于開源系統的 Apache Flink 來說,畢竟不是每一個公司都有這么完備的技術基建。Flink 會定期把結果以事務的方式進行批量存儲,這里的「結果」如上面分析,由源狀態 SourceState(t)、算子狀態 OperatorState(t) 、輸出的結果 Sink(t) 組成,其中 Flink 把源狀態和算子狀態進行了打包,統稱為「分布式一致性快照」(基于 Chandy-Lamport 分布式快照算法來實現),數據會持久化在 RocksDB 中。
如上圖所示,Flink 引擎會定時(每個周期稱之為一個 epoch)以 2PC 的方式提交結果。事實上,即便不考慮結果輸出,Flink 「分布式一致性快照」的快照的實現也是一個 2PC 的過程:算子的狀態快照存儲類似于 2PC 的 Prepare 階段,但 Commit 的確認僅需 Coordinator( Flink JobManager) 根據「是否收到了完整算子的 ACK 」來推出是否 Commit 或 Abort。將結果輸出納入快照生成的 2PC 后,端到端一致性數據處理語義的充分必要條件在這里也得到了滿足:在流計算過程中,定期(epoch)以事務(2PC)的方式進行批量存儲結果(分布式一致性快照 + 寫外部存儲)。需要注意的是,由于 Flink 會以 epoch 為周期輸出結果,因此基于此構建的流處理系統會存在一定的端到端延遲。
3 Apache Kafka Streams
Kafka Streams 是 Apache Kafka 0.10.0版本中包含的一個Java庫,嚴格來講并不算一個完整的流處理引擎,利用這個庫,用戶可以基于 Kafka 構建有狀態的實時數據處理應用,更進一步地,Kafka Streams 需要數據輸入源和輸出均為 Kafka 消息隊列。
Kafka Streams 中的「結果」也以事務的方式批量持久化,但和 Flink 不同的是,這些結果是被寫入不同的消息隊列中:
源狀態 SourceState(t):即 Kafka 源中的 Offset 信息,會被寫入一個單獨的 Kafaka 隊列中,該隊列對用戶透明;
算子狀態 OperatorState(t) :計算中算子的 Changelog,也會寫入單獨的 Kafaka 隊列中,該隊列對用戶透明;
輸出結果 Sink(t) :即用戶配置的實際的輸出隊列,用于存放計算結果。
Kafka Streams 將上述結果定期以事務的方式進行批量存儲,上述事務在 Kafka 這被稱之為 Transactions API,使用這個 API 構建的流處理應用,可以在一個事務中將多個主題消息進行同時提交,如果事務終止或回滾,則下游消費不會讀取到相應的結果(當然下游消費者也需要配置相應的一致性級別),其過程如下圖所示:
如果稍微回顧一下 Flink 一致性的實現邏輯,會發現這兩者有很多相似點,因此 Kafka Streams 的輸出結果也會存在一定的端到端延遲。因為在提交結果時創建了新的事務,所以平均事務大小由提交間隔確定,當流量相同時,較短的提交間隔將導致較小的事務,但太小的間隔將導致吞吐下降,因此吞吐量與端到端處理延遲之間需要有一個折衷。
同時,我們需要注意到的是,Flink 和 Kafaka 中的「事務」提交,和我們常規的操作關系型數據庫中的事務還是有所不同的,后者的事務提交對象一般就一個(e.g. MySQL Server),但在流計算中,由于結果有下游輸出、消費進度、算子狀態等,因此流計算引擎需要設計一個全局的事務協議用于和下游待提交的各個存儲后端進行交互。舉例:Kafka Streams 的輸出后端需要是 Kafka,以配合在事務提交過程中,屏蔽部分已輸出至下游(被 Kafka Broker 持久化),但還不滿足事務隔離性的消息(read_committed 級別),從流計算輸出的角度來看,這些消息已被成功處理同時輸出至下游,但從端到端的一致性來看,它們依然屬于不一致的數據。又如,使用 Flink 處理 CDC(Change Data Capture) 的場景,如果下游是 MySQL,在 Flink 2PC 完成之前,來自不同 Flink 節點的數據輸出后其實已經被 commit,類似 Kafka Broker 中的消息無法撤回,MySQL 提交的事務也無法回滾,因此輸出數據中也需要有類似的字段實現隔離(isolation)語義,以屏蔽這種不一致的數據。
4 Apache Spark Streaming
這里提到的 Spark Streaming 指的是原始的基于「Micro-batch,微批」的 Spark 流處理引擎,后面 Spark 又提出了Structured Streaming,使用 Continuous Processing mode 來替代「微批」解決延遲的問題,容錯機制上和 Flink 一樣也使用了Chandy-Lamport 算法,Structured Stream 目前還不成熟,暫時還不能完全支持 Exactly-Once-Processing,因此這里著重對比 Spark Streaming。
Spark Streaming 只能保證引擎內部的處理邏輯是一致的,但是對于結果輸出,則并沒有做特別的抽象,因此如果我們希望實現端到端的一致性語義,則需要對自行維護和判斷一些信息。同傳統的批處理系統類似,流處理中也是以 RDD 構建出整個的數據血緣,當發生 FailOver 時,則重新計算整個 RDD 就可以了。如果 Spark Streaming 存在非確定性的計算,則不能實現端到端一致,原因是:1、不滿足條件一「實時存儲每一條結果」。如果能記錄下每個 RDD 分區下的執行情況,避免重復執行(冪等),也一定程度上能實現端到端一致,但這需要進行大量的改造工作,最終形態會和 MillWheel 比較類似;2、不滿足條件二「事務方式存儲」,需要保證每個 RDD 產出環節的事務性(如最終結果寫 HDFS 就不是原子的)。
考慮一種比較簡單的場景:不存在非確定計算的流計算應用。如果不存在非確定計算,根據端到端的一致性語義的充分必要條件,只需要接受端實現冪等,則 Spark Streaming 就可以實現端到端的一致性。背后的原因是,當將形式化的結果定義與 Spark Streaming 進行映射,會發現當以「微批」的形式存儲結果時,源狀態和算子狀態以 RDD 血緣的方式天然地和輸出結果進行了綁定,即當輸出最終結果時,我們其實也一并輸出了源和算子狀態,操作符合一致性條件。
更進一步,當把僅有確定性計算(冪等輸出)的 Spark Streaming 和 僅有確定性計算(冪等輸出)的的 Flink 進行對比時,會發現二者非常相似。RDD 血緣類比分布式一致性快照,批量輸出類比一致性快照后的結果輸出,微批類比 epoch。不同之處在于:1、Spark Streaming 在計算過程中的每一個 RDD 生成階段都會有延遲,而 Flink 在計算過程中可以進行實時處理;2、Spark Streaming 只有一個「epoch」,而 Flink 可以有多個 「epoch」并行存在?;谏鲜鰞牲c原因,Flink 的數據處理的端到端延遲要小得多,但這兩種引擎冪等輸出能實現一致性的本質是相似的。
5 各引擎一致性實現總結
上面我們簡述了目前主流的幾種流計算引擎的一致性實現機制。從整體來看,如果實現端到端的一致性,則均需要滿足我們上面從形式化定義推導出來的充分必要條件:實時存儲每一條中間和最終計算結果,如果考慮吞吐率不能存儲每一條,則需定期以事務的方式進行批量存儲,這里的結果包含流計算引擎中的狀態。上面的充分必要條件還可以進一步簡化,即實時存儲結果或定期事務,均可以視為當前處理邏輯單元(算子或最終存儲)對上游的輸入(引擎狀態+輸出結果)進行的冪等化處理:引擎 FailOver -> 輸入源的事件會進行重發 -> 前期存儲的結果會用于去重/事務回滾讓結果(引擎狀態+輸出結果)回到上一次的一致性狀態 -> 下一批結果輸出 -> 結果接受端只影響一次 -> 實現了端到端的一致。
下面的圖列舉出各引擎實現端到端一致性的路線圖:
前面分析端到端一致性的實現中,重點在分析引擎處理(算子)和輸出端行為,沒有提及對數據源的要求,數據源需具備重播(repaly)和消息去重的功能即可,屬于基礎要求,這里不再展開。
五 總結與展望
本文從流計算的本質出發,推導出了在流處理中實現端到端一致性的通用解法,同時結合通用解法,分析了目前幾種主流流計算引擎在一致性上的實現思路。有「財大氣粗」型的 Google MillWheel,背靠強大的基礎架構用于狀態管理;有「心靈手巧」型的 Apache Flink,巧妙地結合了分布式一致性快照和兩階段事務實現一致性;也有「重劍無鋒」型的 Apache Kafka Streams,直接將流處理過程事務化,屏蔽復雜的底層邏輯,編程模型和理解成本都更簡單(當然也一定程度上限制其使用的場景);也有 「蓬勃發展」中的 Apache Spark (Structured)Streaming,底層的一些實現構想和 Apache Flink 愈加趨同,可以期待它將來能達到類似 Apache Spark 在批處理流域中的地位。
當然,引擎雖然這么多,但其背后是有若干條主線貫穿的,希望我們能撥開迷霧,不被營銷的噱頭所影響,能洞察到一些更為本質的東西。本文論述的端到端一致的流數據處理實現,重點聚焦在「計算和狀態」管理,但實際上,還有很多因素需要我們去考慮,如時間窗口的推導、延遲數據的處理策略、底層計算節點的通信容錯等,這些問題多多少少也會影響數據的一致性,考慮到文中篇幅,這里就不一一展開了,感興趣的同學可以選擇一個主題做深入研究。
下面這些論文對進一步了解流計算很有幫助,感興趣的同學可以參考:
《Streaming System》,T Akidau, S Chernyak, R Lax
《Transactions in Apache Kafka》,Apurva Mehta,Jason Gustafson
《A Survey of State Management in Big Data Processing Systems》,QC To, J Soto, V Markl
《MillWheel: fault-tolerant stream processing at Internet scale》,T Akidau, A Balikov, K Bekirolu, S Chernyak
《Discretized Streams: Fault-Tolerant Streaming Computation at Scale》,M Zaharia, T Das, H Li, T Hunter