轉載地址:
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集羣由一個主節點和多個工作節點組成。主節點運行了一個名爲“Nimbus”的守護進程,用於分配代碼、佈置任務及故障檢測。每個工作節點都運行了一個名爲“Supervisor”的守護進程,用於監聽工作,開始並終止工作進程。Nimbus和Supervisor都能快速失敗,而且是無狀態的,這樣一來它們就變得十分健壯,兩者的協調工作是由Zookeeper來完成的。ZooKeeper用於管理集羣中的不同組件,ZeroMQ是內部消息系統,JZMQ是ZeroMQMQ的Java Binding。有個名爲storm-deploy的子項目,可以在AWS上一鍵部署Storm集羣.
Storm的術語包括Stream、Spout、Bolt、Task、Worker、Stream Grouping和Topology。Stream是被處理的數據。Sprout是數據源。Bolt處理數據。Task是運行於Spout或Bolt中的 線程。Worker是運行這些線程的進程。Stream Grouping規定了Bolt接收什麼東西作爲輸入數據。數據可以隨機分配(術語爲Shuffle),或者根據字段值分配(術語爲Fields),或者 廣播(術語爲All),或者總是發給一個Task(術語爲Global),也可以不關心該數據(術語爲None),或者由自定義邏輯來決定(術語爲Direct)。Topology是由Stream Grouping連接起來的Spout和Bolt節點網絡.下面進行詳細介紹:
- Topologies 用於封裝一個實時計算應用程序的邏輯,類似於Hadoop的MapReduce Job
- Stream 消息流,是一個沒有邊界的tuple序列,這些tuples會被以一種分佈式的方式並行地創建和處理
- Spouts 消息源,是消息生產者,他會從一個外部源讀取數據並向topology裏面面發出消息:tuple
- Bolts 消息處理者,所有的消息處理邏輯被封裝在bolts裏面,處理輸入的數據流併產生輸出的新數據流,可執行過濾,聚合,查詢數據庫等操作
- Task 每一個Spout和Bolt會被當作很多task在整個集羣裏面執行,每一個task對應到一個線程.
- Stream groupings 消息分發策略,定義一個Topology的其中一步是定義每個tuple接受什麼樣的流作爲輸入,stream grouping就是用來定義一個stream應該如果分配給Bolts們.
stream grouping分類
1. Shuffle Grouping: 隨機分組, 隨機派發stream裏面的tuple, 保證每個bolt接收到的tuple數目相同.
2. Fields Grouping:按字段分組, 比如按userid來分組, 具有同樣userid的tuple會被分到相同的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"));
這個topology從kestrel queue讀取句子,並把句子劃分成單詞,然後彙總每個單詞出現的次數,一個tuple負責讀取句子,每一個tuple分別對應計算每一個單詞出現的次數,大概樣子如下所示:
一個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通過調用spout的nextTuple方法來獲取下一個tuple, Spout通過open方法參數裏面提供的SpoutOutputCollector來發射新tuple到它的其中一個輸出消息流, 發射tuple的時候spout會提供一個message-id, 後面我們通過這個tuple-id來追蹤這個tuple。舉例來說, KestrelSpout從kestrel隊列裏面讀取一個消息,並且把kestrel提供的消息id作爲message-id, 看例子:
collector.emit(new Values("field1", "field2", 3) , msgId);
接下來, 這個發射的tuple被傳送到消息處理者bolt那裏, storm會跟蹤這個消息的樹形結構是否創建,根據messageid調用Spout裏面的ack函數以確認tuple是否被完全處理。如果tuple超時就會調用spout的fail方法。由此看出同一個tuple不管是acked還是fail都是由創建他的那個spout發出的,所以即使spout在集羣環境中執行了很多的task,這個tule也不會被不同的task去acked或failed.
當kestrelspout從kestrel隊列中得到一個消息後會打開這個他,這意味着他並不會把此消息拿走,消息的狀態會顯示爲pending,直到等待確認此消息已經處理完成,處於pending狀態直到ack或者fail被調用,處於"Pending"的消息不會再被其他隊列消費者使用.如果在這過程中spout中處理此消息的task斷開連接或失去響應則此pending的消息會回到"等待處理"狀態.
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
N的tuple。最簡單的辦法是有一個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);
這個模式之所以可以成功是因爲第一個bolt的fields
grouping使得這種並行算法在語義上是正確的。
用TimeCacheMap來高效地保存一個最近被更新的對象的緩存
6.用TimeCacheMap來高效地保存一個最近被更新的對象的緩存
有時候你想在內存裏面保存一些最近活躍的對象,以及那些不再活躍的對象。 TimeCacheMap 是一個非常高效的數據結構,它提供了一些callback函數使得我們在對象不再活躍的時候我們可以做一些事情.
7.分佈式RPC:CoordinatedBolt和KeyedFairBolt
用storm做分佈式RPC應用的時候有兩種比較常見的模式:它們被封裝在CoordinatedBolt和KeyedFairBolt裏面.
CoordinatedBolt包裝你的bolt,並且確定什麼時候你的bolt已經接收到所有的tuple,它主要使用Direct
Stream來做這個.
KeyedFairBolt同樣包裝你的bolt並且保證你的topology同時處理多個DRPC調用,而不是串行地一次只執行一個。