转载自:https://blog.youkuaiyun.com/lulongzhou_llz/article/details/46399445
Spout 接口
Storm中与Spout相关的接口主要有ISpout和IRichSpout。
Spout中由于nextTuple、ack和fail方法是在一个线程里面被调用的,如果nextTuple阻塞,其他方法也将被阻塞,这样会有许多意外情况发生,因此nextTuple必须是非阻塞的。任何的Spout都将利用nextTuple来发送信息。
ISpout的fail和ack回调方法仅仅给出了发送消息时所对应的Messageld, 而没有给出具体的消息内容,这就意味着如果要实现消息的重传,用户则需要自己来维护那些已经发送的消息。
实现Spout的过程中,用户可以编写其构造函数,然而该构造函数并不会被实际调用。因为在提交Topology时,系统会调用Topology的构造函数(而非Spout的构造函数 ),并将产生的对象序列化成字符数组。每一个节点上的Spout对象都是通过反序列化得到的,这可能导致某些成员没有被正确初始化。ISpout中的open回调函数会在对象被反序列后调用,我们应当在open方法中对对象的复杂成员进行初始化,而不应使用构造函数来完成这一过程。
IRichSpout同时实现IComponent和ISpout接口,于是从含义上看它表示一个具有Spout功能的组件。
对于Storm来说,根据每个拓扑的需要担保消息的可靠性是开发者的责任。这就涉及到消息可靠性和资源消耗之间的权衡。高可靠性的拓扑必须管理丢失的消息,必然消耗更多资源;可靠性较低的拓扑可能会丢失一些消息,占用的资源也相应更少。不论选择什么样的可靠性策略,Storm都提供了不同的工具来实现它。
要在spout中管理可靠性,可以在分发时包含一个元组的消息ID(collector.emit(new Values(…),tupleId))。在一个元组被正确的处理时调用ack方法,而在失败时调用fail方法。当一个元组被所有的靶bolt和锚bolt处理过,即可判定元组处理成功。
发生下列情况之一时为元组处理失败:
- 提供数据的spout调用collector.fail(tuple)
- 处理时间超过配置的超时时间
来看一个例子。处理银行事务,需求如下:如果事务失败了,重新发送消息;如果失败了太多次,终结拓扑运行。
创建一个spout和一个bolt,spout随机发送100个事务ID,有80%的元组不会被bolt收到。实现spout时利用Map分发事务消息元组,这样就比较容易实现重发消息。
public void nextTuple() {
if(!toSend.isEmpty()){
for(Map.Entry<Integer, String> transactionEntry : toSend.entrySet()){
Integer transactionId = transactionEntry.getKey();
String transactionMessage = transactionEntry.getValue();
collector.emit(new Values(transactionMessage),transactionId);
}
toSend.clear();
}
}
如果有未发送的消息,得到每条事务消息和它的关联ID,把它们作为一个元组发送出去,最后清空消息队列。值得一提的是,调用map的clear是安全的,因为nextTuple失败时,只有ack方法会修改map,而它们都运行在一个线程内。
维护两个map用来跟踪待发送的事务消息和每个事务的失败次数。ack方法只是简单的把事务从每个列表中删除。
public void ack(Object msgId) {
messages.remove(msgId);
failCounterMessages.remove(msgId);
}
fail方法决定应该重新发送一条消息,还是已经失败太多次而放弃它。
NOTE:如果你使用全部数据流组,而拓扑里的所有bolt都失败了,spout的fail方法才会被调用。
public void fail(Object msgId) {
Integer transactionId = (Integer) msgId;
//检查事务失败次数
Integer failures = transactionFailureCount.get(transactionId) + 1;
if(failes >= MAX_FAILS){
//失败数太高了,终止拓扑
throw new RuntimeException("错误, transaction id 【"+
transactionId+"】 已失败太多次了 【"+failures+"】");
}
//失败次数没有达到最大数,保存这个数字并重发此消息
transactionFailureCount.put(transactionId, failures);
toSend.put(transactionId, messages.get(transactionId));
LOG.info("重发消息【"+msgId+"】");
}
首先,检查事务失败次数。如果一个事务失败次数太多,通过抛出RuntimeException终止发送此条消息。否则,保存失败次数,并把消息放入待发送队列(toSend),它就会再次调用nextTuple时得以重新发送。
NOTE:Storm节点不维护状态,因此如果在内存保存信息(就像本例做的那样),而节点又不幸挂了,就会丢失所有缓存的消息。
Storm是一个快速失败的系统。拓扑会在抛出异常时挂掉,然后再由Storm重启,恢复到抛出异常前的状态。
本文深入探讨了Apache Storm中Spout接口的设计与实现细节,包括ISpout和IRichSpout的功能区别,以及如何通过实现nextTuple、ack和fail等方法确保消息的可靠传输。文章还提供了一个具体的银行交易事务处理案例,演示如何利用这些方法实现消息重传机制。
8613

被折叠的 条评论
为什么被折叠?



