一、Storm
Storm是一個實時的可靠地分佈式流計算框架。一個典型的大數據實時計算應用場景:從Kafka消息隊列讀取消息(可以是logs,clicks,sensor data);通過Storm對消息進行計算聚合等預處理;把處理結果持久化到數據庫或者HDFS做進一步深入分析。 Storm中分爲Topology開發和Trident開發,Trident是一種高度抽象的實時計算模型,是一種有狀態的流式處理框架,而Topology是一種無狀態流式處理框架。
Topology特點:
無狀態
低延遲
開啓ack後保證tuple至少一次處理
Trident特點:
有狀態
批處理
提供有且只有一次的處理語義
選擇策略:
若需要有且只有一次的批處理統計則選擇Trident,其他情況使用Topology即可滿足。
二、批處理策略
對消息預聚合時,通常是將多個tuple的計算結果存儲到數據庫中,即批處理的概念。使用Storm來進行批處理大概有三種方案:Trident API、Storm windowing、自定義批處理 (如利用TickTuple)。
- Trident API
1.1 基本概念
Trident從概念上就是以Batch爲處理單元,是天然的批處理方案。Tident提供了 joins(數據流融合), aggregations(聚合), grouping(分組), functions(自定義函數), 以及 filters(過濾)等功能。除此之外,Trident 還提供了一些專門的原語,從而在基於數據庫或者其他存儲的前提下來應付有狀態的遞增式處理。但由於存儲了狀態信息,性能相比Topology有所下降。
-
Storm Windowing
2.1 基本概念
Storm抽象出窗口處理的概念,使得開發者可以很方便的做一些統計計算。Storm支持用戶每隔一段時間(Sliding interval)集中處理落在相同窗口(Window length,可以是時間段也可以tuple數量)下的所有tuples(一個窗口爲一個批)。目前有兩種窗口抽象:
Tumbling Window : Sliding interval = Window length 這就使得一個tuple只屬於一個窗口。
Sliding Window : Sliding interval <> Window length 這就使得一個tuple可能屬於多個窗口。
2.2 使用方法
window舉例
public class SlidingWindowBolt extends BaseWindowedBolt {
private OutputCollector collector;
// 接受tuple前調用
@Override
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
this.collector = collector;
}
// 在每一次窗口激活後調用,如到達用戶時間間隔或者滿足tuple數量
@Override
public void execute(TupleWindow inputWindow) {
for(Tuple tuple: inputWindow.get()) {
// do the windowing computation
...
}
// emit the results
collector.emit(new Values(computedValue));
}
// 在bolt銷燬前調用
@Override
public void cleanup();
}
public static void main(String[] args) {
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout("spout", new RandomSentenceSpout(), 1);
builder.setBolt("slidingwindowbolt",
new SlidingWindowBolt().withWindow(new Count(30), new Count(10)),
1).shuffleGrouping("spout"); // 設置窗口的Sliding interval 和 Window length
Config conf = new Config();
conf.setDebug(true);
conf.setNumWorkers(1);
StormSubmitter.submitTopologyWithProgressBar(args[0], conf, builder.createTopology());
}
- 使用TickTuple
3.1 基本概念
Storm中內置了一種定時機制Tick,它能夠讓任何Bolt的所有task每隔一段時間(精確到秒級,用戶可以自定義)收到一個來自_systemd的_tick stream的tick tuple,bolt收到這樣的tuple後可以根據業務需求完成相應的處理。可以爲某個Bolt單獨設置Tick也可以爲所有Bolt設置全局的Tick。若在未收到Tick之前Bolt存儲數據(或簡單處理),收到Tick後對存儲的數據集中處理(或者這段時間處理的tuple結果做處理),即實現了批處理的功能。
3.2 使用方法
爲Bolt單獨設置Tick:若希望某個bolt每隔一段時間做一些操作,那麼可以將bolt繼承BaseBasicBolt/BaseRichBolt,並重寫getComponentConfiguration()方法。在方法中設置Config.TOPOLOGY_TICK_TUPLE_FREQ_SECS的值,單位是秒。getComponentConfiguration()是backtype.storm.topology.IComponent接口中定義的方法,在此方法的實現中可以定義以Topology開頭的此bolt特定的Config。
Bolt Tick
public class TickBolt extends BaseBasicBolt{
@Override
public void execute(Tuple tuple, BasicOutputCollector collector) {
if (isTick(tuple)) {
// 接收到Tick後的操作
} else {
// 接收到業務tuple後的操作
}
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
}
//設置10秒發送一次tick心跳
@SuppressWarnings("static-access")
@Override
public Map<String, Object> getComponentConfiguration() {
Config conf = new Config();
conf.put(conf.TOPOLOGY_TICK_TUPLE_FREQ_SECS, 10);
return conf;
}
// 判斷tuple是否是TickTuple
public boolean isTick(Tuple tuple) {
return tuple != null
&& Constants.SYSTEM_COMPONENT_ID.equals(tuple.getSourceComponent())
&& Constants.SYSTEM_TICK_STREAM_ID.equals(tuple.getSourceStreamId());
}
}
若希望Topology中的每個bolt都每隔一段時間做一些操作,那麼可以定義一個Topology全局的tick,同樣是設置Config.TOPOLOGY_TICK_TUPLE_FREQ_SECS的值:
Topology Tick
Config conf = new Config();
conf.put(conf.TOPOLOGY_TICK_TUPLE_FREQ_SECS, 10);
StormSubmitter.submitTopology(topologyName,conf, topology.build());
3.3 tick設置的優先級
與Linux中的環境變量的優先級類似,storm中的tick也有優先級,即全局tick的作用域是全局bolt,但對每個bolt其優先級低於此bolt定義的tick。這個參數的名字TOPOLOGY_TICK_TUPLE_FREQ_SECS具有一定的迷惑性,一眼看上去應該是Topology全局的,但實際上每個bolt也可以自己定義。
3.4 tick的精確度
Config.TOPOLOGY_TICK_TUPLE_FREQ_SECS是精確到秒級的。例如某bolt設置Config.TOPOLOGY_TICK_TUPLE_FREQ_SECS爲10s,理論上說bolt的每個task應該每個10s收到一個tick tuple。實際測試發現,這個時間間隔的精確性是很高的,一般延遲(而不是提前)時間在1ms左右。
3.5 storm tick的實現原理
在bolt中的getComponentConfiguration()定義了該bolt的特定的配置後,storm框架會在TopologyBuilder.setBolt()方法中調用bolt的getComponentConfiguration()方法,從而設置該bolt的配置。
調用路徑爲:TopologyBuilder.setBolt() —> TopologyBuilder.initCommon() —> getComponentConfiguration()