Storm基礎(四)保證訊息處理
本人原創翻譯,轉載請註明出處
Storm提供了幾種不同級別的保證訊息處理機制,包括best effort, at least once, 通過Trident實現的exactly once。這篇文章描述了Storm如何保證at least once處理。
一個訊息被完全處理(fully processed)究竟是什麼意思?
一個tuple從spout中發出可能觸發成千上萬個tuples的建立。例如,單詞計數topology:
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout("sentences", new KestrelSpout("kestrel.backtype.com",
22133,
"sentence_queue",
new StringScheme()));
builder.setBolt("split", new SplitSentence(), 10)
.shuffleGrouping("sentences");
builder.setBolt("count", new WordCount(), 20)
.fieldsGrouping("split", new Fields("word"));
這個topology 從Kestrel佇列中讀取句子,把句子拆分成單片語,然後每次emit一個單詞(如果單詞重複出現,那麼出現多少次emit多少次)。這解釋了一個tuple如何導致n個tuples被建立:句子中的每個單詞,都會成為一個單詞tuple和一個更新單詞計數的tuple。訊息樹大概像這樣:
Storm定義一個從spout發出的tuple被完全處理,當且僅當tuple樹已經為空並且樹中的每個訊息都已被處理。如果tuple沒有在給定的超時時間(timeout)內被完全處理,就定義為處理失敗。timeout可以使用Config.TOPOLOGY_MESSAGE_TIMEOUT_SECS來配置,預設是30秒。
當一個訊息被完全處理或沒有被完全處理時發生了什麼?
為了理解這個問題,讓我們看看tuple從spout開始的生命週期。作為參考,這裡是spouts實現的介面:
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使用SpoutOutputCollector(在open函式中提供)來emit一個tuple到某個輸出流。當emitting tuple的時候,Spout設定了一個"message id",後續會用來識別tuple。舉個例子,KestrelSpout從kestrel佇列中讀取訊息,由Kestrel給出id並設定為"message id",然後emit。像這樣發出訊息:
_collector.emit(new Values("field1", "field2", 3) , msgId);
接下來,tuple被髮送給消費bolts,Storm負責維護訊息樹。如果Storm檢測到一個tuple被完全處理了,Storm會呼叫Spout的ack方法(攜帶引數message id)。同樣的,如果tuple處理超時,Storm將呼叫Spout的fail方法。注意,一個tuple只會被建立它的那個Spout任務acked或failed,如果Spout被叢集中的多個任務執行,tuple不會被非建立它的任務acked或failed。
再次以KestrelSpout為例來說明Spout如何保證訊息處理。當KestrelSpout從Kestrel佇列中取出一個訊息,它"opens"這個訊息,訊息並沒有真的從佇列中取下來,只是設定了一個掛起("pending")狀態,等待訊息處理完成的確認。處於掛起狀態的訊息不會被髮送給其他佇列消費者。此外,如果一個客戶端失去連線,它的所有掛起狀態的訊息會被放回佇列。KestrelSpout會給SpoutOutputCollector傳遞一個"message id"引數,稍後,KestrelSpout的ack和fail函式被呼叫,KestrelSpout會給Kestrel發一個帶"message id"的ack或fail訊息,進而將訊息移除或放回佇列。
什麼是Storm的可靠性API?
要想利用Storm的可靠效能力要做兩件事。首先,任何時候你在tuple樹中建立新的link都要通知Storm。其次,當你完成一個獨立tuple的處理時也要通知Storm。通過做這兩件事,Storm可以檢測tuple樹是否處理完畢並恰當的處理spout tuple的ack或fail。Storm的API提供了一種簡潔的方式來完成這些任務。
在tuple樹中指定一個link被稱作錨定(anchoring)。在你emit一個新的tuple時就同步完成了錨定。舉個例子,下面這個bolt把一個包含句子的tuple拆分成每個單詞的tuple:
public class SplitSentence extends BaseRichBolt {
OutputCollector _collector;
public void prepare(Map conf, TopologyContext context, OutputCollector collector) {
_collector = collector;
}
public void execute(Tuple tuple) {
String sentence = tuple.getString(0);
for(String word: sentence.split(" ")) {
_collector.emit(tuple, new Values(word));
}
_collector.ack(tuple);
}
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("word"));
}
}
每個單詞tuple通過指定輸入tuple為emit的第一個引數而錨定。由於單詞tuple已被錨定,在單詞tuple處理失敗的時候,tuple樹的根spout tuple將被重新傳輸。相反的,讓我們看看如果像這樣emit tuple會發生什麼:
_collector.emit(new Values(word));
這樣emit的單詞tuple沒有被錨定,如果tuple處理失敗,根tuple不會被重傳。取決於你的容錯需求,有時候以非錨定的方式emit tuple也是恰當的。
一個輸出tuple可以被錨定到多個輸入tuple,這對流連線或流聚合(streaming joins or aggregations)很有用。被多個輸入錨定的tuple處理失敗,會導致多個根tuple重傳。例子:
List<Tuple> anchors = new ArrayList<Tuple>();
anchors.add(tuple1);
anchors.add(tuple2);
_collector.emit(anchors, new Values(1, 2, 3));
多錨定(Multi-anchoring)將把輸出tuple加入到多個tuple樹。注意,這可能會破壞樹的結構並且建立tuple 有向無環圖(DAGs)。例如:
Storm的實現支援有向無環圖和樹。
錨定就是你如何說明tuple樹——下一個也是最後一個關於Storm可靠性API的點是當你處理完一個獨立的tuple時,如何說明tuple樹。通過呼叫OutputCollector的ack和fail來實現這個操作。如果你往回看例子SplitSentence,你會看到在所有單詞tuple被emit之後輸入tuple被確認了(acked)。
你可以使用OutputCollector 的fail方法來立即使根tuple(spout tuple)失敗。例如,你的應用也許會選擇捕獲資料庫客戶端的異常,顯式的使輸入tuple失敗。通過顯式的使tuple失敗,根tuple可以比等待超時更快的被重傳。
每個tuple都應該被ack或fail。Storm佔用了記憶體來跟蹤每一個tuple,如果不ack/fail每個tuple,任務可能最終會耗盡記憶體。
許多bolts使用了一種通用模式來讀取和發出輸入tuple,在execute方法的最後ack tuple。這些bolts歸類為過濾器和簡單函式(filters and simple functions)。Storm提供了一個BasicBolt介面封裝了這種模式,SplitSentence例子可以用BasicBolt實現:
public class SplitSentence extends BaseBasicBolt {
public void execute(Tuple tuple, BasicOutputCollector collector) {
String sentence = tuple.getString(0);
for(String word: sentence.split(" ")) {
collector.emit(new Values(word));
}
}
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("word"));
}
}
這種實現比之前的實現簡單,語義上一致。Tuples自動錨定到輸入tuple,execute方法完成時自動ack。
相反,實現聚合和連線的bolts可能會延遲ack,直到一組tuples處理完畢。聚合和連線一般也會多錨定(multi-anchor),IBasicBolt不能自動做這些。
如果tuples可以重傳,程式該如何正確工作?
軟體設計的一貫答案是“取決於”。如果你一定要一個答案,考慮使用Trident API。某些情況下,如要做很多分析並且可以容忍丟失資料,那麼可以通過設定acker bolts為0(Config.TOPOLOGY_ACKERS)來禁用容錯。但在有些情況下,你想要確保每個資料都被至少處理了一次並且沒有丟失。
Storm如何有效的實現可靠性?
Storm的topology有一些特殊的“acker”任務,負責追蹤每個spout tuple的tuples DAG,一旦acker發現DAG完成了,它就會發一個確認訊息給spout。你可以通過Config.TOPOLOGY_ACKERS設定acker任務的數量。Storm預設是每個worker有一個acker。
不管是spout還是bolt發出的tuple都有一個64位的id。每個tuple都知道tuple樹中的所有spout tuples的ids。當你發出一個新tuple時,老的tuple錨定的spout tuples ids被複制到新的tuple。當一個tuple被確認了,它會發一個tuple樹如何變更的訊息給acker任務,特別地,訊息可能像這樣:“我已經完成了tuple樹中這個spout tuple的處理,樹中有一些新的tuples以我為錨”。
例如,如果tuples “D”和“E”是基於tuple “C”而建立,當“C”確認時,tuple樹的變化如下:
由於在“D”和“E”建立的同時,“C”被從樹中移除了,樹永遠不會過早的(prematurely)完成。注:這句不是很理解
還有一些細節要提一下。之前提到可以有多個acker任務,那麼當一個tuple被確認時,如何知道由哪一個任務傳送確認資訊?
Storm使用mod hashing來對映spout tuple id到acker任務。由於每個tuple都攜帶了它所在所有樹中的spout tuple ids,因此知道該與哪個acker任務通訊。
另一個細節是acker任務如何跟蹤spout任務。當spout task發出一個新tuple,它只是簡單的傳送訊息到恰當的acker,告訴它為這個spout tuple負責。之後當一個acker發現樹已經完成,它就知道該給哪個任務id發完成資訊。
acker任務不會顯式追蹤tuples樹。對於有好幾萬節點(甚至更多)的大tuple樹,跟蹤所有的tuple樹可能會造成記憶體不夠用。ackers採用一種策略,對每個spout tuple只要求固定數量的記憶體(大約20位元組)。這個追蹤演算法是理解Storm工作的關鍵,也是Storm主要的突破之一。
acker任務儲存了一個spout tuple到一組值的map。第一個值是任務id,用來傳送完成資訊。第二個值是64位數字,名為“ack val”,這個值代表了整個tuple樹的狀態,無論樹多大多小。它只是簡單的把樹中所有已建立或確認的tuple ids做xor運算。
當一個acker任務發現“ack val”變成了0,它就知道tuple樹完成了。由於tuple ids是64位隨機數,“ack val”意外變成0的概率極小。用數學知識算一下,每秒10K個acks,大概要花50,000,000年才會發生一個錯誤。即使發生錯誤,也只是丟失資料。
現在你理解了可靠性演算法,讓我們過一遍失敗的情形,看看每種情形下Storm如何避免資料丟失:
- 由於任務異常終止,tuple未被確認:這種情況下失敗tuple的樹根處的spout tuple將超時並重發。
- acker任務異常終止:這種情況下,所有這個akcer跟蹤的spout tuples都會超時並重發。
- spout任務異常終止:這種情況下,spout的源負責重發訊息。例如,客戶端失去連線時,像Kestrel和RabbitMQ這樣的佇列將把掛起的訊息重新放回佇列。
如你所見,Storm的可靠性機制是完全分散式、大規模和容錯的。
調教reliability
acker任務是輕量級的,所以在一個topology裡不需要很多個acker。你可以通過Storm UI(元件id“__acker”)跟蹤acker的效能,如果吞吐量不行,可以增加acker的數量。
如果可靠性對你不重要——你不關心丟失tuples,那麼你可以通過不追蹤tuple樹來增加效能。不追蹤tuple樹可以減半訊息傳輸的數量,另外,下游的tuple可以儲存更少的ids,節省了網路頻寬。
有三種方式可以移除可靠性。第一種是設定Config.TOPOLOGY_ACKERS為0。這種情況下,Storm會在spout發出tuple時立即呼叫ack方法。
第二種是移除訊息上的可靠性。你可以在呼叫SpoutOutputCollector.emit方法的時候不傳訊息id,這樣就關閉了對某個spout tuple的追蹤。
最後,如果你不關心下游tuples是否處理失敗,你可以在emit它們的時候不錨定它們。由於它們沒有錨定到任何spout tuples上,它們沒被確認不會導致任何spout tuples失敗。
相關文章
- Storm確保訊息被消費ORM
- 【scipy 基礎】--訊號處理
- MATLAB及其訊號處理基礎Matlab
- 訊息佇列-如何保證訊息的不被重複消費(如何保證訊息消費的冪等性)佇列
- 訊息佇列之如何保證訊息的可靠傳輸佇列
- 雲原生賦能智慧網聯汽車訊息處理基礎框架構建框架架構
- 關於MQ的幾件小事(四)如何保證訊息不丟失MQ
- MPLS RSVP訊息處理——VecloudCloud
- Kafka如何保證訊息不丟之無訊息丟失配置Kafka
- 分散式訊息佇列:如何保證訊息的順序性分散式佇列
- RabbitMQ使用教程(四)如何通過持久化保證訊息99.99%不丟失?MQ持久化
- RabbitMQ-如何保證訊息不丟失MQ
- 《RabbitMQ》如何保證訊息的可靠性MQ
- 如何保證訊息不被重複消費
- RabbitMQ如何保證訊息的可達性MQ
- Redis 使用 List 實現訊息佇列能保證訊息可靠麼?Redis佇列
- .net core 訊息流處理流程
- 如何處理錯誤訊息PleaseinstalltheLinuxkernelheaderfilesLinuxHeader
- 流式處理框架storm淺析(下篇)框架ORM
- 《RabbitMQ》如何保證訊息不被重複消費MQ
- 如何保證訊息佇列的順序性?佇列
- 二、如何保證訊息佇列的高可用?佇列
- 【scipy 基礎】--影像處理
- 如何使用JSR303驗證及自定義訊息統一處理JS
- 如何處理RabbitMQ 訊息堆積和訊息丟失問題MQ
- RabbitMQ高階之如何保證訊息可靠性?MQ
- 訊息佇列學習基礎佇列
- C#基礎之前處理器,異常處理C#
- 處理器基礎知識
- Python基礎 -- 異常處理Python
- 影像處理基礎篇(一)
- [資料處理]python基礎Python
- java從SQS訂閱訊息 的demo, 要求保證訊息可靠投遞的例子Java
- Android應用程式訊息處理機制Android
- 原始碼分析:Android訊息處理機制原始碼Android
- KafkaConsumer對於事務訊息的處理Kafka
- 使用訊息中介軟體時,如何保證訊息僅僅被消費一次?
- MQ系列10:如何保證訊息冪等性消費MQ
- 如何保證訊息佇列的可靠性傳輸?佇列