转载请注明出处:http://blog.youkuaiyun.com/lonelytrooper/article/details/9970241
第五章 Bolts
正如你看到的,bolts是一个storm集群中的关键组件。在本章中,你将看到一个bolt的生命周期,bolt的设计策略,以及怎样实现它们的一些示例。
Bolt生命周期
Bolt是一种将元组作为输入并且制造元组作为输出的组件。当你实现一个bolt的时候,你通常实现IRichBolt接口。Bolts在客户端机器被创建,序列化至topology中,并提交至集群中的master机器。集群运行workers来反序列bolt,调用它上的prepare方法,然后开始处理元组。
要自定义bolt,你需要在它的构造方法中设置参数并且将它们保存为实例变量,这样它们可以在bolt被提交至集群时序列化。
Bolt结构
Bolts包含如下方法:
declareOutputFields(OutputFieldsDeclarerdeclarer)
定义该bolt的输出模式。
prepare(java.util.Map stormConf,TopologyContext context, OutputCollector col
lector)
在bolt开始处理元组前被调用。
execute(Tuple input)
处理一个单独的输入元组。
cleanup()
当bolt将要关闭时被调用。
来看一个将句子分隔成单词的bolt示例:
class SplitSentenceimplementsIRichBolt{
private OutputCollector collector;
public voidprepare(Map conf,TopologyContext context,OutputCollectorcollector) {
this.collector=collector;
}
public voidexecute(Tuple tuple) {
String sentence =tuple.getString(0);
for(Stringword:sentence.split("")) {
collector.emit(newValues(word));
}
}
public voidcleanup(){
}
public voiddeclareOutputFields(OutputFieldsDeclarerdeclarer) {
declarer.declare(newFields("word"));
}
}
正如你看到的,该bolt是非常直观的。值得一提的是这个例子中没有消息担保。这意味着如果该bolt由于某种原因丢弃了一条信息---因为该消息失败了或者以编程的方式故意丢弃了---产生该消息的spout是永远不会被提醒的,并且其间的任何的bolts和spouts也不会。
在许多场景下,你是希望在整个topology中消息是被确保处理的。
可靠Bolts versus不可靠Bolts
如前述,storm确保由spout发送的每条消息都会被所有bolts完全处理。这是一种设计考虑,意味着你需要决定是否你的bolts是确保消息被处理的。
Topology是一颗消息(元组)经过一条或多条分支形成的的结点树。每个结点将ack(元组)或fail(元组),这样storm知道一个消息是什么时候失败的并且通知产生该消息的spout或spouts。因为storm topology运行在一个高度并行的环境,因此记录原始的spout实例的最好方式是在消息元组中包含一个原始spout的引用。这个技术叫做锚定。修改你刚刚看到的SplitSentence bolt,这样它可以确保消息处理。
class SplitSentenceimplementsIRichBolt{
private OutputCollector collector;
public voidprepare(Map conf,TopologyContext context,OutputCollectorcollector) {
this.collector=collector;
}
public voidexecute(Tuple tuple) {
String sentence =tuple.getString(0);
for(Stringword:sentence.split("")) {
collector.emit(tuple,newValues(word));
}
collector.ack(tuple);
}
public voidcleanup(){
}
public voiddeclareOutputFields(OutputFieldsDeclarerdeclarer) {
declarer.declare(newFields("word"));
}
}
锚定发生的具体行是在collector.emit() 语句。就像前边提到的,传递元组使得storm可以记录原始spout的踪迹。collector.ack(tuple) 和collector.fail(tuple)告诉spout每条消息发生了什么。当树中每条消息都被处理后,storm认为由spout发出的元组被完全处理了。当它的消息树没有在配置的超时时间内被全部处理时,一个元组的处理被认为是失败的。缺省时间为30秒。
你可以通过修改topology的Config.TOPOLOGY_MESSAGE_TIMEOUT_SECS配置来修改超时。
当然,spout需要考虑消息失败并且重试或者相应的丢弃消息的场景。
每个你处理的元组都必须被应答或者宣告失败。Storm使用内存来保存每个元组的踪迹,所以如果你没有确认/宣告失败每个元组,该任务会逐渐的耗尽内存。
多流
Bolt可以使用emit(streamId, tuple)发射元组到多条流,每条流由字符串streamId来识别。然后,在TopologyBuilder中,你可以决定订阅哪条流。
多锚定
使用bolt来连接或者聚合流时,你需要在内存中缓存元组。为了这种情形下的消息担保,你不得不锚定流到不止一个元组。这通过调用包含元组列表的emit方法实现。
...
List<Tuple>anchors=newArrayList<Tuple>();
anchors.add(tuple1);
anchors.add(tuple2);
_collector.emit(anchors,values);
...
那样,任何时候一个bolt被确认或者失败,它会通知树,并且由于流被锚定给不止一个元组,被包含的所有spouts都会被通知。
通过IBasicBolt来自动Ack
正如你可能注意到的,在许多用例中,你需要消息担保。为简化它,storm为bolt提供了另一个叫做IBasicBolt的接口,它封装了一种在execute方法之后调用ack方法的模式。BaseBasicBolt,一个该接口的实现,被用来做自动确认。
class SplitSentenceextendsBaseBasicBolt{
public voidexecute(Tuple tuple,BasicOutputCollector collector) {
String sentence =tuple.getString(0);
for(Stringword:sentence.split("")) {
collector.emit(newValues(word));
}
}
public voiddeclareOutputFields(OutputFieldsDeclarerdeclarer) {
declarer.declare(newFields("word"));
}
}
被发射到BasicOutputCollector的元组被自动锚定到输入元组。