Storm入门指南第三章 拓扑结构

在本章你将会看到如何在一个Storm拓扑的不同组件间传递元组,以及如何将一个topology部署到一个运行的Storm集群上。

流分组

在设计一个topology时,一件最重要的事就是定义数据在组件之间怎样交换(流怎样被bolt消费)。流分组(Stream Grouping)指定了每个bolt消费哪些流,以及这些流如何被消费。

提示:一个节点可以发送不止一条数据流,流分组允许我们选择接收哪些流。

正如第二章中见到的,流分组在topology被定义的时候就已经被设定了:

1
2
builder.setBolt( "word-normalizer" , new WordNormalizer())
     .shuffleGrouping( "word-reader" );

在上面的代码块中,在topology builder上设置了一个bolt,然后设置该bolt的源使用随机分组(shuffleGrouping)。通常情况下,一个流分组携带源组件的Id作为参数,并且还有其他可选参数,这取决于流分组的种类。

提示:每个InputDeclarer可以有不止一个源,并且每个源可以用不同的流分组来分组。

随机分组

随机分组(Shuffle Grouping)是最常用的分组方式,该分组方式携带一个参数(源组件Id),源组件发送元组到一个随机选择的bolt并确保每个消费者(即bolt)会收到相等数量的元组。

随机分组对于做原子操作(例如数学运算)是很有用的。然而,如果操作不能被随机分配,就应该考虑使用其他分组,例如在第二章为单词计数的例子中。

字段分组

字段分组(Fields Grouping)允许你基于tuple中的一个或多个字段控制元组如何被发送到bolt,该分组方式确保了对于一个组合字段所确定的的值集合总是会被发送到相同的bolt。回到单词计数的例子,如果你根据“word”字段将流分组,则WordNormalizer bolt总是会将包含给定的单词的元组一起发送到相同的WordCounter bolt实例中。

1
2
builder.setBolt( "word-counter" , new WordCounter(), 2 )
     .fieldsGrouping( "word-normalizer" , new Fields( "word" ));

提示:字段分组中设置的所有字段在源组件的输出字段声明中也必须存在(译者注:在源组件的declareOutputFields()方法中声明)。

全部分组

全部分组(All Grouping)会向接收bolt的所有实例发送一份每个元组的副本,这种分组方式被用于向bolts发送信号。例如,如果你需要刷新缓存,你可以向所有bolt发送一个刷新缓存信号。在第二章单词计数的例子中,可以使用所有分组方式增加清空计数器缓存的功能(WordCounter中相应代码如下)

01
02
03
04
05
06
07
08
09
10
11
12
13
public void execute(Tuple input) {
     String str = null ;
     try {
         if (input.getSourceStreamId().equals( "signals" )){
             str =input.getStringByField( "action" );
             if ( "refreshCache" .equals(str))
                 counters.clear();
         }
     } catch (IllegalArgumentException e) {
     //Do nothing
     }
     ...
}

上面的代码中增加了一个if检查源流的名字 (sourceStreamId) 是否为”signal”。Storm中可以声明命名的流 (named streams) ,如果你不发送元组到一个命名的流,则流的默认名字为“default”。这是一个非常好的方式来确定元组的源,正如这个例子中我们需要确定信号一样。

在topology定义中,向word-counter bolt 增加第二个流分组方式,以便来自signals-spout 流中的每个tuple能发送到bolt的所有实例中。

1
2
3
builder.setBolt( "word-counter" , new WordCounter(), 2 )
     .fieldsGrouping( "word-normalizer" , new Fields( "word" ))
     .allGrouping( "signals-spout" , "signals" )

关于signals-spout的实现可以在git库中找到。

自定义分组

通过实现CustomStreamGrouping接口,你也可以创建自定义流分组,这让你有权决定每个元组将被哪个(些)bolt接收。

下面我们修改单词计数的例子,将元组分组以便相同字母开头的单词能被相同的bolt接收。

01
02
03
04
05
06
07
08
09
10
11
12
public class ModuleGrouping implements CustomStreamGrouping,Serializable{
 
     int numTasks= 0 ;
 
     @Override
     public List chooseTasks(List</pre>
上面是一个CustomStreamGrouping的简单实现,在这里我们使用任务的数量 (numTasks) 来对单词的第一个字符的整型值取模,由此选择哪个bolt将接收这个元组。
<p align= "left" >要在单词计数的例子中使用这种分组,按照下列方式修改word-counter分组(<strong>译者注:</strong>此处有修改):</p>
 
1
builder.setBolt( "word-counter" , new WordCounter())
     .customGrouping( "word-normalizer" , new ModuleGrouping());

直接分组

这是一个特殊的分组方式,由源组件决定哪个组件将接收元组。同前面的例子类似,源组件将基于单词中的第一个字母决定哪个bolt接收这个tuple,为了使用直接分组,需要在WordNormalizer bolt中使用emitDirect()方法代替emit方法。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public void execute(Tuple input) {
     ...
     for (String word:words){
         if (!word.isEmpty()){
             ...
             collector.emitDirect(getWordCountIndex(word), new Values(word));
         }
     }
     // Acknowledge the tuple
     collector.ack(input);
}
 
public Integer getWordCountIndex(String word) {
     word =word.trim().toUpperCase();
     if (word.isEmpty())
         return 0 ;
     else
         return word.charAt( 0 ) % numCounterTasks;
}

在prepare()方法中算出目标任务的数目:

1
2
3
4
5
public void prepare(Map stormConf,TopologyContext context,
     OutputCollector collector) {
     this .collector=collector;
     this .numCounterTasks=context.getComponentTasks( "word-counter" );
}

在topology的定义中,指定流将被直接分组:

1
2
builder.setBolt( "word-counter" , new WordCounter(), 2 )
     .directGrouping( "word-normalizer" );

全局分组

全局分组(Global Grouping)将源组件的所有实例产生的元组发送到一个目标组件的实例()中(具体地说,是bolt中Id最小的那个任务)。

无分组

在Storm0.7.1时,使用这种分组和使用随机分组一样,即不关注流怎样被分组。


LocalCluster vs. StormSubmitter

到现在为止,你都在使用一个叫LocalCluster的工具在本地计算机上运行topology。在自己的计算机上运行Storm基础结构可以使你方便地运行和调试不同的topology。但是当你想将你的topology提交到一个运行的Storm集群上时该怎么做呢?Storm的一大特点就是可以很方便地将你的topology发送到一个真实的集群上运行,此时你需要将LocalCluster改成StormSubmitter并且实现其中的submitTopology()方法,该方法负责将topology发送到集群上。

代码改变如下:

1
2
3
4
5
6
7
//LocalCluster cluster = new LocalCluster();
//cluster.submitTopology("Count-Word-Topology-With-Refresh-Cache",conf,
     builder.createTopology());
StormSubmitter.submitTopology( "Count-Word-Topology-With-Refresh-Cache" ,conf,
     builder.createTopology());
//Thread.sleep(1000);
//cluster.shutdown();

提示:当使用StormSubmitter时,不可以在代码中控制集群,但是使用LocalCluster可以。

接着,将源码打包成一个jar文件,它将在你运行Storm客户端命令提交topology时被发送。因为使用的是Maven,所以你只需进入源文件夹下运行命令:mvn package

生成jar文件后,使用 storm jar 命令提交topology(如何安装Storm客户端参见附录A),这个命令的语法是:

storm jar allmycode.jar org.me.MyTopology arg1 arg2 arg3

本例中,在topologies的源项目文件夹运行:

storm jar target/Topologies-0.0.1-SNAPSHOT.jar countword.TopologyMain src/main/resources/words.txt

通过这些命令,你就已经将topology提交到集群上了。要想停止或者杀死该topology,运行:

storm kill Count-word-Topology-With-Refresh-Cache

提示:Topology的名字必须唯一。

DRPC拓扑结构

有一种被称为DRPC(分布式远程过程调用,Distributed Remote Procedure Call)的特殊的topology类型,它使用Storm分布式的能力来执行远程过程调用 (RPC)。如图3-1所示,Storm提供了一些工具让你使用DRPC,第一个工具是DRPC服务器,它的作用是充当DRPC 客户端和topology之间的连接器 ,以及topology spouts的源。DRPC服务器接收一个函数和它的参数来执行,然后对于函数操作的每个数据片,服务器都分配一个在整个topology中使用的请求ID来识别RPC请求。当topology执行最后一个bolt时,它必须发送标识RPC的请求ID和结果,使得DRPC服务器可以返回结果至正确的客户端。

DRPCTopology图3-1.DRPC topology图解

提示:一个DRPC服务器可以执行很多函数,每个函数都被一个唯一的ID标识。

Storm提供的第二个工具是LinearDRPCTopologyBuilder——一个来帮助构建DRPC topologies的抽象。构建的topology创建DRPCSpouts(它连接DRPC服务器,并且发送数据到topology的剩余部分)、包装bolt(以便结果从最后一个bolt返回)。所有添加到LinearDRPCTopologyBuilder的bolt被顺序执行。

作为这种类型topology的一个例子,我们将创建一个累加数的程序。这个例子很简单,但是这种理念可以被扩展到执行复杂的分布式数学运算。

bolt有如下输出声明:

1
2
3
public void declareOutputFields(OutputFieldsDeclarer declarer) {
     declarer.declare( new Fields( "id" , "result" ));
}

由于这是topology中的唯一bolt,所以必须发送标识RPC的请求ID和结果。

execute()方法负责执行累加操作:

01
02
03
04
05
06
07
08
09
10
11
public void execute(Tuple input) {
     String[] numbers= input.getString( 1 ).split( "\\+" );
     Integer added = 0 ;
     if (numbers.length< 2 ){
         throw new InvalidParameterException( "Shouldbe at least 2 numbers" );
     }
     for (String num:numbers){
         added +=Integer.parseInt(num);
     }
     collector.emit( new Values(input.getValue( 0 ),added));
}

包含做累加的bolt的topology的定义如下:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) {
     LocalDRPC drpc = new LocalDRPC();
 
     LinearDRPCTopologyBuilder builder = new LinearDRPCTopologyBuilder( "add" );
     builder.addBolt( new AdderBolt(), 2 );
 
     Config conf = new Config();
     conf.setDebug( true );
 
     LocalCluster cluster = new LocalCluster();
     cluster.submitTopology( "drpc-adder-topology" ,conf,
         builder.createLocalTopology(drpc));
     String result =drpc.execute( "add" , "1+-1" );
     checkResult(result, 0 );
     result =drpc.execute( "add" , "1+1+5+10" );
     checkResult(result, 17 );
 
     cluster.shutdown();
     drpc.shutdown();
}

创建LocalDRPC对象来在本地运行DRPC。接着,创建topology builder来添加bolt到topology中。为了测试这个topology,在DRPC对象上使用execute方法。

提示:使用DRPCClient类连接到远程DRPC服务器。DRPC服务器提供一个可被多种语言使用的Trift API,并且在本地或者远程运行DRPC服务器使用同样的API。  为了将topology提交到Storm集群,使用builder对象中的createRemoteTopology()方法代替createLocalTopology()方法,该方法使用storm配置中的DRPC配置。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值