对于Storm,有一个相对比较重要的概念就是 "Guarantee no data loss" -- 可靠性
很明显,要做到这个特性,必须要tracker 每一个data的去向和结果,Storm是如何做到的?
那就是我们接下来要说的 Acker 机制,先概括下Acker所参与的工作流程
1 Spout 创建一个新的Tuple时候,会发射一个消息通知acker去跟踪;
2 Bolt 在处理Tuple成功或者失败的时候,也会发送一个消息通知Acker
3 Acker会回调发射该Tuple的Spout其Ack ,fail方法
一个tuple被完全处理的意思是:
这个tuple以及由这个tuple后续所导致的所有tuple 都被成功的处理, 而一个tuple会被认为处理失败了,如果这个消息在timeout所指定的时间内没有成功处理。
也就是说对于任何一个Spout-tuple以及它的子孙,到底处理成功失败与否,我们都会得到通知。
由一个tuple产生一个新的tuple称为:anchoring,你发射一个tuple的同时也就完成了一次anchoring。
Storm 里面有一类特殊的task称为:acker,请注意,Acker也是属于一种task,如果您对Task还不够熟悉,请参考另外的一篇文档:有关Storm-executor-task的关系,acker负责跟踪spout发出的每一个tuple的tuple树,当Acker发现一个tuple树已经处理完成了,它就会发送一个消息给产生这个tuple的task。
Acker task 组件来设置一个topology里面的acker的数量,默认值是一,如果你的topoogy里面的tuple比较多的话,那么请把acker的数量设置多一点,效率会更高一点。
理解Storm的可靠性的办法是看看 tuple,tuple树的生命周期,当一个tuple被创建,不管是Spout 和bolt 创建的,他被赋予一个位的ID,而acker就是利用这个ID 去跟踪所有的tuple的。每一个tuple知他祖宗的iD,吐过Stomr检测到一个tuple被完全处理了,那么Storm会以最开始的那个message-id 作为参数去调用消息源头的ACK方法,反之Storm会调用Spout的fail方法。
值得注意的一点是Storm调用Ack或者fail的task始终是产生这个tuple的那个task,所以如果一个Spout,被分为很多个task来执行,消息执行的成功失败与否始终会通知最开始发出tuple的那个task。
作为Storm的使用者,有两件事情要做以更好的利用Storm的可靠性特征,首先你在生成一个tuple的时候要通知Storm,其次,完全处理一个tuple之后要通知Storm,这样Storm就可以检测到整个tuple树有没有完成处理,并且通知源Spout处理结果。
1 由于对应的task挂掉了,一个tuple没有被Ack:
Storm的超时机制在超时之后会把这个tuple标记为失败,从而可以重新处理。
2 Acker挂掉了: 在这种情况下,由这个Acker所跟踪的所有spout tuple都会出现超时,也会被重新的处理。
3 Spout 挂掉了:在这种情况下给Spout发送消息的消息源负责重新发送这些消息。
三个基本的机制,保证了Storm的完全分布式,可伸缩的并且高度容错的。
============================
对于Storm的Ack机制 在源码实现上进行一些补充。
1: 在Ack框架的设计之中,Storm发射出去的消息都会对应于一个随机的消息ID号。
2 : Spout发射消息之后。将像Acker Bolt发射一个消息,这个消息的内容为 《RootID,消息ID》 Acker Bolt将会为该消息创建一条跟踪项。
3: Bolt产生要发射的消息的过程之中,也会将该消息ID发射到Acker Bolt,AckerBolt 对消息ID进行异或以后进行存储。
4:Acker Bolt 在更新某一个消息的跟踪值时,若发现他的值变为0,那么就会像Spout节点发射消息,表明Spout发射的这条消息已经被成功的处理 。
5:所有消息的消息ID 都将更新到Spout发射出去的根消息上,Bolt新产生的消息并不会被单独跟踪。
6:如果你在发射的过程之中,没有指定用于消息跟踪的ID,那么系统就不对消息进行跟踪,若系统中不含有Ack bolt,消息也不会被跟踪。
7: Spout的每条消息以及该消息所演化出来的消息的跟踪 容量负载为:16个字节:8个字节的根消息ID,以及8个字节的消息跟踪值AckValue。 事实上,由于Storm中采用的是HashMap对其进行的跟踪,在32位的JVM之中,每条消息至少需要20个字节的额外负载,故有一条消息的跟踪需要 40个左右的负载。