使用Clojure DSL来写storm

storm 提供一套Clojure DSL来定义spouts,bolts,和topologies。因为Clojure DSL可以调用所有暴露在外的java api,所以如果你是一个
clojure开发者,你不用接触java代码就可以编写storm topologys。定义Clojure DSL的代码在backtype.storm.clojure 命名空间下.
本文概述了使用Clojure DSL的所有部分,包括:
1.定义 topologies
2. defbolt
3. defspout
4. 在本地模式或集群模式运行topologies
5. 测试topologies

定义topologies
通过使用topology方法来定义一个topology。topology方法接受2个参数:一个value为spout-spec类型的map和一个value为bolt-spec类型的map。
这些spout-spec和bolt-spec设置了这个topology的输入,并行度等
我们来看一个storm-starter项目中的例子:
(topology
 {"1" (spout-spec sentence-spout)
  "2" (spout-spec (sentence-spout-parameterized
                   ["the cat jumped over the door"
                    "greetings from a faraway land"])
                   :p 2)}
 {"3" (bolt-spec {"1" :shuffle "2" :shuffle}
                 split-sentence
                 :p 5)
  "4" (bolt-spec {"3" ["word"]}
                 word-count
                 :p 6)})
maps中的spout-spec和bolt-spec是一个id到对应spec的映射,这个id必须在整个map里面是唯一的,和使用java类似,这个id被用于
定义所有bolt的输入是哪些。

spout-spec

spout-spec的参数包括一个spout的实例(实现 IRichSpout)和可选的关键字参数。目前存在的唯一的选择是:p选项,这个选项指定了spout的并行度,如果
不设置这个选项,spout将以一个线程运行。

bolt-spec

bolt-spec的参数包括bolt的输入说明,bolt实例(实现IRichBolt),和可选的关键字参数
输入参数是一个stream ids到stream groupings的映射,stream id可以有两种形式:
1:[==component id== ==stream id==]: 为组件指定特定的流id
2:==component id==: 对一个组件使用默认流
对于流的分组必须是下面几种中的一个:
1: :shuffle:随机发给其它task
2: 存放字段的vector,如["id" "name"]:相同field值的tuple会去同一个task
3::global:全局分组,分配给id值最低的那个task
4: :all:广播发送,所有的Bolts都会收到每一个tuple
5: :direct 直接分组,消息的发送者指定由消息接收者的哪个task处理这个消息
https://github.com/nathanmarz/storm/wiki/Concepts 有更多关于stream groupings的介绍,下面的例子展示了不同类型的输入参数的声明。
{["2" "1"] :shuffle
 "3" ["field1" "field2"]
 ["4" "2"] :global}
这个输入声明了3中流,为组件“2”定制了“1”的stream,并且使用shuffle grouping,为组件“3”定制了默认流,使用fields grouping,为
组件“4”定制了“2”的stream,使用global
和spout-spec一样bolt-spec提供:p来指定bolt的并行度。

shell-bolt-spec

shell-bolt-spec 用于为non-JVM language定义bolts。它接收输入,输出配置,指令,以及实现bolt的文件的名字,另外支持和bolt-spec同样的参数
这里有一个shell-bolt-spec的例子:
(shell-bolt-spec {"1" :shuffle "2" ["id"]}
                 "python"
                 "mybolt.py"
                 ["outfield1" "outfield2"]
                 :p 25)
更详细的语法描述看https://github.com/nathanmarz/storm/wiki/Using-non-JVM-languages-with-Storm

defbolt

defbolt用于使用Clojure定义一个bolt,bolt必须是serializable的,这也是为什么要通过实现IRichBolt来实例化一个bolt,
(闭包不能实现序列化),defbolt通过一些良好的语法来实现这些特性,而不仅仅是实现某个java接口。

另外defbolt支持参数化的bilts和维护状态在一个bolt的实现里。同时也提供了快捷方式去定义bolts而不需要额外的方法。
defbolt的定义如下所示:
(defbolt name output-declaration *option-map & impl)
省略option的map相当与使用一个{:prepare false}.

Simple bolts

我们从最简单的开始,这里有一个将一个句子的tuple切分成单词的tuple的bolt
(defbolt split-sentence ["word"] [tuple collector]
  (let [words (.split (.getString tuple 0) " ")]
    (doseq [w words]
      (emit-bolt! collector [w] :anchor tuple))
    (ack! collector tuple)
    ))

该例子省略了option map,所以是一个non-prepared bolt。这个DSL简单的实现了IRichBolt的execute方法。这个实现接收2个参数
一个是tuple,一个是OutputCollection,接下来是execute的方法体。DSL会自动映射参数类型,所以不必担心怎么与java互操作。
这个实现绑定dplit-sentence到一个真是的IRichBolt,你可以在一个topologies里面使用它,像这样:
(bolt-spec {"1" :shuffle}
           split-sentence
           :p 5)

Parameterized bolts
很多时候我们希望使用其它的参数,例如,我们想实现一个在接收的参数后面追加后缀的bolt,我们可以通过使defbolt包含:params
option在option map,像这样:
(defbolt suffix-appender ["word"] {:params [suffix]}
  [tuple collector]
  (emit-bolt! collector [(str (.getString tuple 0) suffix)] :anchor tuple)
  )
不同于上面的例子,suffix-appender会返回一个IRichBolt而不是实现一个IRichBolt。这是因为指定一个:params在option map。
所以要想在topology中使用suffix-appender,需要这样写:
(bolt-spec {"1" :shuffle}
           (suffix-appender "-suffix")
           :p 10)

Prepared bolts

更综合的bolts,可以用来做joins和流聚合,这个bolt需要储藏状态。你可以通过创建一个option map为{:prepare true}的bolt来创建它。加入我们做一个
单词统计的例子:
(defbolt word-count ["word" "count"] {:prepare true}
  [conf context collector]
  (let [counts (atom {})]
    (bolt
     (execute [tuple]
       (let [word (.getString tuple 0)]
         (swap! counts (partial merge-with +) {word 1})
         (emit-bolt! collector [word (@counts word)] :anchor tuple)
         (ack! collector tuple)
         )))))

prepared bolt的实现是一个作为topology配置的方法。TopologyContext,和OutputCollector,并且返回一个IBot的实例。这个设计要求
有一个execute和cleanup的实现。
在这个例子中,单词统计在叫做counts的map中存储。这个叫做bolt的宏用来创建一个IBot实例。bolt宏是一个简洁的实现这个接口的方式,
并且它自动映射方法中所有的参数。这个bolt实现execute方法,用来更新统计值和发射一个新的word count。
需要注意的是execute方法在prepared bolts中作为输入tuple是因为OutputCollector已经存在在闭包方法里
(simple bolts这个collector是作为第二个参数传进excute方法的)

Output declarations

Clojure DSL 有一个简明的语法来说明bolt的输出。这个简单的做法是定义一个stream id到stream spec的map
例如:
{"1" ["field1" "field2"]
 "2" (direct-stream ["f1" "f2" "f3"])
 "3" ["f1"]}

这个stream id是一个字符串,而stream spec则是一个字段或者使用direct-stream的字段的vector。
direct stream表示这个stream是一个直接分组。

如果这个bolt只有一个输出流,你可以通过一个vector定义一个默认的stream来代替定义一个map。例如:
["word" "count"]
是为默认的stream id使用["word" "count"]配置输出

Emitting,acking,and failing

相比直接使用java方法OutputCollector,Clojure的DSL提供更友善的方法来使用OutputCollector:
emit-bolt!, emit-direct-bolt!, ack!,和fail!
1.emit-bolt:接收OutputCollector的参数,会emit一个values,并提供关键字:anchor和:stream,:anchor是一个单独的tuple或者是一个
tuple的list,:stream是emit的stream的id。如果忽略这个关键字参数则会给默认stream发射一个不可靠的tuple。
2.emit-direct-bolt!:作为OutputCollector的参数,task id用于发送tuple,发射values,也有关键字参数:anchor和:stream。
该方法只能以直接分组的形式发送stream。
3.ack!:接收OutputCollector的参数,维护tuple的可靠性。
4.fail!:接收OutputCollector的参数,tuple是否失败

defspout

defspout用来使用Clojure定义spout。和bolt一样,spout也必须是serializable的,所以不能仅仅实现IRichSpout。defspout提供了
比直接实现javaapi更良好的方法来实现。
defspout的格式:
(defspout name output-declaration *option-map & impl)
如果忽略option map,它的默认值为{:prepare true}。声明输出的语法和defbolt一样。
这里有一个原子storm-starter的defspout的实现:
(defspout sentence-spout ["sentence"]
  [conf context collector]
  (let [sentences ["a little brown dog"
                   "the man petted the dog"
                   "four score and seven years ago"
                   "an apple a day keeps the doctor away"]]
    (spout
     (nextTuple []
       (Thread/sleep 100)
       (emit-spout! collector [(rand-nth sentences)])        
       )
     (ack [id]
        ;; You only need to define this method for reliable spouts
        ;; (such as one that reads off of a queue like Kestrel)
        ;; This is an unreliable spout, so it does nothing here
        ))))
TopologyContext, 和SpoutOutputCollector作为实现topology输入的配置。这个实现返回一个ISpout对象。nextTuple方法在sentences发射一个随机的
句子
这个spout是不可靠的,所以ack和fail方法永远不会被调用。一个可靠的spout在发射tuple的时候需要加上message id,这样ack和fail
将会在tuple完成或者失败的时候分别被调用。
emit-spout!需要一个参数SpoutOutputCollector,并且发射新的tuple,并且接收关键字参数:stream和:id.:stream指定这个流将被发射到哪,
:id指定了这个消息的来源id(用于ack或者fail的时候进行callback),忽略这些参数,将会发射一个使用默认流分组的不可靠的tuple
这里还有一个emit-direct-spout!方法来发射一个直接分组的tuple,并且需要一个额外的参数来作为第二个参数的task id来发射tuple。
spout可以像bolt一样被参数化,在这种情况下,符号取决于一个方法返回IRichSpout,而不是IRichDpout本身。
你可以声明一个只定义了nextTuple方法的不完全的spout。
这里有一个运行时发射随机句子的例子。
(defspout sentence-spout-parameterized ["word"] {:params [sentences] :prepare false}
  [collector]
  (Thread/sleep 500)
  (emit-spout! collector [(rand-nth sentences)]))

下面的例子是说明如何在spout-spec中使用这个spout
(spout-spec (sentence-spout-parameterized
                   ["the cat jumped over the door"
                    "greetings from a faraway land"])
            :p 2)

在本地模式或集群模式运行topology

这里是全部的ClojureDSL。远程模式或者本地模式提交topology,只需要使用StormSubmitter或者LocalCluster类,就像java一样。
如果要创建一个topology的配置,可以简单的使用backtype.storm.config,该命名空间下有默认的常量配置。
这些配置和在Config类下面的静态配置是一样的,除了横杠用下划线表示。例如这里有一个topology的配置配置worker的数量为15,并且
topology为debug模式:
{TOPOLOGY-DEBUG true
 TOPOLOGY-WORKERS 15}

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值