Storm的可靠性

1、Spout的可靠性保证


     在Storm中,消息处理可靠性从Spout开始的。storm为了保证数据能正确的被处理, 一个提供了可靠的处理机制的spout需要记录它发射出去的tuple,当下游bolt处理tuple或者子tuple处理失败时spout能够重新发射。子tuple可以理解为bolt处理spout原始tuple。另外一个角度来看,可以将spout发射的数据流看作一个tuple数的主干。如下图:

在图中,实线部分为tuple的主干部分,而虚线部分为子tuple部分,子tuple源于原始的tuple部分。这样产生的结构为tuple树。在有保障数据处理的过程中,bolt每接收到一个tuple,都需要向上游确认应答(ask)者报错。对于有主干一个tuple中的tuple,如果tuple树上的每个bolt进行了确认应答,spout会调用ack方法来表名这条信息已经完全处理了。如果树干中的任何一个bolt出错,或者处理超时,spout会调用fail方法。

Storm接口定义了三个可靠性相关的API:nextTuple(),ack和fail
 

2、Bolt中的可靠性
     Bolt中的可靠性主要靠两步来实现:

  1. 发射衍生Tuple的同时anchor原Tuple
  2. 对各个Tuples做ack或fail处理     

     anchor一个Tuple就意味着在输入Tuple和其衍生Tuple之间建立了关联,关联之后的Tuple便加入了Tuple tree。我们可以通过如下方式anchor一个Tuple:

collector.emit( tuple, new Values( word));


     如果我们发射新tuple的时候不同时发射元tuple,那么新发射的Tuple不会参与到整个可靠性机制中,它们的fail不会引起root tuple的重发,我们成为unanchor:

 collector.emit( new Values( word));


     ack和fail一个tuple的操作方法: 

this .collector.ack(tuple);
this .collector.fail(tuple);


     上面讲过了,IBasicBolt 实现类不关心ack/fail, spout的ack/fail完全由后面的bolt的ack/fail来决定. 其execute方法的BasicOutputCollector参数也没有提供ack/fail方法给你调用. 相当于忽略了该bolt的ack/fail行为。
     在 IRichBolt实现类中, 如果OutputCollector.emit(oldTuple,newTuple)这样调用来发射tuple(anchoring), 那么后面的bolt的ack/fail会影响spout ack/fail, 如果collector.emit(newTuple)这样来发射tuple(在storm称之为anchoring), 则相当于断开了后面bolt的ack/fail对spout的影响.spout将立即根据当前bolt前面的ack/fail的情况来决定调用spout的ack/fail.
 所以某个bolt后面的bolt的成功失败对你来说不关心, 你可以直接通过这种方式来忽略.中间的某个bolt fail了, 不会影响后面的bolt执行, 但是会立即触发spout的fail. 相当于短路了, 后面bolt虽然也执行了, 但是ack/fail对spout已经无意义了. 也就是说, 只要bolt集合中的任何一个fail了, 会立即触发spout的fail方法. 而ack方法需要所有的bolt调用为ack才能触发. 所以IBasicBolt用来做filter或者简单的计算比较合适。
3.总结
    storm的可靠性是由spout和bolt共同决定的,storm利用了anchor机制来保证处理的可靠性。如果spout发射的一个tuple被完全处理,那么spout的ack方法即会被调用,如果失败,则其fail方法便会被调用。在bolt中,通过在emit(oldTuple,newTuple)的方式来anchor一个tuple,如果处理成功,则需要调用bolt的ack方法,如果失败,则调用其fail方法。一个tuple及其子tuple共同构成了一个tupletree,当这个tree中所有tuple在指定时间内都完成时spout的ack才会被调用,但是当tree中任何一个tuple失败时,spout的fail方法则会被调用。
     IBasicBolt类会自动调用ack/fail方法,而IRichBolt则需要我们手动调用ack/fail方法。我们可以通过TOPOLOGY_MESSAGE_TIMEOUT_SECS参数来指定一个tuple的处理完成时间,若这个时间未被处理完成,则spout也会调用fail方法。

4、一个可靠的WordCount例子

一个实现可靠性的spout:


 public class ReliableSentenceSpout extends BaseRichSpout {

     private static final long serialVersionUID = 1L;

     private ConcurrentHashMap<UUID, Values> pending;

     private SpoutOutputCollector collector;

     private String[] sentences = { "my dog has fleas", "i like cold beverages" , "the dog ate my homework" , "don't have a cow man" , "i don't think i like fleas" };

     private int index = 0;

     public void declareOutputFields(OutputFieldsDeclarer declarer) {

          declarer.declare( new Fields( "sentence"));

      }

     public void open( Map config, TopologyContext context, SpoutOutputCollector collector) {

           this. collector = collector;

           this. pending = new ConcurrentHashMap<UUID, Values>();

      }

     public void nextTuple() {

          Values values = new Values( sentences[ index]);

          UUID msgId = UUID. randomUUID();

           this. pending.put(msgId, values);

           this. collector.emit(values, msgId);

           index++;

           if ( index >= sentences. length) {

               index = 0;

          }

           //Utils.waitForMillis(1);

      }

     public void ack(Object msgId) {

           this. pending.remove(msgId);

      }

     public void fail(Object msgId) {

           this. collector.emit( this. pending.get(msgId), msgId);

      }

 }

一个实现可靠性的bolt:


public class ReliableSplitSentenceBolt extends BaseRichBolt {

     private OutputCollector collector;

     public void prepare( Map config, TopologyContext context, OutputCollector collector) {

           this. collector = collector;

      }

     public void execute(Tuple tuple) {

          String sentence = tuple.getStringByField("sentence" );

          String[] words = sentence.split( " ");

           for (String word : words) {

               this. collector.emit(tuple, new Values(word));

          }

           this. collector.ack(tuple);

      }

     public void declareOutputFields(OutputFieldsDeclarer declarer) {

          declarer.declare( new Fields( "word"));

      }

 }

猜你喜欢

转载自blog.csdn.net/qq_38709565/article/details/84889917