getting start with storm 翻译 第二章 part-2

转载请注明出处:http://blog.csdn.net/lonelytrooper/article/details/9961747

Bolts

现在我们有了一个spout来读取文件并且每一行发射一个元组。我们需要建立两个bolt来处理元组(见图2-1)。这些bolts实现了backtype.storm.topology.IRichBolt接口。

Bolt最重要的方法是voidexecute(Tuple input),每收到一个元组调用一次。对于每个收到的元组,bolt会发射出一些元组。

bolt或者spout可以发射如所需一样多的元组。当nextTuple或execute方法被调用时,它们可能发射0个,1个或多个元组。你将在第五章了解到更多。

 

第一个bolt,WordNormalizer,负责获取行并且标准化行。它会将行分隔成单词,将单词转化成小写并且trim单词。

首先我们要声明bolt的输出参数:

public voiddeclareOutputFields(OutputFieldsDeclarerdeclarer) {

declarer.declare(newFields("word"));

}

这里我们声明bolt将输出一个域,名为word。

接着我们实现public void execute(Tuple input)方法,在这里输入的元组将被处理:

public voidexecute(Tuple input) {

String sentence =input.getString(0);

String[]words= sentence.split(" ");

for(String word:words){

word =word.trim();

if(!word.isEmpty()){

word =word.toLowerCase();

//Emit the word

collector.emit(newValues(word));

}

}

// Acknowledge the tuple

collector.ack(input);

}

第一行读取元组的值。值可以通过位置或者名字读取。值被处理然后使用collector对象发射。在每个元组被处理完成后,collector的ack()方法被调用来表明处理被成功的完成。如果该元组不能被处理,应该调用collector的fail()方法。

示例2-2包含这个类的完整代码。

Example 2-2.src/main/java/bolts/WordNormalizer.java

package bolts;

import java.util.ArrayList;

import java.util.List;

import java.util.Map;

import backtype.storm.task.OutputCollector;

import backtype.storm.task.TopologyContext;

import backtype.storm.topology.IRichBolt;

import backtype.storm.topology.OutputFieldsDeclarer;

import backtype.storm.tuple.Fields;

import backtype.storm.tuple.Tuple;

import backtype.storm.tuple.Values;

public class WordNormalizerimplementsIRichBolt{

private OutputCollector collector;

         publicvoidcleanup(){}

/**

* The bolt will receive the line from the

* words file and process it to Normalize this line

*

* The normalize will be put the words in lower case

* and split the line to get all words in this

*/

public voidexecute(Tuple input) {

String sentence =input.getString(0);

String[]words= sentence.split(" ");

for(String word:words){

word =word.trim();

if(!word.isEmpty()){

word =word.toLowerCase();

//Emit the word

List a =newArrayList();

a.add(input);

collector.emit(a,newValues(word));

}

}

// Acknowledge the tuple

collector.ack(input);

}

public voidprepare(Map stormConf,TopologyContext context,

OutputCollector collector) {

this.collector=collector;

}

/**

* The bolt will only emit the field "word"

*/

public voiddeclareOutputFields(OutputFieldsDeclarerdeclarer) {

declarer.declare(newFields("word"));

}

}

在这个类中,我们看到了在一个单独的execute方法中发射多个元组的例子。如果方法收到句子This is the Storm book,在一个单独的execute方法中,它将发射五个元组。

 

下一个bolt,WordCounter,负责为单词计数。当topology结束的时候(cleanup()方法被调用时),我们会显示每个单词的计数。

 

这是一个bolt不发射任何东西的示例。在这个例子中,数据被加到一个map中,但在实际中,bolt会将数据存到数据库中。

package bolts;

import java.util.HashMap;

import java.util.Map;

import backtype.storm.task.OutputCollector;

import backtype.storm.task.TopologyContext;

import backtype.storm.topology.IRichBolt;

import backtype.storm.topology.OutputFieldsDeclarer;

import backtype.storm.tuple.Tuple;

public class WordCounterimplementsIRichBolt{

Integer id;

String name;

Map<String,Integer>counters;

private OutputCollector collector;

/**

* At the end of the spout (when the cluster is shutdown

* We will show the word counters

*/

@Override

public voidcleanup(){

System.out.println("-- Word Counter ["+name+"-"+id+"]--");

for(Map.Entry<String,Integer>entry: counters.entrySet()){

System.out.println(entry.getKey()+": "+entry.getValue());

}

}

/**

* On each word We will count

*/

@Override

public voidexecute(Tuple input) {

String str =input.getString(0);

/**

* If the word dosn't exist in the map we will create

* this, if not We will add 1

*/

if(!counters.containsKey(str)){

counters.put(str,1);

}else{

Integer c =counters.get(str) +1;

counters.put(str,c);

}

//Set the tuple as Acknowledge

collector.ack(input);

}

/**

* On create

*/

@Override

public voidprepare(Map stormConf,TopologyContext context,

OutputCollector collector) {

this.counters=newHashMap<String,Integer>();

this.collector=collector;

this.name=context.getThisComponentId();

this.id=context.getThisTaskId();

}

@Override

public voiddeclareOutputFields(OutputFieldsDeclarerdeclarer) {}

}

execute方法是用map来收集和计数单词。当topology结束的时候,cleanup()方法被调用并且打印出计数的map。(这只是一个示例,通常来讲你应该在cleanup()方法里关闭有效的连接和其他资源当topology关闭的时候。)

主类

在主类中,你会建立topology和LocalCluster对象,它使你可以在本地测试和调试topology。与Config对象结合,LocalCluster允许你尝试不同的集群配置。例如,当一个全局或者类变量被不慎使用时,在使用不同数量的workers配置来测试你的topology时你会找到这个错误。(在第三章你将看到更多关于config对象。)

所有的topology结点应该可以在进程间没有数据共享的情形下独立运行(例如,没有全局或类变量),因为topology在实际的集群中运行时,这些进程可能运行在不同的机器上。

你将使用TopologyBuilder创建topology,topology告诉storm结点是怎么安排的并且它们之间怎样交换数据。

TopologyBuilder builder =newTopologyBuilder();

builder.setSpout("word-reader",newWordReader());

builder.setBolt("word-normalizer",newWordNormalizer()).shuffleGrouping("wordreader");

builder.setBolt("word-counter",newWordCounter()).shuffleGrouping("wordnormalizer");

spout和bolts通过shuffleGroupings连接起来。这种分组(grouping)告诉storm在源结点和目标结点之间以随机分布的方式发送消息。

接着,建立包含topology配置的Config对象,该配置在运行时会被与集群的配置合并并且通过prepare方法发送到所有结点。

Config conf =newConfig();

conf.put("wordsFile",args[0]);

conf.setDebug(true);

对将要被spout读取的文件名设置属性wordsFile,因为你在开发过程中所以debug属性为true。当debug为true时,storm打印结点间交换的所有消息,debug数据对于理解topology怎样运行是有用的。

正如前边提到的,你将使用LocalCluster来运行topology。在生产环境中,topology持续的运行,但对于这个例子你可以只运行topology几秒以便你可以查看结果。

LocalCluster cluster =newLocalCluster();

cluster.submitTopology("Getting-Started-Toplogie",conf,builder.createTopology());

Thread.sleep(2000);

cluster.shutdown();

创建和运行topology使用createTopology和submitTopology,睡眠两秒(topology运行在不同的线程中),然后通过关闭集群来停止topology。

看示例2-3来把它放到一起。

Example 2-3.src/main/java/TopologyMain.java

import spouts.WordReader;

import backtype.storm.Config;

import backtype.storm.LocalCluster;

import backtype.storm.topology.TopologyBuilder;

import backtype.storm.tuple.Fields;

import bolts.WordCounter;

import bolts.WordNormalizer;

public class TopologyMain{

public static voidmain(String[]args)throws InterruptedException{

//Topology definition

TopologyBuilder builder =newTopologyBuilder();

builder.setSpout("word-reader",newWordReader());

builder.setBolt("word-normalizer",newWordNormalizer())

.shuffleGrouping("word-reader");

builder.setBolt("word-counter",newWordCounter(),2)

.fieldsGrouping("word-normalizer",newFields("word"));

//Configuration

Config conf =newConfig();

conf.put("wordsFile",args[0]);

conf.setDebug(false);

//Topology run

conf.put(Config.TOPOLOGY_MAX_SPOUT_PENDING,1);

LocalCluster cluster =newLocalCluster();

cluster.submitTopology("Getting-Started-Toplogie",conf,

builder.createTopology());

Thread.sleep(1000);

cluster.shutdown();

}

}

实践一下

你将要运行你的第一个topology ! 如果你已经建立了文件src/main/resources/

words.txt并且其中每行有一个单词,你可以用这个命令运行topology:

         mvnexec:java -Dexec.mainClass="TopologyMain"-Dexec.args="src/main/resources/

words.txt"

例如,你可以用下边的word.txt文件:

Storm

test

are

great

is

an

Storm

simple

application

but

very

powerful

really

Storm

is

great

在日志中,你应该看到类似如下的输出:

is: 2

application: 1

but: 1

great: 1

test: 1

simple: 1

Storm: 3

really: 1

are: 1

great: 1

an: 1

powerful: 1

very: 1

在这个示例中,你只使用了每个结点的一个单例。但如果你有一个非常大的日志文件呢?你可以轻易的改变系统中结点的数量来并行工作。在这个例子中,你可以建立两个WordCounter的实例:

builder.setBolt("word-counter",newWordCounter(),2)

.shuffleGrouping("word-normalizer");

如果你重新运行程序,你将看到:

-- Word Counter [word-counter-2] --

application: 1

is: 1

great: 1

are: 1

powerful: 1

Storm: 3

-- Word Counter [word-counter-3] --

really: 1

is: 1

but: 1

great: 1

test: 1

simple: 1

an: 1

very: 1

牛逼啊!改变并行度这么容易(在实际中,当然,每个实例运行在独立的机器中)。但看起来似乎有个问题:单词is和great在每个WordCounter实例中各被计算了一次。为什么呢?当你使用shuffleGrouping的时候,你告诉storm以随机分布的方式将每条消息发送至你的bolt实例。在这个例子中,总是把相同的单词送到相同的WordCounter是更理想的。为了实现这个,你可以将shuffleGrouping("wordnormalizer")换成fieldsGrouping("word-normalizer",newFields("word"))。尝试一下并且重新运行程序来验证结果。你会在后续的章节看到更多关于分组和消息流的内容。

结论

我们已经讨论了storm本地操作模式和远程操作模式的不同,以及用storm开发的强大和简便。你也学到了更多关于storm的基本概念,这些概念我们将在接下来的章节深入解释。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值