ApacheStorm官方文件——基礎概念

青衫無名發表於2017-05-22

Storm 系統中包含以下幾個基本概念:

  1. 拓撲(Topologies)
  2. 流(Streams)
  3. 資料來源(Spouts)
  4. 資料流處理元件(Bolts)
  5. 資料流分組(Stream groupings)
  6. 可靠性(Reliability)
  7. 任務(Tasks)
  8. 工作程式(Workers)

譯者注:由於 Storm 的幾個基礎概念無論是直譯還是意譯均不夠清晰,而且還會讓習慣了 Storm 程式設計模型的讀者感到困惑,因此後文在提及這些概念時大多還會以英文原文出現,希望大家能夠諒解。


拓撲(Topologies)

Storm 的拓撲是對實時計算應用邏輯的封裝,它的作用與 MapReduce 的任務(Job)很相似,區別在於 MapReduce 的一個 Job 在得到結果之後總會結束,而拓撲會一直在叢集中執行,直到你手動去終止它。拓撲還可以理解成由一系列通過資料流(Stream Grouping)相互關聯的 Spout 和 Bolt 組成的的拓撲結構。Spout 和 Bolt 稱為拓撲的元件(Component)。我們會在後文中給出這些概念的解釋。

相關資料

資料流(Streams)

資料流(Streams)是 Storm 中最核心的抽象概念。一個資料流指的是在分散式環境中並行建立、處理的一組元組(tuple)的無界序列。資料流可以由一種能夠表述資料流中元組的域(fields)的模式來定義。在預設情況下,元組(tuple)包含有整型(Integer)數字、長整型(Long)數字、短整型(Short)數字、位元組(Byte)、雙精度浮點數(Double)、單精度浮點數(Float)、布林值以及位元組陣列等基本型別物件。當然,你也可以通過定義可序列化的物件來實現自定義的元組型別。

在宣告資料流的時候需要給資料流定義一個有效的 id。不過,由於在實際應用中使用最多的還是單一資料流的 Spout 與 Bolt,這種場景下不需要使用 id 來區分資料流,因此可以直接使用 OutputFieldsDeclarer來定義“無 id”的資料流。實際上,系統預設會給這種資料流定義一個名為“default”的 id。

相關資料

資料來源(Spouts)

資料來源(Spout)是拓撲中資料流的來源。一般 Spout 會從一個外部的資料來源讀取元組然後將他們傳送到拓撲中。根據需求的不同,Spout 既可以定義為可靠的資料來源,也可以定義為不可靠的資料來源。一個可靠的 Spout 能夠在它傳送的元組處理失敗時重新傳送該元組,以確保所有的元組都能得到正確的處理;相對應的,不可靠的 Spout 就不會在元組傳送之後對元組進行任何其他的處理。

一個 Spout 可以傳送多個資料流。為了實現這個功能,可以先通過 OutputFieldsDeclarer 的 declareStream 方法來宣告定義不同的資料流,然後在傳送資料時在 SpoutOutputCollector 的 emit 方法中將資料流 id 作為引數來實現資料傳送的功能。

Spout 中的關鍵方法是 nextTuple。顧名思義,nextTuple 要麼會向拓撲中傳送一個新的元組,要麼會在沒有可傳送的元組時直接返回。需要特別注意的是,由於 Storm 是在同一個執行緒中呼叫所有的 Spout 方法,nextTuple 不能被 Spout 的任何其他功能方法所阻塞,否則會直接導致資料流的中斷(關於這一點,阿里的 JStorm 修改了 Spout 的模型,使用不同的執行緒來處理訊息的傳送,這種做法有利有弊,好處在於可以更加靈活地實現 Spout,壞處在於系統的排程模型更加複雜,如何取捨還是要看具體的需求場景吧——譯者注)。

Spout 中另外兩個關鍵方法是 ack 和 fail,他們分別用於在 Storm 檢測到一個傳送過的元組已經被成功處理或處理失敗後的進一步處理。注意,ack 和 fail 方法僅僅對上述“可靠的” Spout 有效。

相關資料

資料流處理元件(Bolts)

拓撲中所有的資料處理均是由 Bolt 完成的。通過資料過濾(filtering)、函式處理(functions)、聚合(aggregations)、聯結(joins)、資料庫互動等功能,Bolt 幾乎能夠完成任何一種資料處理需求。

一個 Bolt 可以實現簡單的資料流轉換,而更復雜的資料流變換通常需要使用多個 Bolt 並通過多個步驟完成。例如,將一個微博資料流轉換成一個趨勢影像的資料流至少包含兩個步驟:其中一個 Bolt 用於對每個圖片的微博轉發進行滾動計數,另一個或多個 Bolt 將資料流輸出為“轉發最多的圖片”結果(相對於使用2個Bolt,如果使用3個 Bolt 你可以讓這種轉換具有更好的可擴充套件性)。

與 Spout 相同,Bolt 也可以輸出多個資料流。為了實現這個功能,可以先通過 OutputFieldsDeclarer 的 declareStream 方法來宣告定義不同的資料流,然後在傳送資料時在 OutputCollector 的 emit 方法中將資料流 id 作為引數來實現資料傳送的功能。

在定義 Bolt 的輸入資料流時,你需要從其他的 Storm 元件中訂閱指定的資料流。如果你需要從其他所有的元件中訂閱資料流,你就必須要在定義 Bolt 時分別註冊每一個元件。對於宣告為預設 id(即上文中提到的“default”——譯者注)的資料流,InputDeclarer支援訂閱此類資料流的語法糖。也就是說,如果需要訂閱來自元件“1”的資料流,declarer.shuffleGrouping("1") 與 declarer.shuffleGrouping("1", DEFAULT_STREAM_ID) 兩種宣告方式是等價的。

Bolt 的關鍵方法是 execute 方法。execute 方法負責接收一個元組作為輸入,並且使用 OutputCollector 物件傳送新的元組。如果有訊息可靠性保障的需求,Bolt 必須為它所處理的每個元組呼叫 OutputCollector 的 ack 方法,以便 Storm 能夠了解元組是否處理完成(並且最終決定是否可以響應最初的 Spout 輸出元組樹)。一般情況下,對於每個輸入元組,在處理之後可以根據需要選擇不傳送還是傳送多個新元組,然後再響應(ack)輸入元組。IBasicBolt 介面能夠實現元組的自動應答。

在 Bolt 中啟動新執行緒來進行非同步處理是一種非常好的方式,因為 OutputCollector 是執行緒安全的物件,可以在任意時刻被呼叫(此處譯者保留意見,由於 Storm 的併發設計和叢集的彈性擴充套件機制,在 Bolt 中新建的執行緒可能存在一定的不可控風險——譯者注)。

請注意 OutputCollector 不是執行緒安全的物件,所有的 emit、ack 和 fail 操作都需要在同一個執行緒中進行處理。更多資訊請參考問題與解決一文。

相關資料

資料流分組(Stream groupings)

為拓撲中的每個 Bolt 的確定輸入資料流是定義一個拓撲的重要環節。資料流分組定義了在 Bolt 的不同任務(tasks)中劃分資料流的方式。

在 Storm 中有八種內建的資料流分組方式(原文有誤,現在已經已經有八種分組模型——譯者注),而且你還可以通過CustomStreamGrouping 介面實現自定義的資料流分組模型。這八種分組分時分別為:

  1. 隨機分組(Shuffle grouping):這種方式下元組會被儘可能隨機地分配到 Bolt 的不同任務(tasks)中,使得每個任務所處理元組數量能夠能夠保持基本一致,以確保叢集的負載均衡。
  2. 域分組(Fields grouping):這種方式下資料流根據定義的“域”來進行分組。例如,如果某個資料流是基於一個名為“user-id”的域進行分組的,那麼所有包含相同的“user-id”的元組都會被分配到同一個任務中,這樣就可以確保訊息處理的一致性。
  3. 部分關鍵字分組(Partial Key grouping):這種方式與域分組很相似,根據定義的域來對資料流進行分組,不同的是,這種方式會考慮下游 Bolt 資料處理的均衡性問題,在輸入資料來源關鍵字不平衡時會有更好的效能1。感興趣的讀者可以參考這篇論文,其中詳細解釋了這種分組方式的工作原理以及它的優點。
  4. 完全分組(All grouping):這種方式下資料流會被同時傳送到 Bolt 的所有任務中(也就是說同一個元組會被複制多份然後被所有的任務處理),使用這種分組方式要特別小心。
  5. 全域性分組(Global grouping):這種方式下所有的資料流都會被髮送到 Bolt 的同一個任務中,也就是 id 最小的那個任務。
  6. 非分組(None grouping):使用這種方式說明你不關心資料流如何分組。目前這種方式的結果與隨機分組完全等效,不過未來 Storm 社群可能會考慮通過非分組方式來讓 Bolt 和它所訂閱的 Spout 或 Bolt 在同一個執行緒中執行。
  7. 直接分組(Direct grouping):這是一種特殊的分組方式。使用這種方式意味著元組的傳送者可以指定下游的哪個任務可以接收這個元組。只有在資料流被宣告為直接資料流時才能夠使用直接分組方式。使用直接資料流傳送元組需要使用 OutputCollector 的其中一個 emitDirect 方法。Bolt 可以通過 TopologyContext 來獲取它的下游消費者的任務 id,也可以通過跟蹤 OutputCollector 的 emit 方法(該方法會返回它所傳送元組的目標任務的 id)的資料來獲取任務 id。
  8. 本地或隨機分組(Local or shuffle grouping):如果在源元件的 worker 程式裡目標 Bolt 有一個或更多的任務執行緒,元組會被隨機分配到那些同程式的任務中。換句話說,這與隨機分組的方式具有相似的效果。

相關資料

  • TopologyBuilder:使用此類構造拓撲
  • InputDeclarer:在 TopologyBuilder 中呼叫 setBolt 方法時會返回這個物件的例項,通過該物件就可以定義 Bolt 的輸入資料流以及資料流的分組方式
  • CoordinatedBolt:這個 Bolt 主要用於分散式 RPC 拓撲,其中大量使用了直接資料流與直接分組模型

可靠性(Reliability)

Storm 可以通過拓撲來確保每個傳送的元組都能得到正確處理。通過跟蹤由 Spout 發出的每個元組構成的元組樹可以確定元組是否已經完成處理。每個拓撲都有一個“訊息延時”引數,如果 Storm 在延時時間內沒有檢測到元組是否處理完成,就會將該元組標記為處理失敗,並會在稍後重新傳送該元組。

為了充分利用 Storm 的可靠性機制,你必須在元組樹建立新結點的時候以及元組處理完成的時候通知 Storm。這個過程可以在 Bolt 傳送元組時通過 OutputCollector 實現:在 emit 方法中實現元組的錨定(Anchoring),同時使用 ack 方法表明你已經完成了元組的處理。

關於可靠性保障的更多內容可以參考這篇文章:訊息的可靠性處理

任務(Tasks)

在 Storm 叢集中每個 Spout 和 Bolt 都由若干個任務(tasks)來執行。每個任務都與一個執行執行緒相對應。資料流分組可以決定如何由一組任務向另一組任務傳送元組。你可以在 TopologyBuilder 的 setSpout 方法和 setBolt 方法中設定 Spout/Bolt 的並行度。

工作程式(Workers)

拓撲是在一個或多個工作程式(worker processes)中執行的。每個工作程式都是一個實際的 JVM 程式,並且執行拓撲的一個子集。例如,如果拓撲的並行度定義為300,工作程式數定義為50,那麼每個工作程式就會執行6個任務(程式內部的執行緒)。Storm 會在所有的 worker 中分散任務,以便實現叢集的負載均衡。

相關資料


1 Partial Key grouping 方式目前僅支援開發版,尚未加入 Storm 的正式發行版,不過可以通過 CustomStreamGrouping間接實現該分組功能,具體的實現可以參考 PartialKeyGrouping 原始碼


相關文章