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

转载 2015年11月18日 18:35:44

在本章你将会看到如何在一个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配置。


相关文章推荐

PCIe通常的拓扑结构和布线指南

  • 2011年02月26日 14:20
  • 312KB
  • 下载

Storm入门之第三章拓扑

本文翻译自《Getting Started With Storm》  译者:吴京润   编辑:方腾飞 在这一章,你将学到如何在同一个Storm拓扑结构内的不同组件之间传递元组,以及如何向一个...

【Storm入门指南】第三章 Topologies

你将从本章学习到:如何在一个 Storm topology 的不同组件间传递 tuple,以及如何将 topology 部署到一个 Storm 集群。 3.1 流分组 当设计一个 topo...

[译]【Storm入门指南】第三章 Topologies

特别注明:本文翻译自 Getting started with Storm 第三章,以作学习交流之用,非盈利性质。如需转载,请以超链接形式标明文章原始出处和作者信息及版权声明。 你将从本章学...

基于拓扑结构的关键蛋白识别

  • 2016年03月18日 17:36
  • 955KB
  • 下载

3d点云拓扑结构表征

  • 2013年04月20日 19:01
  • 727KB
  • 下载

组建局域网时常用的拓扑结构

常见的局域网拓扑结构 网络中的计算机等设备要实现互联,就需要以一定的结构方式进行连接,这种连接方式就叫做"拓扑结构",通俗地讲这些网络设备如何连接在一起的。目前常见的网络拓扑结构主要有以下四大类: (...

局域网拓扑结构

  • 2011年12月22日 14:07
  • 79KB
  • 下载

常见拓扑结构搭建.doc

  • 2011年04月21日 16:14
  • 276KB
  • 下载

计算机网络拓扑结构

互联网时代已经到来了,下面学习啦小编为您科普一下网络相关基础知识《什么是拓扑结构》,让您更快融入互联网时代,希望对您有所帮助! 什么是拓扑结构?   首先我们来解释一下拓扑的含义,所谓“拓扑”就是...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Storm入门指南第三章 拓扑结构
举报原因:
原因补充:

(最多只允许输入30个字)