1.简介
Storm是由Twitter公司开源的一个实时分布式流处理系统,被广泛应用在实时分析、在线机器学习连续计算(continuous computation)、分布式RPC、ETL等场景。Storm支持水平扩展、具有高容错性,保证数据能被处理,而且处理速度很快。Storm还支持多种编程语言,易于部署和管理,是目前广泛使用的流处理系统之一。
2.系统架构
图5.17 Storm系统架构[4]
图5.17是整个Storm系统的架构,Storm运行在一个分布式的集群上。Storm集群采用的是主从架构方式,Storm的集群有两类节点:主控节点和工作节点,集群的各个组件如下:
(1) Nimbus
运行Nimbus的节点是Storm集群的主控节点,Nimbus类似Hadoop中JobTracker的角色,是用户和Storm系统之间的交互点。Nimbus主要的工作是用于用户提交任务、分配集群任务、集群监控等。
(2) Supervisor
每个工作节点上都运行着一个Supervisor,Supervisor用于接收Nimbus分配的任务,并根据分配产生worker进程。同时它还会监视worker的健康状况,在必要的情况下会重启worker进程。
(3) ZooKeeper
Nimbus和Supervisor之间的所有协调工作都是通过ZooKeeper集群完成的。另外,Nimbus和Supervisor进程都是快速失败(fail-fast)和无状态(stateless)的,所有的状态要么在本地磁盘上要么在ZooKeeper上。这就是说,如果Nimbus服务失败了,所有的worker还是会继续执行完成当前的拓扑;如果worker失败了,那么Supervisor会重启它们,这样的设计保证了Storm的可靠性。但是这样的设计上依然存在一些问题,比如 Nimbus的机器宕机会造成用户就不能再提交拓扑;运行拓扑的机器宕机了,在Nimbus进程恢复之前,这些机器上task将不会被重新分配到其他机器。因此,在Storm1.0.0之后Nimbus就被设计成高可用(HA)。
3. 一致性语义
Storm支持三种计算语义,at-most-once、at-least-once和exactly-once。
at-most-once:每个tuple最多被处理一次,即编程时不需要实现fail和ack方法,每个spout针对相同的tuple只发送一次。
at-least-once:保证每个tuple至少会被拓扑完整执行一次,Storm通过acker机制追踪每个tuple所产生的tuple树可以确定tuple是否已经完成处理。而且拓扑会有一个消息超时时间的设置,如果在规定时间内tuple树没有被执行成功,那么拓扑会将这个tuple标记为执行失败,然后重发这个tuple。这样可以保证每个tuple至少被处理一次,但是这样可能会出现一个tuple会被多次处理的情况。
exactly-once:保证每个tuple有且只被处理一次,效率不高。Storm提供了一套事务性组件TransactionalTopology来解决这个问题,目前这个组件已经不再维护,现在由Trident来实现Topology,但是原理是一样的。具体的原理和使用可以参考官网的文档。
Storm并不像Spark Streaming那样考虑了慢节点问题,因此无法保证强一致性(参考Spark Streaming那章)。
4. Apache Storm编程模型
4.1 数据模型
流(Stream)是Storm的一个重要抽象概念。一个Stream是一个无边界的tuple序列,而这些tuple序列是以分布式的方式创建和处理的。tuple是消息传递中的基本单元,数据结构如下图所示:
图5.19 tuple数据结构
一个tuple可以包含多个field,每个field表示一个属性,tuple字段名称已经事先预定好了,因此tuple就是一个值列表。一个没有边界、源源不断的tuple序列就组成了Stream。
4.2 计算模型
其计算模型,主要由以下概念:
(1) Topology
在Storm中,用户提交的应用被表示为一个Topology(拓扑),类似于MapReduce中的一个job,但区别在于这个拓扑会永远运行(或者直到手动结束)。用户通过Streaming Grouping将Spout和Bolt连接起来构成一个拓扑。
(2) Spout
消息源Spout是拓扑中消息流的来源。通常Spout会从外部源读取数据并将其发送到拓扑中。Spout可以是可靠的也可以是不可靠的,可靠的Spout可以在一个tuple没有被Storm成功处理时重发这个tuple,以确保tuple至少会被处理一次,但是不可靠的Spout一旦发送完一个tuple就不能重发了。
(3) Bolt
所有对消息流的处理都是在Bolt中实现的,Bolt可以做很多事情:过滤、聚合、join操作等等。Bolt每处理完一个tuple后,可以发送0个或多个tuple给下游的Bolt。
(4) Task
每个spout/bolt会被当作很多task在集群中运行,一个task就是一个spout/bolt的实例。
(5) Worker和Executor
一个拓扑会在一个或者多个worker进程执行,每个worker都是一个物理JVM且执行的只是拓扑的一部分,一个worker只能为一个拓扑执行executor。每个worker会在一个JVM中运行一个或多个executor,而每个executor对应一个线程,可以运行多个task。executor会在每次循环里顺序调用所有的task,在Storm中一个executor默认对应一个task。
(6) Streaming Grouping
Streaming Grouping定义了一个消息流是如何在两个组件之间分发的,要记住bolt和spout在集群中是由多个task实例并行执行的。如果从task的角度来看一个拓扑tuple流在组件之间的分发的话,应该如下图所示:
图5.20 StreamingGrouping[14]
图5.20中,圆圈代表的是task,方框代表的是组件spout/bolt,组件之间的箭头表示的是消息流在组件之间的分发,注意每个组件的task不一定都在同一个worker进程中,上图是从task的角度来看流分组的过程。具体有哪些分发策略,可以回顾之前讲的流数据分组。
4.3 基本操作
Storm编写代码,Spout是发送数据的源,Bolt是处理逻辑组件,这些组件实现是用户自己实现的。Storm提供一些组件间的分发策略,可以回顾之前讲的流数据分组,如Shuffle Grouping、Fields Grouping等策略,这些策略是Strom实现好的,用户只需要用分组策略将这些组件组成一个DAG即可。
4.4 编程模型实例分析
编写程序时,我们需要实现相应的Spout组件、Bolt处理逻辑组件的代码,然后将这些组件组成一个拓扑提交到集群即可。下面以一个单词计数的组件作为示例来了解编程模型,
图XXX-单词计数拓扑
图来源:Storm-SIGMOD14
该拓扑定义了三个组件TweetSpout、ParseTweetBolt和WordCountBolt,作用分别是发送推文tuple、对推文tuple进行单词分割、统计每个单词的数量。每个组件的并行度(即instance数量)在代码中指定,注意TweetSpout和ParseTweetBolt组件之间用的是Shuffle Grouping的分发策略,是为了使ParseTweetBolt的实例任务被分配的任务均匀些,而ParseTweetBolt和WordCountBolt组件之间用的是Fields Grouping策略,保证了相同单词一定在同一个WordCountBolt实例中统计个数。
Topology核心代码:
文本框: //创建一个TopologyBuilder TopologyBuilder tb = new TopologyBuilder(); //将Spout、Bolt连接成一个拓扑,并且指定每个组件运行的executor数量和组件之间的shuffle方式,默认一个executor使用一个task实例 tb.setSpout("SpoutBolt", new CreateSentenceSpout(), 2); tb.setBolt("SplitBolt", new SpiltWordBolt(), 2).shuffleGrouping("SpoutBolt"); tb.setBolt("CountBolt", new CountBolt(), 4).fieldsGrouping("SplitBolt", new Fields("word")); //创建配置 Config conf = new Config(); //设置worker数量 conf.setNumWorkers(2);//每一个Worker 进程默认都会对应一个Acker 线程,用于容错 //提交任务 //集群提交 //StormSubmitter.submitTopology("myWordcount", conf, tb.createTopology()); //本地提交 LocalCluster localCluster = new LocalCluster(); localCluster.submitTopology("myWordcount", conf, tb.createTopology());
4.5 Topology的并行度
通过前面的小节知道,用户提交的应用被表示为一个topology,然后提交到Storm集群中运行的。在Storm集群中运行的topology主要有三个主体:worker进程、Executor(线程)、task(任务),三个主体之间的关系在上节已经讲过,下面通过一个例子来理解上节的概念和一个topology的并行度。
图5.21 拓扑并行度计算[15]
如图5.21所示,该拓扑由三个组件构成:BlueSpout、GrennBolt和YellowBolt,形成了一个有向无环图(DAG)。这三个组件可以分别生成多个线程来进行并行处理。图中,这三个组件对应的线程个数分别被设置为2、2、6,以每个线程处理的单位在Storm中被称为executor。假设运行这个拓扑的worker进程数量是2个(worker可以在一个或多个节点上),那么每个worker将会分配10/2=5个线程。如上图所示,最后每个worker分配了1个有两个task的GreenBolt的线程、1个有一个task的BlueBolt的线程、3个有一个task的YellowBolt的线程。
每个executor默认对应一个task进行计算,然而Storm中也可以进一步设置每个executor生成多个task实例来进行并行计算。例如,图中GreenBolt每个Executor的task数为2,BlueSpout和YellowBolt的task数为1。因此,在图中,GreenBolt的实际task并行度为4。