当前位置: 代码迷 >> 综合 >> Storm (分布式的、容错的实时流式计算系统) 基础概念
  详细解决方案

Storm (分布式的、容错的实时流式计算系统) 基础概念

热度:54   发布时间:2023-12-18 06:18:07.0
http://storm.apache.org/
Twitter将Storm正式开源了,这是一个分布式的、容错的实时计算系统,它被托管在 GitHub上,遵循 Eclipse Public License 1.0。Storm是由BackType开发的实时处理系统,BackType现在已在Twitter麾下。GitHub上的最新版本是Storm 0.8.0,基本是用 Clojure写的。
Storm为分布式实时计算提供了一组通用原语,可被用于“流处理”之中,实时处理消息并更新数据库。这是管理队列及工作者集群的另一种方式。 Storm也可被用于“连续计算”(continuous computation),对数据流做连续查询,在计算时就将结果以流的形式输出给用户。它还可被用于“分布式RPC”,以并行的方式运行昂贵的运算。 Storm的主工程师Nathan Marz表示:
Storm可以方便地在一个计算机集群中编写与扩展复杂的实时计算,Storm用于实时处理,就好比  Hadoop 用于批处理。Storm保证每个消息都会得到处理,而且它很快——在一个小集群中,每秒可以处理数以百万计的消息。更棒的是你可以使用任意编程语言来做开发。
Storm的主要特点如下:
简单的编程模型。类似于 MapReduce降低了并行批处理复杂性,Storm降低了进行实时处理的复杂性。
可以使用各种编程语言。你可以在Storm之上使用各种编程语言。默认支持Clojure、Java、Ruby和Python。要增加对其他语言的支持,只需实现一个简单的Storm通信协议即可。
容错性。Storm会管理工作进程和节点的故障。
水平扩展。计算是在多个线程、进程和服务器之间并行进行的。
可靠的消息处理。Storm保证每个消息至少能得到一次完整处理。任务失败时,它会负责从消息源重试消息。
快速。系统的设计保证了消息能得到快速的处理,使用?MQ作为其底层消息队列。
本地模式。Storm有一个“本地模式”,可以在处理过程中完全模拟Storm集群。这让你可以快速进行开发和单元测试。
Storm集群由一个主节点和多个工作节点组成。主节点运行了一个名为“Nimbus”的守护进程,用于分配代码、布置任务及故障检测。每个工作节 点都运行了一个名为“Supervisor”的守护进程,用于监听工作,开始并终止工作进程。Nimbus和Supervisor都能快速失败,而且是无 状态的,这样一来它们就变得十分健壮,两者的协调工作是由Apache ZooKeeper来完成的。
Storm的术语包括Stream、Spout、Bolt、Task、Worker、Stream Grouping和Topology。Stream是被处理的数据。Spout是数据源。Bolt处理数据。Task是运行于Spout或Bolt中的 线程。Worker是运行这些线程的进程。Stream Grouping规定了Bolt接收什么东西作为输入数据。数据可以随机分配(术语为Shuffle),或者根据字段值分配(术语为Fields),或者 广播(术语为All),或者总是发给一个Task(术语为Global),也可以不关心该数据(术语为None),或者由自定义逻辑来决定(术语为 Direct)。Topology是由Stream Grouping连接起来的Spout和Bolt节点网络。在Storm Concepts页面里对这些术语有更详细的描述。
可以和Storm相提并论的系统有Esper、Streambase、HStreaming和Yahoo S4。其中和Storm最接近的就是S4。两者最大的区别在于Storm会保证消息得到处理。Storm,如果需要持久化,可以使用一个类似于Cassandra或Riak这样的外部数据库。Storm是分布式数据处理的框架,本身几乎不提供复杂事件计算,而Esper、Streambase属于CEP系统。
入门的最佳途径是阅读GitHub上的官方《Storm Tutorial》。 其中讨论了多种Storm概念和抽象,提供了范例代码以便你可以运行一个Storm Topology。开发过程中,可以用本地模式来运行Storm,这样就能在本地开发,在进程中测试Topology。一切就绪后,以远程模式运行 Storm,提交用于在集群中运行的Topology。
要运行Storm集群,你需要Apache Zookeeper、?MQ、JZMQ、Java 6和Python 2.6.6。ZooKeeper用于管理集群中的不同组件,?MQ是内部消息系统,JZMQ是?MQ的Java Binding。有个名为storm-deploy的子项目,可以在AWS上一键部署Storm集群。关于详细的步骤,可以阅读Storm Wiki上的《Setting up a Storm cluster》。

出现的背景

在过去10 年中,随着互联网应用的高速发展,企业积累的数据量越来越大,越来越多。随着Google MapReduce、Hadoop 等相关技术的出现,处理大规模数据变得简单起来,但是这些数据处理技术都不是实时的系统,它们的设计目标也不是实时计算。毕竟实时的计算系统和基于批处理模型的系统(如Hadoop)有着本质的区别。
但是随着大数据业务的快速增长,针对大规模数据处理的实时计算变成了一种业务上的需求,缺少“实时的Hadoop 系统”已经成为整个大数据生态系统中的一个巨大缺失。Storm 正是在这样的需求背景下出现的,Storm 很好地满足了这一需求。
在Storm 出现之前,对于需要实现计算的任务,开发者需要手动维护一个消息队列和消息处理者所组成的实时处理网络,消息处理者从消息队列中取出消息进行处理,然后更新数据库,发送消息给其他队列。所有这些操作都需要开发者自己实现。这种编程实现的模式存在以下缺陷。
● 单调乏味性:开发者需要花费大部分时间去配置消息如何发送,消息发送到哪里,如何部署消息的处理者,如何部署消息的中间处理节点等。如果使用Storm 进行处理,那么开发者只需要很少的消息处理逻辑代码,这样开发者就可以专注于业务逻辑的开发,从而大大提高了开发实时计算系统的效率。
● 脆弱性:程序不够健壮,开发者需要自己编写代码以保证所有的消息处理者和消息队列的正确运行。
● 可伸缩性差:当一个消息处理者能处理的消息达到自己能处理的峰值时,就需要对消息流进行分流,这时需要配置新的消息处理者,以让它们处理分流消息。
对于需要处理大量消息流的实时系统来说,消息处理始终是实时计算的基础,消息处理的最后就是对消息队列和消息处理者之间的组合。消息处理的核心是如何在消息处理的过程中不丢失数据,而且可以使整个处理系统具有很好的扩展性,以便能够处理更大的消息流。而Storm 正好可以满足这些要求。

应用领域

Storm 有许多应用领域,包括实时分析、在线机器学习、信息流处理(例如,可以使用Storm 处理新的数据和快速更新数据库)、连续性的计算(例如,使用Storm 连续查询,然后将结果返回给客户端,如将微博上的热门话题转发给用户)、分布式RPC(远过程调用协议,通过网络从远程计算机程序上请求服务)、ETL(Extraction Transformation Loading,数据抽取、转换和加载)等。
Storm 的处理速度惊人,经测试,每个节点每秒可以处理100 万个数据元组。Storm 可扩展且具有容错功能,很容易设置和操作。Storm 集成了队列和数据库技术,Storm 拓扑网络通过综合的方法,将数据流在每个数据平台间进行重新分配。图1 所示为Storm 处理消息流的示意图。
图1 Storm 数据流图
在Storm 消息处理模型中,消息源就像图1 中所示的水龙头,它可以源源不断地将消息发送到消息处理者那里,消息处理者接收消息,进行处理,然后消息处理者既可以将消息发送到其他消息处理者那里,或者不发送消息,表示消息处理结束。

设计特征

如同Hadoop 由于定义了并行计算原语,大大简化了对大规模数据的并行批处理一样,Storm 也为实时计算定义了一些计算原语,从而简化了并行实时数据处理的复杂性。Storm 在官方网站中列举了它的几大关键特征。
● 适用场景广:Storm 可以用来处理消息和更新数据库(消息的流处理),对一个数据量进行持续的查询并将结果返回给客户端(连续计算),对于耗费资源的查询进行并行化处理(分布式方法调用),Storm 提供的计算原语可以满足诸如以上所述的大量场景。
● 可伸缩性强:Storm 的可伸缩性可以让Storm 每秒处理的消息量达到很高,如100 万。实现计算任务的扩展,只需要在集群中添加机器,然后提高计算任务的并行度设置。Storm 网站上给出了一个具有伸缩性的例子,一个Storm应用在一个包含10 个节点的集群上每秒处理1 000 000 个消息,其中包括每秒100 多次的数据库调用。Storm 使用Apache ZooKeeper 来协调集群中各种配置的同步,这样Storm 集群可以很容易地进行扩展。
● 保证数据不丢失:实时计算系统的关键就是保证数据被正确处理,丢失数据的系统使用场景会很窄,而Storm 可以保证每一条消息都会被处理,这是Storm 区别于S4(Yahoo 开发的实时计算系统)系统的关键特征。
● 健壮性强:不像Hadoop 集群很难进行管理,它需要管理人员掌握很多Hadoop 的配置、维护、调优的知识。而Storm 集群很容易进行管理,容易管理是Storm 的设计目标之一。
● 高容错:Storm 可以对消息的处理过程进行容错处理,如果一条消息在处理过程中失败,那么Storm 会重新安排出错的处理逻辑。Storm 可以保证一个处理逻辑永远运行。
● 语言无关性:Storm 应用不应该只能使用一种编程平台,Storm 虽然是使用Clojure 语言开发实现,但是,Storm 的处理逻辑和消息处理组件都可以使用任何语言来进行定义,这就是说任何语言的开发者都可以使用Storm。

关键概念

(1)计算拓扑(Topologies)
在 Storm 中,一个实时计算应用程序的逻辑被封装在一个称为Topology 的对象中,也称为计算拓扑。Topology 有点类似于Hadoop 中的MapReduce Job,但是它们之间的关键区别在于,一个MapReduce Job 最终总是会结束的,然而一个Storm 的Topology 会一直运行。在逻辑上,一个Topology 是由一些Spout(消息的发送者)和Bolt(消息的处理者)组成图状结构,而链接Spouts 和Bolts 的则是Stream Groupings。
(2)消息流(Streams)
消息流是 Storm 中最关键的抽象,一个消息流就是一个没有边界的tuple序列,tuple 是一种Storm 中使用的数据结构,可以看作是没有方法的Java 对象。这些tuple 序列会被一种分布式的方式并行地在集群上进行创建和处理。对消息流的定义主要就是对消息流里面的tuple 进行定义,为了更好地使用tuple,需要给tuple 里的每个字段取一个名字,并且不同的tuple 字段对应的类型要相同,即两个tuple 的第一个字段类型相同,第二个字段类型相同,但是第一个字段和第二个字段的类型可以不同。默认情况下,tuple 的字段类型可以为integer、long、short、byte、string、double、float、boolean 和byte array 等基本类型,也可以自定义类型,只需要实现相应的序列化接口。每一个消息流在定义的时候需要被分配一个id,最常见的消息流是单向的消息流,在Storm 中OutputFieldsDeclarer 定义了一些方法,让你可以定义一个Stream 而不用指定这个id。在这种情况下,这个Stream 会有个默认的id: 1。
(3)消息源(Spouts)
Spouts 是Storm 集群中一个计算任务(Topology)中消息流的生产者,Spouts一般是从别的数据源(例如,数据库或者文件系统)加载数据,然后向Topology中发射消息。在一个Topology 中存在两种Spouts,一种是可靠的Spouts,一种是非可靠的Spouts,可靠的Spouts 在一个tuple 没有成功处理的时候会重新发射该tuple,以保证消息被正确地处理。不可靠的Spouts 在发射一个tuple 之后,不会再重新发射该tuple,即使该tuple 处理失败。每个Spouts 都可以发射多个消息流,要实现这样的效果,可以使用OutFieldsDeclarer.declareStream 来定义多个Stream,然后使用SpoutOutputCollector 来发射指定的Stream。
在Storm 的编程接口中,Spout 类最重要的方法是nextTuple()方法,使用该方法可以发射一个消息tuple 到Topology 中,或者简单地直接返回,如果没有消息要发射。需要注意的是,nextTuple 方法的实现不能阻塞Spout,因为Storm在同一线程上调用Spout 的所有方法。Spout 类的另外两个重要的方法是ack()和fail(),一个tuple 被成功处理完成后,ack()方法被调用,否则就调用fail()方法。注意,只有对于可靠的Spout,才会调用ack()和fail()方法。
(4)消息处理者(Bolts)
所有消息处理的逻辑都在Bolt 中完成,在Bolt 中可以完成如过滤、分类、聚集、计算、查询数据库等操作。Bolt 可以做简单的消息处理操作,例如,Bolt 可以不做任何操作,只是将接收到的消息转发给其他的Bolt。Bolt 也可以做复杂的消息流的处理,从而需要很多个Bolt。在实际使用中,一条消息往往需要经过多个处理步骤,例如,计算一个班级中成绩在前十名的同学,首先需要对所有同学的成绩进行排序,然后在排序过的成绩中选出前十名的
成绩的同学。所以在一个Topology 中,往往有很多个Bolt,从而形成了复杂的流处理网络。
Bolts 不仅可以接收消息,也可以像Spout 一样发射多条消息流,可以使用OutputFieldsDeclarer.declareStream 定义Stream,使用OutputCollector.emit 来选择要发射的Stream。在编程接口上,Bolt 类中最终需要的方法是execute()方法,该方法的参数就是输入Tuple,Bolt 使用OutputCollector 发送消息tuple,Bolt 对于每个处理过的消息tuple 都必须调用OutputCollector 的ack()方法,通知Storm 这个消息被处理完成,最终会通知到发送该消息的源,即Spout。消息在Bolt 中的处理过程一般是这样,Bolt 将接收到的消息tuple 进行处理,然后发送0 个或多个消息tuple,之后调用OutputCollector 的ack()方法通知消息的发送者。
(5)Stream Groupings(消息分组策略)
定义一个 Topology 的其中一步是定义每个Bolt 接收什么样的流作为输入。Stream Grouping 就是用来定义一个Stream 应该如何分配给Bolts 上面的多个Tasks。Storm 里面有6 种类型的Stream Grouping。
● Shuffle Grouping:随机分组,随机派发Stream 里面的tuple,保证每个Bolt 接收到的tuple 数目相同。
● Fields Grouping:按字段分组,比如按userid 来分组,具有同样userid 的tuple 会被分到相同的Bolts,而不同的userid 则会被分配到不同的Bolts。
● All Grouping:广播发送,对于每一个tuple,所有的Bolts 都会收到。
● Global Grouping: 全局分组,这个tuple 被分配到Storm 中一个Bolt 的其中一个Task。再具体一点就是分配给id 值最低的那个Task。
● Non Grouping:不分组,这个分组的意思是Stream 不关心到底谁会收到它的tuple。目前这种分组和Shuffle Grouping 是一样的效果,有一点不同的是Storm 会把这个Bolt 放到此Bolt 的订阅者同一个线程里面去执行。
● Direct Grouping:直接分组,这是一种比较特别的分组方法,用这种分组意味着消息的发送者指定由消息接收者的哪个Task 处理这个消息。只有被声明为Direct Stream 的消息流可以声明这种分组方法。而且这种消息tuple 必须使用emitDirect 方法来发送。消息处理者可以通过TopologyContext 来获取处理它的消息的taskid(OutputCollector.emit 方法也会返回taskid)。
(6)可靠性(Reliability)
Storm 可以保证每个消息tuple 会被Topology 完整地处理,Storm 会追踪每个从Spout 发送出的消息tuple 在后续处理过程中产生的消息树(Bolt 接收到的消息完成处理后又可以产生0 个或多个消息,这样反复进行下去,就会形成一棵消息树),Storm 会确保这棵消息树被成功地执行。Storm 对每个消息都设置了一个超时时间,如果在设定的时间内,Storm 没有检测到某个从Spout 发送的tuple 是否执行成功,Storm 会假设该tuple 执行失败,因此会重新发送该tuple。这样就保证了每条消息都被正确地完整地执行。
Storm 保证消息的可靠性是通过在发送一个tuple 和处理完一个tuple 的时候都需要像Storm 一样返回确认信息来实现的,这一切是由OutputCollector 来完成的。通过它的emit 方法来通知一个新的tuple 产生,通过它的ack 方法通知一个tuple 处理完成。
(7)任务(Tasks)
在 Storm 集群上,每个Spout 和Bolt 都是由很多个Task 组成的,每个Task对应一个线程,流分组策略就是定义如何从一堆Task 发送tuple 到另一堆Task。在实现自己的Topology 时可以调用TopologyBuilder.setSpout() 和TopBuilder.setBolt()方法来设置并行度,也就是有多少个Task。
(8)工作进程(Worker)
一个 Topology 可能会在一个或者多个工作进程里面执行,每个工作进程执行整个Topology 的一部分。比如,对于并行度是300 的Topology 来说,如果我们使用50 个工作进程来执行,那么每个工作进程会处理其中的6 个Tasks(其实就是每个工作进程里面分配6 个线程)。Storm 会尽量均匀地把工作分配给所有的工作进程。
(9)配置
在 Storm 里面可以通过配置大量的参数来调整Nimbus、Supervisor 以及正在运行的Topology 的行为,一些配置是系统级别的,一些配置是Topology 级别的。所有有默认值的配置的默认配置是配置在default.xml 里面的,用户可以通过定义一个storm.xml 在classpath 里来覆盖这些默认配置。并且也可以使用Storm Submitter 在代码里面设置一些Topology 相关的配置信息。当然,这些配置的优先级是default.xml<storm.xml<TOPOLOGY-SPECIFIC 配置。

集群中的组件

Storm 的集群从形式上看和Hadoop 的集群非常相似,也是采用主从架构。但是在Hadoop 上面运行的是MapReduce 的Job, 而在Storm 上面运行的是Topology。它们有很大的不同。它们之间的关键区别是,一个MapReduceJob 会有启动、运行到最终会结束的过程,而一个Topology 在启动后,会永远运行。
在Storm 的集群里面有两种节点:控制节点和工作节点。控制节点上面运行一个后台进程Nimbus,它的作用类似于Hadoop 里面的JobTracker。Nimbus 负责在集群里面分发执行代码,分配工作给工作节点,并且监控任务的执行状态。每一个工作节点上面运行一个叫作Supervisor 的守护进程。Supervisor 会监听分配给自己所在机器的工作,根据需要启动/关闭工作进程。每一个工作进程执行一个Topology 的一个子集,一个运行的Topology 由运行在很多机器上的很多工作进程组成。
集群中,除了控制节点和工作节点外,Storm 集群使用Apache Zookeeper集群来作为自己的协调系统。下面给出Storm 集群的组织架构,如图2所示。
图2 Storm 集群的组织结构 图2 Storm 集群的组织结构
Nimbus 和Supervisor 之间的所有协调工作都是通过一个Zookeeper 集群来完成的。并且,Nimbus 进程和Supervisor 都是快速失败和无状态的。所有的状态要么在Zookeeper 里面,要么在本地磁盘上。这也就意味着你可以用kill -9来“杀死”Nimbus 和Supervisor 进程,然后再重启它们,它们可以继续工作,就好像什么都没有发生过似的。这个设计使得Storm 的运行可以非常稳定。

高效实现信息的可靠性

在Storm中运行着一类特殊的Task,称为acker。acker 能够监控每个从Spout发送的tuple 产生的消息树。当acker 发现一个tuple 产生的消息树被完整地处理后,会向产生该tuple 的Task 发送一条通知消息,表示该消息已经执行完成。用户可以通过配置Config.TOPOLOGY_ACKERS 的值来设置一个Topology 中acker 的数量,默认值为1。如果一个Topology 中tuple 的数量较多,就可以将acker 的数量设置得大一些,以此提高整个Topology 运行时的性能。

容错

Storm 的容错分为如下几种类型。
(1)工作进程worker 失效:如果一个节点的工作进程worker“死掉”,supervisor 进程会尝试重启该worker。如果连续重启worker 失败或者worker 不能定期向Nimbus 报告“心跳”,Nimbus 会分配该任务到集群其他的节点上执行。
(2)集群节点失效:如果集群中某个节点失效,分配给该节点的所有任务会因超时而失败,Nimbus 会将分配给该节点的所有任务重新分配给集群中的其他节点。
(3)Nimbus 或者supervisor 守护进程失败:Nimbus 和supervisor 都被设计成快速失败(遇到未知错误时迅速自我失败)和无状态的(所有的状态信息都保存在Zookeeper 上或者是磁盘上)。Nimbus 和supervisor 守护进程必须在一些监控工具(例如,daemontools 或者monitor)的辅助下运行,一旦Nimbus 或者supervisor 失败,可以立刻重启它们,整个集群就好像什么事情也没发生。最重要的是,没有工作进程worker 会因为Nimbus 或supervisor 的失败而受到影响,Storm 的这个特性和Hadoop 形成了鲜明的对比,如果JobTracker 失效,所有的任务都会失败。
(4)Nimbus 所在的节点失效:如果Nimbus 守护进程驻留的节点失败,工作节点上的工作进程worker 会继续执行计算任务,而且,如果worker 进程失败,supervisor 进程会在该节点上重启失败的worker 任务。但是,没有Nimbus的影响时,所有worker 任务不会分配到其他的工作节点机器上,即使该worker所在的机器失效。