流式計算之Storm簡介

轉載地址:

http://blog.sina.com.cn/s/blog_406d9bb00100ui5p.html


Storm是一個分佈式的、容錯的實時計算系統,遵循Eclipse Public License 1.0,Storm可以方便地在一個計算機集羣中編寫與擴展複雜的實時計算,Storm之於實時處理,就好比Hadoop之於批處理。Storm保證每個消息都會得到處理,而且它很快——在一個小集羣中,每秒可以處理數以百萬計的消息。可以使用任意編程語言來做開發。
主要商業應用及案例:Twitter
Storm的優點
1. 簡單的編程模型。類似於MapReduce降低了並行批處理複雜性,Storm降低了進行實時處理的複雜性。
2. 服務化,一個服務框架,支持熱部署,即時上線或下線App.
3. 可以使用各種編程語言。你可以在Storm之上使用各種編程語言。默認支持Clojure、Java、Ruby和Python。要增加對其他語言的支持,只需實現一個簡單的Storm通信協議即可。
4. 容錯性。Storm會管理工作進程和節點的故障。
5. 水平擴展。計算是在多個線程、進程和服務器之間並行進行的。
6. 可靠的消息處理。Storm保證每個消息至少能得到一次完整處理。任務失敗時,它會負責從消息源重試消息。
7. 快速。系統的設計保證了消息能得到快速的處理,使用ZeroMQ作爲其底層消息隊列。
8. 本地模式。Storm有一個“本地模式”,可以在處理過程中完全模擬Storm集羣。這讓你可以快速進行開發和單元測試。


Storm目前存在的問題

1. 目前的開源版本中只是單節點Nimbus,掛掉只能自動重啓,可以考慮實現一個雙nimbus的佈局。
2. Clojure
是一個在JVM平臺運行的動態函數式編程語言,優勢在於流程計算, Storm的部分核心內容由Clojure編寫,雖然性能上提高不少但同時也提升了維護成本。

Storm架構

Storm集羣由一個主節點和多個工作節點組成。主節點運行了一個名爲“Nimbus”的守護進程,用於分配代碼、佈置任務及故障檢測。每個工作節點都運行了一個名爲“Supervisor”的守護進程,用於監聽工作,開始並終止工作進程。NimbusSupervisor都能快速失敗,而且是無狀態的,這樣一來它們就變得十分健壯,兩者的協調工作是由Zookeeper來完成的。ZooKeeper用於管理集羣中的不同組件,ZeroMQ是內部消息系統,JZMQZeroMQMQJava Binding。有個名爲storm-deploy的子項目,可以在AWS上一鍵部署Storm集羣.

http://b2b-doc.alibaba-inc.com/download/attachments/59153862/cluster.jpg?version=1&modificationDate=1318936760000

Storm術語解釋

Storm的術語包括StreamSpoutBoltTaskWorkerStream GroupingTopologyStream是被處理的數據。Sprout是數據源。Bolt處理數據。Task是運行於SpoutBolt中的 線程。Worker是運行這些線程的進程。Stream Grouping規定了Bolt接收什麼東西作爲輸入數據。數據可以隨機分配(術語爲Shuffle),或者根據字段值分配(術語爲Fields),或者 廣播(術語爲All),或者總是發給一個Task(術語爲Global),也可以不關心該數據(術語爲None),或者由自定義邏輯來決定(術語爲Direct)。Topology是由Stream Grouping連接起來的SpoutBolt節點網絡.下面進行詳細介紹:

  • Topologies 用於封裝一個實時計算應用程序的邏輯,類似於HadoopMapReduce Job

http://b2b-doc.alibaba-inc.com/download/attachments/59153862/topo.jpg?version=1&modificationDate=1318936822000

  • Stream 消息流,是一個沒有邊界的tuple序列,這些tuples會被以一種分佈式的方式並行地創建和處理
  • Spouts 消息源,是消息生產者,他會從一個外部源讀取數據並向topology裏面面發出消息:tuple
  • Bolts 消息處理者,所有的消息處理邏輯被封裝在bolts裏面,處理輸入的數據流併產生輸出的新數據流,可執行過濾,聚合,查詢數據庫等操作

http://b2b-doc.alibaba-inc.com/download/attachments/59153862/Bolts.jpg?version=1&modificationDate=1318936793000

  • Task 每一個SpoutBolt會被當作很多task在整個集羣裏面執行,每一個task對應到一個線程.

http://b2b-doc.alibaba-inc.com/download/attachments/59153862/task.jpg?version=1&modificationDate=1318936806000

  • Stream groupings 消息分發策略,定義一個Topology的其中一步是定義每個tuple接受什麼樣的流作爲輸入,stream grouping就是用來定義一個stream應該如果分配給Bolts.

stream grouping分類

1. Shuffle Grouping: 隨機分組, 隨機派發stream裏面的tuple 保證每個bolt接收到的tuple數目相同.
2. Fields Grouping
:按字段分組, 比如按userid來分組, 具有同樣useridtuple會被分到相同的Bolts 而不同的userid則會被分配到不同的Bolts.
3. All Grouping
 廣播發送, 對於每一個tuple 所有的Bolts都會收到.
4. Global Grouping:
 全局分組,這個tuple被分配到storm中的一個bolt的其中一個task.再具體一點就是分配給id值最低的那個task.
5. Non Grouping:
 不分組,意思是說stream不關心到底誰會收到它的tuple.目前他和Shuffle grouping是一樣的效果,有點不同的是storm會把這個bolt放到這個bolt的訂閱者同一個線程去執行.
6. Direct Grouping:
 直接分組,這是一種比較特別的分組方法,用這種分組意味着消息的發送者舉鼎由消息接收者的哪個task處理這個消息.只有被聲明爲Direct Stream的消息流可以聲明這種分組方法.而且這種消息tuple必須使用emitDirect方法來發射.消息處理者可以通過TopologyContext來或者處理它的消息的taskid (OutputCollector.emit方法也會返回taskid)

Storm如何保證消息被處理

storm保證每個tuple會被topology完整的執行。storm會追蹤由每個spout tuple所產生的tuple(一個bolt處理一個tuple之後可能會發射別的tuple從而可以形成樹狀結構), 並且跟蹤這棵tuple樹什麼時候成功處理完。每個topology都有一個消息超時的設置, 如果storm在這個超時的時間內檢測不到某個tuple樹到底有沒有執行成功, 那麼topology會把這個tuple標記爲執行失敗,並且過一會會重新發射這個tuple

一個tuple能根據新獲取到的spout而觸發創建基於此的上千個tuple

TopologyBuilder builder = new TopologyBuilder();

builder.setSpout(1, new KestrelSpout("kestrel.backtype.com",

                                     22133,

                                     "sentence_queue",

                                     new StringScheme()));

builder.setBolt(2, new SplitSentence(), 10)

        .shuffleGrouping(1);

builder.setBolt(3, new WordCount(), 20)

        .fieldsGrouping(2, new Fields("word"));

這個topologykestrel queue讀取句子,並把句子劃分成單詞,然後彙總每個單詞出現的次數,一個tuple負責讀取句子,每一個tuple分別對應計算每一個單詞出現的次數,大概樣子如下所示:

http://b2b-doc.alibaba-inc.com/download/attachments/59153862/wordcount.jpg?version=2&modificationDate=1318936897000

一個tuple的生命週期:

public interface ISpout extends Serializable {

    void open(Map conf, TopologyContext context, SpoutOutputCollector collector);

    void close();

    void nextTuple();

    void ack(Object msgId);

    void fail(Object msgId);

}

首先storm通過調用spoutnextTuple方法來獲取下一個tuple, Spout通過open方法參數裏面提供的SpoutOutputCollector來發射新tuple到它的其中一個輸出消息流, 發射tuple的時候spout會提供一個message-id, 後面我們通過這個tuple-id來追蹤這個tuple。舉例來說, KestrelSpoutkestrel隊列裏面讀取一個消息,並且把kestrel提供的消息id作爲message-id, 看例子:

collector.emit(new Values("field1", "field2", 3) , msgId);

 

接下來, 這個發射的tuple被傳送到消息處理者bolt那裏, storm會跟蹤這個消息的樹形結構是否創建,根據messageid調用Spout裏面的ack函數以確認tuple是否被完全處理。如果tuple超時就會調用spoutfail方法。由此看出同一個tuple不管是acked還是fail都是由創建他的那個spout發出的,所以即使spout在集羣環境中執行了很多的task,這個tule也不會被不同的taskackedfailed.
kestrelspoutkestrel隊列中得到一個消息後會打開這個他,這意味着他並不會把此消息拿走,消息的狀態會顯示爲pending,直到等待確認此消息已經處理完成,處於pending狀態直到ack或者fail被調用,處於"Pending"的消息不會再被其他隊列消費者使用.如果在這過程中spout中處理此消息的task斷開連接或失去響應則此pending的消息會回到"等待處理"狀態.

Storm的一些常用應用場景

1.流聚合
流聚合把兩個或者多個數據流聚合成一個數據流  基於一些共同的tuple字段。

builder.setBolt(5, new MyJoiner(), parallelism)

  .fieldsGrouping(1, new Fields("joinfield1", "joinfield2"))

  .fieldsGrouping(2, new Fields("joinfield1", "joinfield2"))

  .fieldsGrouping(3, new Fields("joinfield1", "joinfield2"))

 

2.批處理
有時候爲了性能或者一些別的原因, 你可能想把一組tuple一起處理, 而不是一個個單獨處理。

3.BasicBolt
1.
 讀一個輸入tuple
2.
 根據這個輸入tuple發射一個或者多個tuple
3.
 execute的方法的最後ack那個輸入tuple
遵循這類模式的bolt一般是函數或者是過濾器, 這種模式太常見,storm爲這類模式單獨封裝了一個接口: IbasicBolt

4.內存內緩存+Fields grouping組合
bolt的內存裏面緩存一些東西非常常見。緩存在和fields grouping結合起來之後就更有用了。比如,你有一個bolt把短鏈接變成長鏈接(bit.ly, t.co之類的)。你可以把短鏈接到長鏈接的對應關係利用LRU算法緩存在內存裏面以避免重複計算。比如組件一發射短鏈接,組件二把短鏈接轉化成長鏈接並緩存在內存裏面。看一下下面兩段代碼有什麼不一樣:

builder.setBolt(2, new ExpandUrl(), parallelism)

  .shuffleGrouping(1);

builder.setBolt(2, new ExpandUrl(), parallelism)

  .fieldsGrouping(1, new Fields("url"));

5.計算top N
比如你有一個bolt發射這樣的tuple: "value", "count"並且你想一個bolt基於這些信息算出top Ntuple。最簡單的辦法是有一個bolt可以做一個全局的grouping的動作並且在內存裏面保持這top N的值。
這個方式對於大數據量的流顯然是沒有擴展性的, 因爲所有的數據會被髮到同一臺機器。一個更好的方法是在多臺機器上面並行的計算這個流每一部分的top N, 然後再有一個bolt合併這些機器上面所算出來的top N以算出最後的top N, 代碼大概是這樣的:

builder.setBolt(2, new RankObjects(), parallellism)

  .fieldsGrouping(1, new Fields("value"));

builder.setBolt(3, new MergeObjects())

  .globalGrouping(2);

這個模式之所以可以成功是因爲第一個boltfields grouping使得這種並行算法在語義上是正確的。
TimeCacheMap來高效地保存一個最近被更新的對象的緩存

6.TimeCacheMap來高效地保存一個最近被更新的對象的緩存
有時候你想在內存裏面保存一些最近活躍的對象,以及那些不再活躍的對象。 TimeCacheMap 是一個非常高效的數據結構,它提供了一些callback函數使得我們在對象不再活躍的時候我們可以做一些事情.

7.分佈式RPC:CoordinatedBoltKeyedFairBolt
storm做分佈式RPC應用的時候有兩種比較常見的模式:它們被封裝在CoordinatedBoltKeyedFairBolt裏面. CoordinatedBolt包裝你的bolt,並且確定什麼時候你的bolt已經接收到所有的tuple,它主要使用Direct Stream來做這個.
KeyedFairBolt
同樣包裝你的bolt並且保證你的topology同時處理多個DRPC調用,而不是串行地一次只執行一個。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章