storm源码之storm代码结构【译】

说明:本文翻译自Storm在GitHub上的官方Wiki中提供的Storm代码结构描述一节Structure of the codebase,希望对正在基于Storm进行源码级学习和研究的朋友有所帮助。

Storm的源码共分为三个不同的层次。

首先,Storm在设计之初就考虑到了兼容多语言开发。Nimbus是一个thrift服务,topologies被定义为Thrift结构体.

Thrift优势 : 使得Storm可以被任意开发语言使用。

其次,Storm的所有接口都是Java语言来定义的。因此,尽管Storm中的很多功能实现都是Clojure代码,但是使用这些功能都必须通过Java API。这意味着Storm的所有特性对于Java来讲都是可用的。

第三,Storm的很大一部分实现都是Clojure代码。从代码行来看,差不多是一半Java代码,一半Clojure代码。但是由于Clojure在表达能力上更为见长,因此,实际上绝大多数逻辑的实现都是Clojure来做的。

接下来的小节里将会逐个详细解释这三个层次。

storm.thrift

要理解Storm的代码结构,首先需要看的是storm.thrift文件。

Storm使用了从这里folk出来的Thrift版本来自动生成代码。这个Thrift版本实际上是将所有的Java packages都重命名为"org.apache.thrift7"之后的Thrift 7。除此之外,它与Thrfit 7是完全一样的。之所以单独出这样一个Thrift版本一是考虑到Thrift缺少向后兼容,而是为了避免包名冲突以满足一些用户在他们自己的topologies中用到其他版本的thrift。

一个topology中的任何一个spout或bolt都会被用户指定一个唯一标识,称为"component id"。当描述1个bolt接收其他哪些spout或bolt的输出时需要用到这个"component id"。StormTopology结构中保存了1个map来保存"component id"到"component"的映射关系,这个映射关系包含所有的component类型(即所有的spout、bolt)。

Thrift对Spout或bolt的定义是相同的,因此我们只需要看一下bolt的thrift定义。它包含了1个"ComponentObject"结构和1个"ComponentCommon"结构。

"ComponentObject"即是bolt的实现实体。它可以是以下三个类型之一:

  1. 1个序列化的java对象(这个对象实现IBolt接口)
  2. 1个"ShellComponent"对象,意味着bolt是由其他语言实现的。如果以这种方式来定义1个bolt,Storm将会实例化1个ShellBolt对象来负责处理基于JVM的worker进程与非JVM的component(即该bolt)实现体之间的通讯。
  3. 1个"JavaObject"结构,这个结构告诉Storm实例化这个bolt所需要得classname和构造函数参数。这一点在你想用非JVM语言来定义topology时比较有用。这样,在你使用非JVM语言来定义topology时就可以做到既使用基于JVM的spout或bolt,同时又不需要创建并序列化它们的Java对象。

"ComponentCommon"定义了这个component的其他所有属性。包括:

  1. 这个component发射什么stream以及stream的元数据(是否是direct stream,stream中field的声明)
  2. 这个component接收什么stream(被定义在1个component_id到stream_id的map里,在stream做分组时用到)
  3. 这个component的并行度
  4. 这个component的配置项configuration

注意,在spout的结构中同样有"ComponentCommon"字段,因此,spout也是可以被声明接收其他的stream输入。然而,Storm Java API并没有提供一种方式指定spout接收什么stream,同时如果你在这里指定1个spout的输入声明,在提交这个topology时将会出现报错信息。之所以这样设计,是因为spout的输入声明不是让用户自己来使用的,而是Storm内部使用的。Storm会在内部自动向topology添加stream和bolt来构造acking framework,其中的两个stream就是从acker bolt发出给topology中的所有spout节点的。只要1个tuple树被检测到完成了或失败了,acker就会通过这两个stream分别发出"ack"或"fail"消息。将用户提交的topology转换成运行时的topology的代码可参见这里

Java接口

Storm的接口定义都是Java接口。主要的接口如下:

  1. IRichBolt
  2. IRichSpout
  3. TopologyBuilder

这样定义这些接口的主要意图在于:

  1. 以Java语言来定义接口
  2. 基于此接口,可以做到在不同的场合,提供出各自最适合的默认实现基类

这一策略的实际运用可以参考BaseRichSpout

Spout和bolt就是按照以上接口描述的方式被序列化到topology的Thrift定义结构中。

值得一提的一个细节是,IBolt、ISpout与IRichBolt、IRichSpout这两对接口是有区别的。它们主要区别是在"Rich"版本里增加了"declareOutputFields"方法。这样设计的原因是所有的输出stream的输出field声明都必须是在Thrift结构里的(这样就可以做到使用任何编程语言来声明了),但是用户又希望能够在自己的class中来声明stream输出field信息。为解决这个问题,"TopologyBuilder"在构造Thrift结构时就是通过调用"declareOutputFields"方法来得到输出field的声明,然后将其转换纳入Thrift结构。这个转换操作可以从"TopologyBuilder"代码中的这一段里看到。

接口实现

通过将Storm所有的接口都由Java语言来定义确保了Storm的所有功能对于Java来讲都是可使用的。同时,Java接口的使用也使得Java用户在使用Storm时体验更好。

应该说,Storm主要是由Clojure语言实现的。尽管从代码行数上看一半是Java一半是Clojure,但其实里面绝大多数的逻辑实现都是Clojure。有两个值得一提的例外就是DRPC支持事务的topology,它们二者都纯Java实现的。这样做的主要目的是来展示如何基于Storm,实现Storm之上更高层次的抽象。DRPC和支持事务的topology的实现分别位于backtype.storm.coordinationbacktype.storm.transactional包里。

这里总结了一份主要的Java包和Clojure命名空间的内容列表:

Java包

  • backtype.storm.coordination:实现了DRPC和事务性topology里用到的基于Storm的批处理功能。这个包里最重要得类是CoordinatedBolt
  • backtype.storm.drpc:DRPC的更高层次抽象的具体实现
  • backtype.storm.generated:自动生成的Thrift代码(利用这里folk出来的Thrift版本生成的,主要是把org.apache.thrift包重命名成org.apache.thrift7来避免与其他Thrift版本的冲突)
  • backtype.storm.grouping:包含了用户实现自定义stream分组类时需要用到的接口
  • backtype.storm.hooks:定义了处理storm各种事件的钩子接口,例如当task发射tuple时、当tuple被ack时。关于钩子的手册详见这里
  • backtype.storm.serialization:storm序列化/反序列化tuple的实现。在Kryo之上构建。
  • backtype.storm.spout:spout及相关接口的定义(例如"SpoutOutputCollector")。也包括了"ShellSpout"来实现非JVM语言定义spout的协议。
  • backtype.storm.task:bolt及相关接口的定义(例如"OutputCollector")。也包括了"ShellBolt"来实现非JVM语言定义bolt的协议。最后,"TopologyContext"也是在这里定义的,用来在运行时供spout和bolt使用以获取topology的执行信息。
  • backtype.storm.testing:包括了storm单元测试中用到的各种测试bolt及工具。
  • backtype.storm.topology:在Thrift结构之上的Java层,用以提供一个纯Java API来使用Storm(用户不需要了解Thrift的细节)。"TopologyBuilder"及不同spout和bolt的基类们也在这里定义。稍高一层次的接口"IBasicBolt"也在这里定义,它会使得创建某些特定类型的bolt会更加简洁。
  • backtype.storm.transactional:包括了事务性topology的实现。
  • backtype.storm.tuple:包括Storm中tuple数据模型的实现。
  • backtype.storm.utils:包含了Storm源码中用到的数据结构及各种工具类。

Clojure 命名空间

    • backtype.storm.bootstrap:包括了1个有用的宏来引入源码中用到的所有类及命名空间。
    • backtype.storm.clojure:包括了利用Clojure为Storm定义的特定领域语言(DSL)。
    • backtype.storm.cluster:Storm守护进程中用到的Zookeeper逻辑都封装在这个文件中。这部分代码提供了API来将整个集群的运行状态映射到Zookeeper的"文件系统"上(例如哪里运行着怎样的task,每个task运行的是哪个spout/bolt)。
    • backtype.storm.command.*:这些命名空间包括了各种"storm xxx"开头的客户端命令行的命令实现。这些实现都很简短。
    • backtype.storm.config:Clojure中config的读取/解析实现。同时也包括了工具函数来告诉nimbus、supervisor等守护进程在各种情况下应该使用哪些本地目录。例如:"master-inbox"函数会返回本地目录告诉Nimbus应该将上传给它的jar包保存到哪里。
    • backtype.storm.daemon.acker:"acker" bolt的实现。这是Storm确保数据被完全处理的关键组成部分。
    • backtype.storm.daemon.common:Storm守护进程用到的公共函数,例如根据topology的名字获取其id,将1个用户定义的topology映射到真正运行的topology(真正运行的topology是在用户定义的topology基础上添加了ack stream及acker bolt,参见system-topology!函数),同时包括了各种心跳及Storm中其他数据结构的定义。
    • backtype.storm.daemon.drpc:包括了DRPC服务器的实现,用来与DRPC topology一起使用。
    • backtype.storm.daemon.nimbus:包括了Nimbus的实现。
    • backtype.storm.daemon.supervisor:包括了Supervisor的实现。
    • backtype.storm.daemon.task:包括了spout或bolt的task实例实现。包括了处理消息路由、序列化、为UI提供的统计集合及spout、bolt执行动作的实现。
    • backtype.storm.daemon.worker:包括了worker进程(1个worker包含很多的task)的实现。包括了消息传输和task启动的实现。
    • backtype.storm.event:包括了1个简单的异步函数的执行器。Nimbus和Supervisor很多场合都用到了异步函数执行器来避免资源竞争。
    • backtype.storm.log:定义了用来输出log信息给log4j的函数。
    • backtype.storm.messaging.*:定义了1个高一层次的接口来实现点对点的消息通讯。工作在本地模式时Storm会使用内存中的Java队列来模拟消息传递。工作在集群模式时,消息传递使用的是ZeroMQ。通用的接口在protocol.clj中定义。
    • backtype.storm.stats:实现了向Zookeeper中写入UI使用的统计信息时如何进行汇总。实现了不同粒度的聚合。
    • backtype.storm.testing:包括了测试Storm topology的工具。包括时间仿真,运行一组固定数量的tuple然后获得输出快照的"complete-topology","tracker topology"可以在集群"空闲"时做更细粒度的控制操作,以及其他工具。
    • backtype.storm.thrift:包括了自动生成的Thrift API的Clojure封装以使得使用Thrift结构更加便利。
    • backtype.storm.timer:实现了1个后台定时器来延迟执行函数或者定时轮询执行。Storm不能使用Java里的Timer类,因为为了单测Nimbus和Supervisor,必须要与时间仿真集成起来使用。
    • backtype.storm.ui.*:Storm UI的实现。完全独立于其他的代码,通过Nimbus的Thrift API来获取需要的数据。
    • backtype.storm.util:包括了Storm代码中用到的通用工具函数。
    • backtype.storm.zookeeper:包括了Clojure对Zookeeper API的封装,同时也提供了一些高一层次的操作例如:"mkdirs"、"delete-recursive"
时间: 2024-11-06 11:59:45

storm源码之storm代码结构【译】的相关文章

storm源码之storm代码结构【译】【转】

[原]storm源码之storm代码结构[译] 说明:本文翻译自Storm在GitHub上的官方Wiki中提供的Storm代码结构描述一节Structure of the codebase,希望对正在基于Storm进行源码级学习和研究的朋友有所帮助. Storm的源码共分为三个不同的层次. 首先,Storm在设计之初就考虑到了兼容多语言开发.Nimbus是一个thrift服务,topologies被定义为Thrift结构体.Thrift的运用使得Storm可以被任意开发语言使用. 其次,Stor

storm源码之一个class解决nimbus单点问题【转】

[原]storm源码之一个class解决nimbus单点问题 一.storm nimbus 单节点问题概述 1.storm集群在生产环境部署之后,通常会是如下的结构:                                         从图中可以看出zookeeper和supervisor都是多节点,任意1个zookeeper节点宕机或supervisor节点宕机均不会对系统整体运行造成影响,但nimbus和ui都是单节点.ui的单节点对系统的稳定运行没有影响,仅提供storm-ui

Storm源码分析--Nimbus-data

nimbus-datastorm-core/backtype/storm/nimbus.clj (defn nimbus-data [conf inimbus] (let [forced-scheduler (.getForcedScheduler inimbus)] {:conf conf :inimbus inimbus ; INimbus实现类, standalone-nimbus的返回值 :submitted-count (atom 0) ; 已经提交的计算拓扑的数量, 初始值为原子值0

JStorm与Storm源码分析(七)--BasicBoltExecutor与装饰模式

在Storm中IBasicBolt的主要作用是为用户提供一种更为简单的Bolt编写方式,更为简单体现在Storm框架本身帮你处理了所发出消息的Ack.Fail和Anchor操作,而这部分操作是由执行器BasicBoltExecutor 实现的. 下面我们看一下BasicBoltExecutor的源码: /** * BasicBoltExecutor实现了IRichBolt接口 * 在该类中持有一个IBasicBolt成员变量用于调用转发 * 说明: * 该类是基于装饰模式实现的. */ publ

storm源码之理解Storm中Worker、Executor、Task关系【转】

[原]storm源码之理解Storm中Worker.Executor.Task关系 Storm在集群上运行一个Topology时,主要通过以下3个实体来完成Topology的执行工作:1. Worker(进程)2. Executor(线程)3. Task 下图简要描述了这3者之间的关系:                                                    1个worker进程执行的是1个topology的子集(注:不会出现1个worker为多个topology服

JStorm与Storm源码分析(四)--均衡调度器,EvenScheduler

EvenScheduler同DefaultScheduler一样,同样实现了IScheduler接口, 由下面代码可以看出: (ns backtype.storm.scheduler.EvenScheduler (:use [backtype.storm util log config]) (:require [clojure.set :as set]) (:import [backtype.storm.scheduler IScheduler Topologies Cluster Topolo

storm源码分析之任务分配--task assignment

在"storm源码分析之topology提交过程"一文最后,submitTopologyWithOpts函数调用了mk-assignments函数.该函数的主要功能就是进行topology的任务分配(task assignment).mk-assignments函数定义如下: ;; get existing assignment (just the executor->node+port map) -> default to {};; filter out ones whi

JStorm与Storm源码分析(三)--Scheduler,调度器

Scheduler作为Storm的调度器,负责为Topology分配可用资源. Storm提供了IScheduler接口,用户可以通过实现该接口来自定义Scheduler. 其定义如下: public interface IScheduler { //接收当前Nimbus的Storm配置作为参数,进行一些初始化工作 void prepare(Map conf); /** * 真正进行任务分配的方法,在Nimbus进行任务分配的时候会调用该方法. * 参数为topologies.cluster:前

使用Maven 编译 storm 源码可能碰到下载依赖jar 的问题

网上很多文件使用 lein 编译 storm 源码,但是我下载的版本 apache-storm-0.9.2-incubating 中没有lein 工程文件,而是有 pom.xml,可以使用maven 编译. 在storm 工程的最上层目录执行: mvn package -DskipTests=true 可能会碰到错误如下描述(jackson-core-2.0.6 只是一个例子): Failed to execute goal org.apache.maven.plugins:maven-remo