ApacheStorm官方文件——常用模式

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

本文列出了 Storm 拓撲中使用的一些常見模式,包括:

  1. 資料流的 join
  2. 批處理
  3. BasicBolt
  4. 記憶體快取與域分組的結合
  5. Top N 流式計算
  6. TimeCacheMap
  7. CoordinatedBolt 與 KeyedFairBolt

Joins

資料流的 join 一般指的是通過共有的域來聚合兩個或多個資料流的過程。與一般的資料庫中 join 操作要求有限的輸入與清晰的語義不同,資料流 join 的輸入往往是無限的資料集,而且並不具備明確的語義。

join 的型別一般是由應用的需求決定的。有些應用需要將兩個流在某個固定時間內的所有 tuple 進行 join,另外一些應用卻可能要求對每個 join 域的 join 操作過程的兩側只保留一個 tuple,而其他的應用也許還有一些其他需求。不過這些 join 型別一般都會有一個基本的模式,那就是將多個輸入流進行分割槽。Storm 可以很容易地使用域分組的方法將多個輸入流聚集到一個聯結 bolt 中,比如下面這樣:

builder.setBolt("join", new MyJoiner(), parallelism)
  .fieldsGrouping("1", new Fields("joinfield1", "joinfield2"))
  .fieldsGrouping("2", new Fields("joinfield1", "joinfield2"))
  .fieldsGrouping("3", new Fields("joinfield1", "joinfield2"));

當然,上面的程式碼只是個例子,實際上不同的流完全可以具有不同的輸入域。

批處理

通常由於效率或者其他方面的原因,你需要使用將 tuple 們組合成 batch 來處理,而不是一個個分別處理它們。比如,在做資料庫更新操作或者流聚合操作時,你就會需要這樣的批處理形式。

要確保資料處理的可靠性,正確的方式是在 bolt 進行批處理之前將 tuple 們快取在一個例項變數中。在完成批處理操作之後,你就可以一起 ack 所有的快取的 tuple 了。

如果這個批處理 bolt 還需要繼續向下遊傳送 tuple,你可能還需要使用多錨定(multi-anchoring)來確保可靠性。具體怎麼做取決於應用的需求。想要了解更多關於可靠性的工作機制的內容請參考訊息的可靠性保障一文。

BasicBolt

Bolt 處理 tuple 的一種基本模式是在 execute 方法中讀取輸入 tuple、傳送出基於輸入 tuple 的新 tuple,然後在方法末尾對 tuple 進行應答(ack)。符合這種模式的 bolt 一般是一種函式或者過濾器。對於這種基本的處理模式,Storm 提供了IBasicBolt 介面來自動實現這個過程。更多內容請參考訊息的可靠性保障一文。

記憶體快取與域分組的結合

在 Storm 的 bolt 中儲存一定的快取也是一種比較常見的方式。尤其是在於域分組結合的時候,快取的作用特別顯著。例如,假如你有一個用於將短連結(short URLs,例如 bit.ly, t.co,等等)轉化成長連結(龍 URLs)的 bolt。你可以通過一個將短連結對映到長連結的 LRU 快取來提高系統的效能,避免反覆的 HTTP 請求操作。假如現在有一個名為 “urls” 的元件用於傳送短連結,另外有一個 “expand” 元件用於將短連結擴充套件為長連結,並且在 “expand” 內部保留一個快取。讓我們來看看下面兩段程式碼有什麼不同:

builder.setBolt("expand", new ExpandUrl(), parallelism)
  .shuffleGrouping(1);
builder.setBolt("expand", new ExpandUrl(), parallelism)
  .fieldsGrouping("urls", new Fields("url"));

由於域分組可以使得相同的 URL 永遠被髮往同一個 task,第二段程式碼會比第一段程式碼高效得多。這樣可以避免在不同的 task 的快取中的複製動作,並且看上去短 URL 可以更好地在命中快取。

Top N

Storm 中一種常見的連續計算模式是計算資料流中某種形式的 Top N 結果。假如現在有一個可以以 [“value”, “count”] 的形式傳送 tuple 的 bolt,並且你需要一個可以根據 count 計算結果輸出前 N 個 tuple 的 bolt。實現這個操作的最簡單的方法就是使用一個對資料流進行全域性分組的 bolt,並且在記憶體中維護一個包含 top N 結果的列表。

這種方法並不適用於大規模資料流,因為整個資料流都會發往同一個 task,會造成該 task 的記憶體負載過高。更好的做法是將資料流分割槽,同時對每個分割槽計算 top N 結果,然後將這些結果彙總來得到最終的全域性 top N 結果。下面是這個模式的程式碼:

builder.setBolt("rank", new RankObjects(), parallelism)
  .fieldsGrouping("objects", new Fields("value"));
builder.setBolt("merge", new MergeObjects())
  .globalGrouping("rank");

這個方法之所以可行是因為第一個 bolt 的域分組操作確保了每個小分割槽在語義上的正確性。你可以在 storm-starter 裡看到使用這個模式的一個例子。

當然,如果待處理的資料集存在較嚴重的資料傾斜,那麼還是應該使用 partialKeyGrouping 來代替 fieldsGrouping,因為 partialKeyGrouping 可以通過兩個下游 bolt 分散每個 key 的負載。

builder.setBolt("count", new CountObjects(), parallelism)
  .partialKeyGrouping("objects", new Fields("value"));
builder.setBolt("rank" new AggregateCountsAndRank(), parallelism)
  .fieldsGrouping("count", new Fields("key"))
builder.setBolt("merge", new MergeRanksObjects())
  .globalGrouping("rank");

這個拓撲中需要一箇中間層來聚合來自上游 bolt 資料流的分割槽計數結果,但這一層僅僅會做一個簡單的聚合處理,這樣 bolt 就不會受到由於資料傾斜帶來的負載壓力。你可以在 storm-starter 中看到使用這個模式的一個例子。

支援 LRU 的 TimeCacheMap

有時候你可能會需要一個能夠保留“活躍的”資料並且能夠使得超時的“非活躍的”資料自動失效的快取。TimeCacheMap 是一個可以高效地實現此功能的資料結構。它還提供了一個鉤子用於實現在資料失效後的回撥操作。

用於分散式 RPC 的 CoordinatedBolt 與 KeyedFairBolt

在構建 Storm 上層的分散式 RPC 應用時,通常會用到兩種常用的模式。現在這兩種模式已經被封裝為 CoordinatedBolt 和KeyedFairBolt,並且已經加入了 Storm 標準庫中。

CoordinatedBolt 將你的處理邏輯 bolt 包裝起來,並且在你的 bolt 收到了指定請求的所有 tuple 之後發出通知。CoordinatedBolt 中大量使用了直接資料流組來實現此功能。

KeyedFairBolt 同樣包裝了你的處理邏輯 bolt,並且可以讓你的拓撲同時處理多個 DRPC 呼叫,而不是每次只執行一個。

如果需要了解更多內容請參考分散式 RPC一文。


相關文章