首先回顾map reduce框架
主节点jobtracker,从节点 taskTracker。
用户提交任务给jobtracker,jobtracker分配给taskTracker,我们管这些任务叫job
运行的作业分为两种 map 和 reduce。
Storm是一个实时计算框架
主节点 nimbus 从节点 supervisor
用户提交作业给nimbus, nimbus把任务分配给supervisor,这些提交的任务就是topology(拓扑)
运行的作业分为两种 spout 和 bolt。
Storm是流式的处理既stream,stream的内容是tuple(元组)
Spout生产tuple(元组)发送给bolt处理,bolt处理过的tuple也可以再次发送给其他的tuple处理,最后存入容器。
Tuple的内容详解:
每一个Tuple都是一个值的集合,值是以name, value的形式存在tuple中,topology的每个节点都是靠name来接受和处理tuple
Topology(计算拓扑)
Topology相当与mayreduce中的job,但是,job任务会执行完,但topology不会结束,除非显示杀死它
Topology是由spouts和bolts组成
Spouts和bolts之间靠stream groupings链接,如上图
stream groupings消息分发策略
1、Shuffle Grouping:随机分组,随机派发stream里面的tuple,保证每个bolt接收到的tuple数目相同。
2、Fields Grouping:按字段分组,比如按userid来分组,具有同样userid的tuple会被分到相同的Bolts,而不同的userid则会被分配到不同的Bolts。
3、All Grouping:广播发送,对于每一个tuple,所有的Bolts都会收到。
4、Global Grouping: 全局分组,这个tuple被分配到storm中的一个bolt的其中一个task。再具体一点就是分配给id值最低的那个task。
5、Non Grouping:不分组,这个分组的意思是说stream不关心到底谁会收到它的tuple。目前这种分组和Shuffle grouping是一样的效果,有一点不同的是storm会把这个bolt放到这个bolt的订阅者同一个线程里面去执行。
6、Direct Grouping:直接分组, 这是一种比较特别的分组方法,用这种分组意味着消息的发送者指定由消息接收者的哪个task处理这个消息。只有被声明为Direct Stream的消息流可以声明这种分组方法。而且这种消息tuple必须使用emitDirect方法来发射。消息处理者可以通过TopologyContext来获取处理它的消息的taskid (OutputCollector.emit方法也会返回taskid)
注:Local or shuffle grouping:如果目标bolt有一个或者多个task在同一个工作进程中,tuple将会被随机发生给这些tasks。否则,和普通的Shuffle Grouping行为一致。
Streams:消息流
消息流是一个没有边界的tuple序列,而这些tuples会被以一种分布式的方式并行创建和处理。每个tuple可以包含多列,字段类型可以是:integer,long,short,byte,string,double,float,boolean和byte array。你还可以自定义类型--只要实现了对应的序列化器。
Spout(获取消息源的组件)
spout是topology消息生产者。spout从一个外部源(消息队列)读取数据向topology发出tuple。消息源spouts可以使可靠地也可以是不可靠的。一个可靠地消息源可以重新发射一个处理失败的tuple,一个不可靠的消息源spouts不会。
spout累的方法nextTuple不断发射tuple到topology,storm在检测到一个tuple被整个topology成功处理的时候调用ack,否则调用fail。
storm只对可靠的spout调用ack和fail。
Bolt(消息处理者)
消息处理逻辑被封装在bolts里面,Bolts可以做很多事情:过滤,聚合,查询数据库等。Bolts可以简单的做消息流的传递。负责的消息流处理往往需要很多步骤,从而也就需要经过很多Bolts。第一级bolt的输出可以作为下一级bolt的输入。而spout只能有一级。
bolts的主要方法是execute(死循环)连续处理传入的tuple,成功处理完每一个tuple后可以回调OutputCollector的ack方法,以通知storm这个tuple被处理完成了。当处理失败时,可以调fail方法通知spout端可以重新发送改tuple。
流程是:bolts处理一个输入tuple,然后调用ack通知storm自己已经处理过这个tuple了。
storm提供了一个IBasicBolt会自动调用ack。
Bolts使用OutputCollector来发射tuple到下一级Blot。
消息的可靠性
一个消息(tuple)从spout发送出来,可能会导致成百上千的消息基于此消息被创建
“单词统计”的例子:
storm任务从数据源每次读取一个完整的英文句子;将这个句子分解为独立的单词,最后,实时的输出每个单词以及它出现过的次数。
每个从spout发送出来的消息(每个英文句子)都会触发很多的消息被创建,那些从句子中分隔出来的单词就是被创建出来的新消息。
这些消息构成一个树状结构,我们称之为“tuple tree”
一条数据在Spout中形成一个Tuple,然后交给一个个Bolt执行,那我们怎么保证这个Tuple被完整的执行了呢?这里的完整执行说的是这个Tuple必须在后面的每一个Bolt都成功处理,假设在一个Bolt中发生异常导致失败,这就不能算完整处理。
为了保证消息处理过程中的可靠性,storm使用了ack机制。storm会专门启动若干acker线程,来追踪tuple的处理过程。acker线程数量可以设置。
每一个Tuple在Spout中生成的时候,都会分配到一个64位的messageId。通过对messageId进行哈希我们可以执行要对哪个acker线程发送消息来通知它监听这个Tuple。
acker线程收到消息后,会将发出消息的Spout和那个messageId绑定起来。然后开始跟踪该tuple的处理流程。如果这个tuple全部都处理完,那么acker线程就会调用发起这个tuple的那个spout实例的ack()方法。如果超过一定时间这个tuple还没处理完,那么acker线程就会调用对应spout的fail()方法,通知spout消息处理失败。spout组件就可以重新发送这个tuple。
从上面的介绍我们知道了,tuple数据的流向会形成一个拓扑图,也可以理解成是一个tuple树。这个拓扑图的节点可能会有很多个,如果要把这些节点全部保存起来,处理大量的数据时势必会造成内存溢出。
对于这个难题,storm使用了一种非常巧妙的方法,使用20个字节就可以追踪一个tuple是否被完整的执行。这也是storm的一个突破性的技术
消息完整条件:
无论何时在tuple tree中创建了一个新的节点,我们需要明确的通知Storm;
当处理完一个单独的消息时,我们需要告诉Storm 这棵tuple tree的变化状态。
通过上面的两步,storm就可以检测到一个tuple tree何时被完全处理了,并且会调用相关的ack或fail方法。
在什么条件下,Storm才会认为一个从spout发送出来的消息被完整处理呢?
tuple tree不再生长
树中的任何消息被标识为“已处理”
集群整体架构
Nimbus和Sipervisor必须通过zookeeper集群进行交互
nimbus进程和sipervisor是无法直接链接和无状态的
所有的状态维持在zookeeper中或保存在本地磁盘上
这意味着可以直接kill -9 这两个进程而不需要做备份
这种设计使storm集群具有令人难以置信的稳定行,即无耦合
Worker
Supervisor会监听分配给它那台机器的工作,根据需要启动/关闭工作进程,这个工作进程就是worker
每一个worker都会占用工作节点的一个端口,这个端口可以在storm.yarm中配置。
一个topology可能会在一个或者多个工作进程里面执行,每个工作进程执行整个topology的一部分,所以一个运行的topology由运行在很多机器上的很多工作进程组成。
Task任务
每一个Spout和Bolt会被当作很多task在整个集群里面执行。默认情况下每一个task对应到一个线程(Executor),这个线程用来执行这个task,而stream grouping则是定义怎么从一堆task发射tuple到另外一堆task。
配置Configuration
storm里面有一堆参数可以配置来调整nimbus, supervisor以及正在运行的topology的行为, 一些配置是系统级别的, 一些配置是topology级别的。所有有默认值的配置的默认配置是配置在default.xml里面的。你可以通过定义个storm.xml在你的classpath里面来覆盖这些默认配置。并且你也可以在代码里面设置一些topology相关的配置信息 – 使用StormSubmitter。当然,这些配置的优先级是: default.xml < storm.xml < TOPOLOGY-SPECIFIC配置。