storm中的ack-fail机制

本文深入探讨了Storm中ACK-Fail机制的工作原理和技术细节,包括其在代码编写中的具体应用,消息处理流程以及底层实现机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概念:storm的ack-fail机制也就是storm的可靠消息处理机制,通俗来讲就是给spout发出的每个tuple带上一个messageid,然后这个spout下面的每一个bolt
都会给他返回一个完成情况,只有当每一个bolt都返回了正确的结果,整个发送过程才算成功,任何一个bolt处理不成功,则不成功。

我对ack-fail机制的讲解分为三个层面:分别是api应用也就是写代码方面、ack-fail机制的处理过程。

首先是代码编写方面:
假设我们在这个系统中有一种spout和一种bolt,如果你不使用ack-fail机制那么一个spout中有三个方法,分别是open(),nextTuple()和outputFields(),
open的作用是初始化那个outputCollector,nextTuple方法就是不断地取值然后发给下一个bolt或者就结束了,declareOutputFields方法就是声明一下我发
射出去的数据id,如果你使用了ack-fail机制那就多了俩方法,ack()和fail(),发送成功了就调ack方法,不成功就调fail,你在fail里可以进行重发或者什
么的,当然这些都是你自己决定,你要是不想做处理函数里就啥都不写,我们这里进行重发:
Myspout{
    Map<String,Values> buffer = new HashMap<>();  //缓存正在发送的tuple
    open();
    nextTuple(){
        String messageId = UUID.randomUUID().toString().replace("-", "");  //随机生成一个msgid
        buffer.put(messageId,tuple);  //放到缓存中
        collector.emit(value,messageId); //发射出去
    }
    fail(Object msgid){
        String value = buffer.get(msgid);  //取出value
        collector.emit(value,msgid);  //重发
    }
    ack(Object msgid){
        buffer.remove(msgid);  //从buffer中拿出来
    }
    declareOutputFields();
}
MyBolt方法本来的三个方法是prepare(),execute(),declareOutputFields(),prepare方法主要也就是初始化那个outputCollector,execute方法就是执行处理
过程,declareOutputFields也一样就是声明一下我发出去的是啥,而在应用了ack-fail机制的bolt中,这里要显示的声明我处理完了:
MyBolt{
    void execute(Tuple input){
        collector.emit(input,value);
        collector.ack(input);
    }
}

然后是ack-fail的处理过程方面:
spout---->tuple1---->bolt1---->ack(tuple1)
                     bolt1---->tuple1-1---->bolt2-1---->ack(tuple1-1);
                     bolt1---->tuple1-2---->bolt2-1---->ack(tuple1-2);
                                            ..........................
只有当每一个bolt都正确ack了,整个发送过程才算成功,任何一个bolt处理不成功,则不成功,重新处理。                                    
那么ack这个东西他如何判断前者发射的tuple和ack返回的tuple是不是同一个呢,这里主要的概念是异或处理,对于每一个spout发射任务,ack维护了这样
一组数据,<spoutTaskId,<RootID,ackValue>,spoutTaskId标志着唯一的一个spouttask,RootId标志着整个过程的结果,ackValue记录着整个过程中不同
的tuple相异或的时候结果的变化,当ackValue最终等于0的时候,就标志着整个过程成功了,那么这个RootID如何计算呢,我们知道每一个tuple的发射过
程bolt都给了相应的返回tupleid,当这两个tupleid相同时就表明这一小阶段的任务完成了,而tuplid转化成二进制是0101形式的,如果返回的tupleid和
这个发送的tupleid相异或等于0,也就是ackValue等于0,就证明这两个是同一个id,也就表明这一小部分的任务成功了,但是整个过程中可能会有多层bolt,
每一个bolt的执行速度可能不同,所以注意,如果这些所有结果相异或后,ackValue等于0,就表明这个传输任务完成了。

最后我们来从底层实现来讲一下ack-fail机制:
我们运行storm程序时会发现有这样一个任务-ackTask,看一下源码他是继承了Bolt,他就是一个和其他数据处理的bolt一起存在一起处理的进程,而实际上
整个过程中是存在两种tuple的,分别是DataTuple和AckTuple,DataTuple主要负责数据的处理,AckTuple负责整个过程的排错,我们先来看这个AckTuple,
他其实封装了AckTuple<RootID,tupleId>,RootID标识了这个tuple属于哪个过程,而tupleId标识了每一个特定的tuple,这个AckTuple最终封装成一个
messageId这样一个对象,而DataTuple中就含有这个messageId。接下来我们来看整个过程,两种线程是一起进行的,ack的线程比较简单,当spout发射
一个DataTuple时同时就会发射一个AckTuple,然后他就在这等待响应,spout将DataTuple(messageId(AckTuple))发送给bolt,bolt.execute(dataTuple)
之后会应答也就是bolt.ack(dataTuple),而dataTuple中封装了ackTuple,就可以还原出这个ackTuple,这样acktask就等到了ack应答,也就是说这一阶段
处理成功,以此类推。

上述过程示意:
spout.emit(dataTuple(messageId(ackTuple)))--->bolt.excute--->bolt.ack(dataTuple(ackTuple))
spout.emit(ackTuple)

这就是我理解的整个ack-fail机制。





















### Storm Ack机制详解 #### 工作原理概述 Storm通过一组特殊任务称为“acker”的组件来追踪每条消息的状态。这些ackers负责维护拓扑结构中有向无环图(DAG)内各节点间的关系,确保每个Tuple都能被正确处理并最终得到确认[^5]。 当一个Spout发出一个新的Tuple时,它会附带一个唯一的ID用于后续识别该Tuple及其衍生出来的所有子Tuples。随着这条原始Tuple在网络中传播到不同的Bolts进行加工转换,在每一个阶段完成操作之后都会发送一次ACK信号给最初的发射者(Spout),表明当前部分已经顺利完成;一旦所有的分支路径都返回了成功的ACK响应,则表示整个事务链路全部结束,此时可以认为原初的那条Tuple已经被完全消费掉了,并且可以从内存里清除掉相应的记录以释放资源[^1]。 然而,如果有任何一个环节出现了错误未能正常传递下去或者超出了配置的时间限制(Config.TOPOLOGY_MESSAGE_TIMEOUT_SECS,默认为30秒)[^4]仍未收到预期之外的结果(即既不是成功也不是失败),那么就会触发Fail事件通知上游重新尝试发送此Tuple直到达到预设的最大重试次数为止。这种设计使得即使面对高并发场景下海量的数据流也能有效保障数据的一致性和完整性[^2]。 对于开发者来说,编写可靠的Word Count应用意味着要特别注意如何利用好这套机制。具体而言就是继承`BaseBasicBolt`类而不是普通的`BaseRichBolt`,因为前者自带实现了自动化的Ack逻辑而后者则需要手动管理[^3]: ```java public class WordCount extends BaseBasicBolt { private Map<String, Integer> counts; @Override public void prepare(Map conf, TopologyContext context) { this.counts = new HashMap<>(); } @Override public void execute(Tuple tuple, BasicOutputCollector collector) { String word = tuple.getStringByField("word"); int count = counts.getOrDefault(word, 0); counts.put(word, ++count); // 发射新的 Tuple 并立即 ACK 它 collector.emit(new Values(word, count)); } } ``` 在这个例子中,每当接收到输入元组后执行计数更新动作的同时也会立刻向外广播下一个批次的内容出去,紧接着就调用了内置的方法告知系统本批工作已完成无需再等待其他反馈信息进来即可继续前进至下一步骤去了——这便是借助于框架本身所提供的便捷之处所在。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值