三、訊息的可靠處理

fan_rockrock發表於2015-12-28

1、訊息被完全處理的含義


當樹建立完畢,並且樹中的每一個訊息都已經被處理時,Storm認為來自Spout的元組是“完全處理”的。當一個元組的訊息樹在指定的超時範圍內不能被完全處理,則元組被認為是失敗的。


2、如果一個訊息被完全處理或完全處理失敗會發生什麼

首先,讓我們看看Spout的元組的生命週期。ISpout介面的定義如下:

public interface ISpout extends Serializable {
	void open(Map conf, TopologyContext context, SpoutOutputCollector colle- ctor);
	void close();
	void nextTuple();
	void ack(Object msgId);
	void fail(Object msgId);
}

首先,Storm通過呼叫Spout的nextTuple()方法從Spout請求一個元組。Spout使用open()方法提供的SpoutOutputCollector物件發射一個元組到它的輸出流。當發射元組時,Spout會提供一個“訊息id”,以便用來識別元組。例如,KestrelSpout從Kestrel訊息佇列中讀取一個訊息時,會發射Kestrel提供的“訊息id”。下面發射一個訊息到SpoutOutputCollector物件:

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

接下來,元組被髮送到Bolt,同時Storm負責跟蹤建立的訊息樹。如果Storm檢測到一個元組是完全處理的,Storm將呼叫原Spout任務的ack()方法,把Spout提供給Storm的訊息id作為輸入引數。同樣,如果元組超時,Storm將呼叫Spoutfail()方法。注意,一個元組將由Spout任務來確認成功或失敗,這個Spout任務是建立此元組的完全相同的Spout任務。如果一個Spout跨叢集執行很多工,元組是不會被建立它的那個任務外的其他任務確認成功或失敗的。

注意:bolt是沒有ack()和fail()函式的,任何訊息出錯了,都是由根spout重發


3、Storm如何保證可靠性

在元組樹中指定一個連結,此連結被稱為錨定(Anchoring。Anchoring在發射一個新的元組的同一時間完成。讓我們使用以下Bolt為例進行介紹,這個Bolt將包含一個句子的元組劃分為一個包含每個單詞的錨定:

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"));
	}
}<

通過指定輸入元組作為第一個引數來發射每個單詞元組被錨定anchored。因為這個單詞元組是被錨定的如果單詞元組未能被下游處理,樹的根的Spout元組將在稍後重發。相反,如果單詞元組的發射操作如下,讓我們看看會發生什麼:

_collector.emit(new Values(word));

這種方式發射的單詞元組導致未被錨定unanchored。如果元組未被下游處理,根元組將不會重發。這取決於你需要的Topology的容錯保證,有時候需要相應地發射一個未被錨定的元組。

很多Bolt遵循一個讀取一個輸入元組,發射元組,在execute方法確認元組的通用模式。這些Bolt具有類別過濾器和簡單的功能。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"));
	}        
}

這個實現與之前的實現相比,語義上是相同的,但是更簡單。元組發射到BasicOutputCollectorare是自動Anchoring到輸入元組execute方法完成時,輸入元組自動為你確認。


4、Storm如何實現可靠性

       storm系統中有一組叫做acker(可以設定並行度為多個)的特殊任務,它負責跟蹤DAG中的每個訊息。每當發現一個DAG被完全處理,它就向建立這個根訊息的Spout任務傳送一個訊號,該tuple tree已經被完全處理成功。

      系統使用一種雜湊演算法根據Spout訊息的id來確定由哪個acker跟蹤此訊息派生出來的tuple tree。因為每個訊息都知道與之對應的根訊息的id(每當Bolt新生成一個訊息,對應的tuple tree中的根訊息id就複製到這個訊息中),所以它知道應該與哪個acker通訊。當這個訊息被應答的時候,它就把關於tuple tree變化的資訊傳送給跟蹤這棵樹的acker。例如,它會告訴acker:“本訊息已經處理完畢,但是我派生出來了一些新的訊息,幫忙跟蹤一下吧”

       一個Acker任務儲存來自Spout元組id到一對值的對映。第一個值是建立Spout元組的任務id,通過這個ID,acker就知道訊息處理完成時該通知哪個spout任務。第二個值是一個64位的值,稱為ack val。它是所有訊息的隨機id的異或結果當一個Acker任務看到ack val已經成為0它就知道元組樹已經完成了。



相關文章