Storm是一个分布式实时计算系统。按照Storm作者的说法,storm对于实时计算的意义类似于hadoop对于批处理的意义。hadoop为我们提供了map、reduce原语,使我们的批处理程序变得非常的简单和优美。同样,storm也为实时计算提供了一些简单优美的原语。
Storm的适用场景:
1. 流数据处理。Storm可以用来处理源源不断流进来的消息,处理之后将结果写入到某个存储中去。
2. 分布式rpc。由于Storm的处理组件是分布式的,而且处理延迟极低,所以可以作为一个通用的分布式rpc框架来使用。当然,其实我们的搜索引擎本身也是一个分布式rpc系统。
Storm的基本概念:
storm和hadoop的对比如图所示:
Nimbus:负责资源分配和任务调度。
Supervisor:负责接受nimbus分配的任务,启动和停止属于自己管理的worker进程。
Woker:运行具体处理组件逻辑的进程。
Task:work中每个spout/bolt的线程称为一个task,在storm0.8之后,task不再与物理线程对应,同一个spout/blot的task可能会共享一个物理线程,该线程称为executor。
Nimbus负责在storm集群内分发代码,分配任务给工作机器,并且负责监控集群运行状态。supervisor负责监听从Nimbus分配给它执行的任务,据此启动或停止执行任务的工作进程。Nimbus和supervisor节点之间所有协调工作是通过zookeeper集群来实现的。
storm中各个组件之间的关系:
Topology:Storm中运行一个实时应用程序,因为各个组件间的消息流动形成逻辑上的 拓扑结
Spout:在一个topology中产生源数据流的组件。通常情况下spout会从外部数据源中读取数据,然后转换为topology内部的源数据。Spout是一个主动的角色,其接口中有个nextTuple函数,storm框架会不停地调用此函数,用户只要在其中生成源数据即可。
Bolt:在一个topology中接受数据然后执行处理的组件。Bolt可以执行过滤、函数操作、合并、写数据库等任何操作。Bolt是一个被动的角色,其接口中有个execute函数,在接受到消息后会调用此函数,用户可以在其中执行自己想要的操作。
Tuple:一次消息传递的基本单元。本来应该是一个Key-value的map,但是由于各个组件间传递的tuple的字段名称已经事先定义好,所以tuple中只要按序填入各个Value就行了,所以就是一个value list。
Stream:源源不断传递的tuple就组成了stream。
Stream grouping:即消息的partition方法。Storm中提供若干种使用的grouping方式,包括shuffle、fields hash、all、global、none、direct和localOrshuffle等。
相比于s4、puma等其他实时计算系统,storm最大的亮点在于其记录级的容错和能够保证消息精确处理的事物功能。
Storm记录级容错的基本原理
Storm允许用户在spout中发射一个新的源tuple时为其指定一个message id,这个message id可以是任意的object对象。多个源tuple可以共用一个message id,表示这多个源tuple对用户来说是同一个消息单元。storm中记录级容错是指storm会告知用户每一个消息是否在指定时间内被完全处理了。完全处理是指该message id绑定的源tuple及由该源tuple后续生成的tuple经过了topology中每一个应该到达的bolt处理。
用图表示完全处理的例子:
在spout由message 1绑定的tuple 1和tuple 2 经过了bolt 1和bult 2的处理生成两个新的tuple,并最终都流向了bolt3。当这个过程完成处理时,称message 1被完全处理了。
在storm的topology中有一个系统级组件,叫做acker。这个acker的任务就是追踪从spout中流出来的每一个message id绑定的若干tuple的处理路径,如果在用户设置的最大超时时间内这些tuple没有被完全处理,那么acker就会告知spout该消息处理失败了,相反则会告知spout该消息处理成功了。在spout中系统会为用户指定的message id生成一个对应的64位整数,作为一个root id。root id会传递给acker及后续的bolt作为该消息单元的唯一标识。同时无论是spout还是bolt每次新生成一个tuple的时候,都会赋予该tuple一个64位的整数的id。Spout发射完某个message id对应的源tuple之后,会告知acker自己发射的root id及生成的那些源tuple的id。而bolt呢,每次接受到一个输入tuple处理完之后,也会告知acker自己处理的输入tuple的id及新生成的那些tuple的id。Acker只需要对这些id做一个简单的异或运算,就能判断出该root id对应的消息单元是否处理完成了。