转载请注明出处:http://blog.youkuaiyun.com/lonelytrooper/article/details/9965209
第四章 Spouts
在本章中,你会看到设计topology入口点(spout)的最常用的策略和怎样使得spouts容错。
可靠消息 versus不可靠消息
当设计topology的时候,一件要时刻记在脑中的重要事情是消息的可靠性。如果一个消息不能被处理,你需要决定对这条单独的消息该做什么,对于整个topology该做什么。例如,当处理银行存款时,不遗漏一条交易消息是重要的。但是如果你处理上百条的tweet消息来寻找一些统计指标的话,丢失了一条数据,你可以假设指标是非常准确的。
在storm中,根据每个topology的需求来确保消息的可靠性是开发者的职责。这其中包含一个权衡问题。一个可靠的topology必须处理丢失的消息,而这需要更多的资源。一个不是那么可靠的topology可能丢失一些消息,但是资源没那么密集。不论选择的可靠性策略是什么,storm都提供工具来实现它。
为了管理spout的可靠性,你可以发送时和元组一起发送一个消息ID(collector.emit(new Values(…),tupleId))。ack方法和fail方法在元组被成功处理和处理失败时分别被调用。只有当元组被所有的目标bolts和锚定的bolts处理完时,元组才被处理成功(在第五章中会了解到怎么为一个元组锚定一个bolt)。
元组处理失败当:
﹒collector.fail(tuple)被目标spout调用
﹒处理时间超出配置的超时时间
我们看一个示例。假设你在处理银行的交易,你有如下的需求:
﹒如果一个交易失败,重新发送消息。
﹒如果一个交易失败太多次,终止topology。
你将创建一个发送100个随机交易ID的spout和一个接受到的元组80%都处理失败的bolt(你可以在ch04-spout示例中找到完成的示例)。你将用Map实现的spout来发射交易消息元组,这样的话重发消息是很容易的。
public voidnextTuple(){
if(!toSend.isEmpty()){
for(Map.Entry<Integer,String>transactionEntry:toSend.entrySet()){
Integer transactionId =transactionEntry.getKey();
String transactionMessage =transactionEntry.getValue();
collector.emit(newValues(transactionMessage),transactionId);
}
toSend.clear();
}
}
如果仍有消息等待被发送,那么获取每个交易消息及它相关联的ID并且将它们作为一个元组发射,然后清空消息队列。调用map的clear是安全的,因为nextTuple,fail和ack是唯一修改map的方法并且它们在相同的线程中运行。
维护两个map来记录等待发送的交易消息和每个交易已经失败的次数。ack方法仅仅从每个列表中删除了交易消息。
public voidack(Object msgId) {
messages.remove(msgId);
failCounterMessages.remove(msgId);
}
fail方法决定是否重发交易消息或者当失败次数太多时终止topology。
当你在topology中使用all分组并且任何bolt的实例失败的时候,spout的fail方法也会被调用。
public voidfail(Object msgId) {
Integer transactionId = (Integer)msgId;
// Check the number of times the transaction has failed
Integer failures =transactionFailureCount.get(transactionId) +1;
if(fails>=MAX_FAILS){
// If the number of failures is too high, terminate thetopology
throw new RuntimeException("Error, transaction id ["+
transactionId+"] has had too many errors ["+failures+"]");
}
// If the number of failures is less than the maximum,save the number and re-send
the message
transactionFailureCount.put(transactionId,failures);
toSend.put(transactionId,messages.get(transactionId));
LOG.info("Re-sending message ["+msgId+"]");
}
首先,检查交易失败的次数。如果一个交易失败太多次,抛出一个RuntimeException来终止它所在的worker。否则,保存失败的次数并且将交易消息发到toSend队列,这样当nextTuple方法被调用时它会被重新发送。
storm结点不保存状态,所以如果你在内存中保存信息(就像在这个示例中)并且结点失败的话,你将丢失所有已保存的信息。
Storm是一个快速失败的系统。如果一个异常被抛出,topology会失败,但是storm将以一致的状态重新启动处理,这样它可以正确的恢复。