storm核心的基本原理,上一篇我们大概都了解了一下。
33-Storm学习-史上最通俗易懂Storm教程:大白话介绍Storm
现在我们,写一下代码,去体验一下storm的程序是怎么开发的,通过了解了代码之后,再回头去看一下之前讲解的一些基本原理,就清楚了一些。
案列做一个单词计数器。
你可以认为,storm源源不断的接收到一些句子,然后你需要实时的统计出句子中每个单词的出现次数
4.0.0
storm-wordcount
jar
storm-wordcount
UTF-8
org.apache.storm
storm-core
1.1.0
commons-collections
commons-collections
3.2.1
com.google.guava
guava
src/main/java
test/main/java
org.apache.maven.plugins
maven-shade-plugin
true
*:*
META-INF/*.SF
META-INF/*.sf
META-INF/*.DSA
META-INF/*.dsa
META-INF/*.RSA
META-INF/*.rsa
META-INF/*.EC
META-INF/*.ec
META-INF/MSFTSIG.SF
META-INF/MSFTSIG.RSA
package
shade
org.codehaus.mojo
exec-maven-plugin
1.2.1
exec
java
true
false
compile
/**
* 单词计数拓扑
*/
public class WordCountTopology {
/**
* Spout - 继承基础的BaseRichSpout,尝试去数据源获取数据,比如说从kafka中消费数据
*/
public static class RandomSentenceSpout extends BaseRichSpout {
SpoutOutputCollector _collector;
Random _rand;
/**
* open方法是对Spout 进行初始化的
* 比如创建线程池、数据库连接池、构造一个httpclient
* 在open方法初始化的时候,会传入一个SpoutOutputCollector,这个类就是用来发射数据的。
*/
@Override
public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
_collector = collector;
//构造一个随机生产对象
_rand = new Random();
}
/**
* 这个Spout之前说过,最终会运行在task中,某个worker进程的某个excuter执行内部的某个task
* 这个task会无限循环的去调用nextTuple()方法
* 这就可以不断发射最新的数据出去,形成一个数据流Stream
*/
@Override
public void nextTuple() {
Utils.sleep(100);
String[] sentences = new String[]{sentence("the cow jumped over the moon"), sentence("an apple a day keeps the doctor away"),
sentence("four score and seven years ago"), sentence("snow white and the seven dwarfs"), sentence("i am at two with nature")};
// 从sentences.length的长度中随机获取一个整数
final String sentence = sentences[_rand.nextInt(sentences.length)];
// 发射数据
_collector.emit(new Values(sentence));
}
protected String sentence(String input) {
return input;
}
@Override
public void ack(Object id) {
}
@Override
public void fail(Object id) {
}
/**
* declareOutputFields方法:定义一个你发出去的每个tuple中的每个field的名称
*/
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("sentence"));
}
}
/**
* bolt : spout将数据传给bolt,
* 每个bolt同样是发送到某个worker进程的某个excuter执行内部的某个task
*
*/
public static class SplitSentence implements IRichBolt {
private static final long serialVersionUID = 6604009953652729483L;
private OutputCollector collector;
/**
* 对于bolt来说,第一个方法,就是prepare方法,初始化的方法
* 传入的OutputCollector,这个也是Bolt的这个tuple的发射器
*/
@SuppressWarnings("rawtypes")
public void prepare(Map conf, TopologyContext context, OutputCollector collector) {
this.collector = collector;
}
/**
* execute方法
* 就是说,每次接收到一条数据后,就会交给这个executor方法来执行
*
*/
public void execute(Tuple tuple) {
String sentence = tuple.getStringByField("sentence");
String[] words = sentence.split(" ");
for(String word : words) {
collector.emit(new Values(word));
}
}
/**
* 定义发射出去的tuple,每个field的名称
*/
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("word"));
}
}
public static class WordCount extends BaseRichBolt {
private static final long serialVersionUID = 7208077706057284643L;
private static final Logger LOGGER = LoggerFactory.getLogger(WordCount.class);
private OutputCollector collector;
private Map wordCounts = new HashMap();
@SuppressWarnings("rawtypes")
public void prepare(Map conf, TopologyContext context, OutputCollector collector) {
this.collector = collector;
}
public void execute(Tuple tuple) {
String word = tuple.getStringByField("word");
Long count = wordCounts.get(word);
if(count == null) {
count = 0L;
}
count++;
wordCounts.put(word, count);
LOGGER.info("【单词计数】" + word + "出现的次数是" + count);
collector.emit(new Values(word, count));
}
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("word", "count"));
}
}
public static void main(String[] args) {
//1、准备任务信息
//Storm框架支持多语言,在JAVA环境下创建一个拓扑,需要使用TopologyBuilder进行构建
// 在main方法中,会去将spout和bolts组合起来,构建成一个拓扑
TopologyBuilder builder = new TopologyBuilder();
// 这里的第一个参数的意思,就是给这个spout设置一个名字
// 第二个参数的意思,就是创建一个spout的对象
// 第三个参数的意思,就是设置spout的executor有几个
builder.setSpout("RandomSentence", new RandomSentenceSpout(), 2);
builder.setBolt("SplitSentence", new SplitSentence(), 5)
.setNumTasks(10)
.shuffleGrouping("RandomSentence");
// 这个很重要,就是说,相同的单词,从SplitSentence发射出来时,
一定会进入到下游的指定的同一个task中
// 只有这样子,才能准确的统计出每个单词的数量
// 比如你有个单词,hello,下游task1接收到3个hello,task2接收到2个hello
// 所以这5个hello,应该全都进入一个task,这样出来的就是5个hello。
builder.setBolt("WordCount", new WordCount(), 10)
.setNumTasks(20)
.fieldsGrouping("SplitSentence", new Fields("word"));
//2、任务提交
//提交给谁?提交什么内容?
Config config = new Config();
// 说明是在命令行执行,打算提交到storm集群上去
if(args != null && args.length > 0) {
//定义你希望集群分配多少个工作进程给你来执行这个topology
config.setNumWorkers(3);
try {
StormSubmitter.submitTopology(args[0], config, builder.createTopology());
} catch (Exception e) {
e.printStackTrace();
}
} else {
// 说明是在eclipse里面本地运行
config.setMaxTaskParallelism(20);
LocalCluster cluster = new LocalCluster();
cluster.submitTopology("WordCountTopology", config, builder.createTopology());
Utils.sleep(60000);
cluster.shutdown();
}
}
}
本地启动也可以看到效果。