再探Apache Storm(4)---ack机制

容错

在分布式环境中,失败是不可避免的,例如网络波动造成的消息丢失、节点故障造成的组件失效等各种问题,在Apache Storm中的表现就是tuple处理失败,在一些场景下不能忽略这些失败,所以就需要一种容错机制。

nimbussupervisor本身就是被设计成快速失败的,所有的元数据都是存放在Zookeeper中,所以这块的容错没有什么可说的,主要是集中在tuple上。

Ack tree

从方便理解的角度来看,可以把Ack机制看成是给每一个tuple建了一棵ack树,树根是spout发出的那条消息,然后枝干就是发出的每一条stream,当下游的bolt处理完消息发出tuple时,使用anchor方法把这些新的tuple挂到原来的ack输上,如果tree上的每一个tuple均被ack则认为整个tuple被处理完毕,调用spout的ack方法。反之,一旦某一个叶子结点在一定时间内没有被确认,则认为tuple处理出现故障,调用spout的fail方法,重新执行整个tuple。
如果下游bolt在emit时没有anchor,则storm认为该tuple 已处理完毕,调用 emit(tuple, new Values())方法才会将新emit的tuple加入到tuple tree中。

1
2
3
4
List<Tuple> anchors = new ArrayList<>();
anchors.add(tuple1);
anchors.add(tuple2);
collector.emit(anchors, tuple);

Ack tree的实现

上面提到的ack tree可以很有效的解决tuple容错问题,但是在实际中则会遇到很严重的空间占用问题,一个spout发出的tuple最终可能需要一个庞大的ack tree来跟踪,造成了极大的额外开销和性能损失。

Acker的跟踪算法是Storm的主要突破之一,对任意大的一个Tuple树,它只需要恒定的20字节就可以进行跟踪。

那么它是如何实现的呢?还记得神奇的位操作么,对每一个spout发出的tuple,storm会分配一个20字节的空间ack-val,同时对每一个tuple和anchor在该tree上的tuple,都会生成一个20个字节的val,然后与ack-val进行异或操作,在tuple产生的时候异或一次,并且在完成时再次异或一次,可以想到的是,如果每一个tuple都被成功执行了,那么最终的ack-val的值一定是0,而如果ack-val不为0,那么说明很有可能中间出现了问题,认为fail了。

于是,Apache Storm仅用了20个字节就完成了tuple的容错机制。尽管没有办法做到百分百可靠,但是工程实践上从来不会盲目的追求百分百正确,而是正确性与成本的一种balance。

参考

Ack 机制 | JStorm

Storm的ack机制分析

Storm的可靠性与ack机制

storm的ack机制

聊聊storm的ack机制

Storm的ack机制在项目应用中的坑

Storm并发模型及ACK机制处理

Storm ACK机制