1. 程式人生 > >《Kafka Stream》調研:一種輕量級流計算模式

《Kafka Stream》調研:一種輕量級流計算模式

Confluent Inc(原LinkedIn Kafka作者離職後創業公司)在6月份預告推出Kafka Stream,Kafka Stream會在Kafka 0.10版本中推出。

對於流計算,已經有Storm、Spark,Samza,包括最近新起的Flink,Kafka為什麼再自己做一套流計算呢?Kafka Stream 與這些框架比有什麼優勢?Samza、Consumer Group已經包裝了Kafka輕量級的消費功能,難道不夠嗎?

花了一些時間閱讀docs 和一些PPT,寫一份粗略的調研材料供大家參考。

什麼是流計算?流是計算的一個連續計算型別

  1. Single:例如HTTP,傳送一個Request請求、返回一個Response
    screenshot
  2. Batch:將一組作業提交給計算機,返回一組,優勢是減少IO等待時間
    screenshot
  3. Stream:Batch非同步過程,任務和任務之間沒有明顯的邊界
    screenshot

流計算一般有哪些方式?

DIY 簡單實現

以wordcount來作例子,我們可以啟動一個server,記憶體中建立一個HashMap,把輸入先分詞,然後根據word檢視更新HashMap。是不是很簡單?但帶來的問題是什麼?

  • 如果掛了,資料都被清空,資料重複怎麼辦?
  • 如果資料量非常大,一塊記憶體放不下怎麼辦?
  • 如果在多臺機器上部署,如何保證分配策略和先後順序?

我們把這些問題做一個分類,主要有這樣幾個:

  • 保序處理
  • 規模和切片
  • 異常恢復
  • 狀態類計算(例如TopK,UV等)
  • 重新計算
  • 時間、視窗等相關問題

利用現有框架

比較成熟度的框架有:Apache Spark, Storm(我們公司開源Jstorm),  Flink, Samza 等。第三方有:Google’s DataFlow,AWS Lambda

現有框架的好處是什麼?

強大計算能力,例如Spark Streaming上已經包含Graph Compute,MLLib等適合迭代計算庫,在特定場景中非常好用。

問題是什麼?

  • 使用起來比較複雜,例如將業務邏輯遷移到完備的框架中,Spark RDD,Spout等。有一些工作試圖提供SQL等更易使用模式降低了開發門檻,但對於個性化ETL工作(大部分ETL其實是不需要重量級的流計算框架的)需要在SQL中寫UDF,流計算框架就退化為一個純粹的容器或沙箱。
  • 作者認為部署Storm,Spark等需要預留叢集資源,對開發者也是一種負擔。

screenshot

Kafka Stream定位是輕量級的流計算類庫,簡單體現在什麼方面?

  • 所有功能放在Lib中實現,實現的程式不依賴單獨執行環境

    • 可以用Mesos,K8S,Yarn和Ladmda等獨立排程執行Binary,試想可以通過Lamdba+Kafka實現一個按需付費、並能彈性擴充套件的流計算系統,是不是很cool?
    • 可以在單整合、單執行緒、多執行緒進行支援
  • 在一個程式設計模型中支援Stateless,Stateful兩種型別計算
  • 程式設計模型比較簡潔,基於Kafka Consumer Lib,及Key-Affinity特性開發,程式碼只要處理執行邏輯就可以,Failover和規模等問題由Kafka本身特性幫助解決

個人感覺Kafka Lib是Samza一個增強版(Samza也是Linkedin與Kafka深度整合的流計算框架),將來可以替換Samza,但無法撼動Spark、Flink等語義上比較高階的流計算系統地位,只能做一些輕量級流處理的場景(例如ETL,資料整合,清洗等)。

Kafka Stream 例子

先來看一個例子,通過Kafka Stream程式碼開發:

screenshot

這裡面做了這樣幾件事情:

  1. 構建了Kafka中資料序列化/反序列化方式
  2. 構建了2個計算節點

    • 分詞(flatMapValues),並將結果根據Key來Map
    • Reduce(根據Key來計算結果)
  3. 將結果寫到Kafka一個結果Topic中(增量方式)

在2個結算節點中,使用了一個Kafka Topic將計算結果序列化、並反序列化。相當於Map-Reduce中Streamline。

這段程式可以執行在一個Thread中,也可以執行在N臺機器上,主要歸結於Kafka Consumer Lib可以幫助對資料與計算解耦分離。

基本概念

Processor:Processor是一個基本的計算節點

public interface Processor<K, V> {
void process (K key, V Value);
void punctuate(long time stampe);
}

Stream: Processor 處理後後結果輸出

兩者的關係如圖:

screenshot

Kafka Stream如何解決流計算中6個問題:

保序(Ordering)

對Kafka而言,在一個Partition(Shard)下,資料是先進先出嚴格有序的,因此不是問題。
screenshot

分割槽與規模(Partition & Scalability)

流計算規模取決於2個因素:資料是否能線性擴容、計算能否線性擴容。

資料

Kafka中的資料通過Partition方式劃分,每個Partition嚴格有序,可以做到彈性伸縮(實際上目前版本中彈性伸縮是不完整的,Kafka在0.10版本中能提供完全彈性伸縮的能力)。

screenshot

計算

Kafka對於消費端提供Consumer Group功能,可以擴充套件消費Instance達到與Partition同樣的水平擴充套件能力,過程中保證一個消費Instance只能消費一個Partition。

screenshot

故障恢復(Fault Tolerance)

Kafka Consumer Group已實現了負載均衡,因此當有消費例項crash時也能保證迅速未完成的任務,過程中資料不丟,可能會重複(取決於消費checkpoint配合)

screenshot

狀態處理(State)

這個問題相對比較複雜,在流計算場景中,分為兩類計算:

  • Stateless(無狀態):例如Filter,Map,Joins,這些只要資料流過一遍即可,不依賴於前後的狀態
  • Stateful(有狀態):主要是基於時間Aggregation,例如某段時間的TopK,UV等,當資料達到計算節點時需要根據記憶體中狀態計算出數值

Kafka Stream 提供了一個抽象概念KTable,KStream來解決狀態儲存和資料變化的問題,見下面的章節解釋。

重放(Reprocessing)

在瞭解了RedoLog和State後,重放這個概念並不難理解

screenshot

基於時間視窗計算(Time, Windowsing)

時間是流計算的一個重要熟悉,因為在現實過程中資料採集往往並不是很完美的,歷史資料的到來會打斷我們對計算的假設。時間有兩個概念:

  • Event Time: 物理時間中的客觀時間,代表事件發生時的一刻
  • Processing Time: 實際處理的時間(到達伺服器時間)

雖然Processing Time對處理比較容易,但因歷史資料的影響,採用Event Time更為準確。一個零售業中比較典型的場景是:統計每10分鐘內每個產品的銷量(或網站每個時間點UV、PV的統計)。銷售資料可能會從不同的渠道實時流入,因此我們必須依賴於銷售資料產生的時間點來作為視窗,而不是資料達到計算的點。

screenshot

Kafka Stream用一種比較簡單粗暴方式來解決這個問題,他會給每個windows一個狀態,這個狀態只是代表當前時刻的數值,當有新資料達到該視窗時,狀態就被改變了。對於windows based aggregation,Kafka Stream做法是:

Table (狀態資料) + Library = Stateful Service

Stream & Table

為了實現狀態的概念,Kafka 抽象了兩種實體Kstream, KTable

  • Stream 等同於資料庫中Change log
  • Table 等同於資料庫在一個時間點Snapshot,兩個不同的Snapshot之間通過1個或多個changelog造成

screenshot

假設有2個流,一個流是送貨,另外一個流是銷售,我們對著兩個流進行Join,獲得當前的庫存狀態:

shipment stream:

item IDstore codecount
10CA200
23NY50
23CA101
54WA1000

sale stream:

item IDstore codecount
10CA20
23NY10

當這兩個流中的記錄先後達到情況下,會影響庫存狀態,整個庫存的變化狀態如下:

screenshot

我們把這兩個流放到Kafka Stream中,就會看到一個Processor節點中的狀態變化如下:

screenshot

基於狀態資料,我們可以在該節點定義處理的邏輯:

if (state.inventory[item].size < 10)
{
notify the manager;
}
else if (state.inventory[item] > 100)
{
on sale;
}

KTable,KStream可能比較抽象,KafkaStream包裝了high-level DSL,直接提供了filter, map, join等運算元,當然如果有個性化需求可以使用更低抽象程度API來完成。

粗淺的看法

流計算場景中,是否會有兩個極端:複雜記憶體操作+迭代計算,輕量級資料加工與ETL。這兩個比例分別佔據多少?在我們常用的ETL場景裡,大部分其實是輕量級Filter,LookUP,Write Storage等操作,有時候我們為了對資料做加工,不得不借助一個執行容器去選擇流計算的框架。Docker,Lamdba可以解決這類問題,但需要有一定流計算的開發量。

我覺得對輕量級ETL場景,一個而理想的架構是Kafka Stream這樣的輕量級計算庫+Lamdba,這樣就能做到安全按需使用的流計算模式。

Kafka Stream有一些關鍵東西沒有解決,例如在join場景中,需要保證來源2個Topic資料Shard個數必須是一定的,因為本身做不到MapJoin等技術。在之前的版本中,也沒有提供EventTime等Meta欄位。