作者:张航东
Linux:CentOS 7
Apache Storm:1.1.1
本文主要用于个人学习、总结,欢迎转载,但请务必注明作者和出处,感谢!
Apache Storm是一个免费、开源的分布式实时计算系统,关于其相关的介绍和安装网上的资料很多,大家可以自行baidu,这里就不重复介绍了。
本文主要是手把手的教大家,如何在零基础的情况下开启Apache Storm开发之路。
有2种方式可以让我们开发的应用程序在Storm上运行,即:本地模式和远程模式
本地模式下,我们只需要在自己的电脑上安装 JDK 和 Eclipse 即可(剩下的让maven来搞定)。
远程模式下,我们需要至少一台Linux电脑/虚拟机,并在其上安装:
即先要保证Apache Storm可以正常运行。
这里,我们以统计英文字符个数(wordcount)为例。
首先,创建一个maven的工程,工程名就叫“wordcount”。
工程创建好后,修改pom.xml文件,增加依赖,我们至少需要增加3个依赖:
1> Storm的依赖
<dependency> <groupId>org.apache.stormgroupId> <artifactId>storm-coreartifactId <version>1.1.1version> <scope>providedscope> dependency>
2> 日志的依赖
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.7version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-apiartifactId>
<version>2.7version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-slf4j-implartifactId>
<version>2.7version>
dependency>
3> clojure的依赖(Storm是基于clojure开发的)
<dependency>
<groupId>org.clojuregroupId>
<artifactId>clojureartifactId>
<version>1.8.0version>
dependency>
依赖添加好后,我们就可以正常开发了。
在着手敲代码之前,先设计好 Storm 的 Topology,会让编码变的更简单,也更能体会到 Storm 的强大。
这里,我们设计了:
根据设计,我们需要定义以下4个类:
Spout
主要功能:
1> 从文件中(按行)读取字符串
2> 将数据发送出去
3> 校验数据处理结果,并打印输出结果
Split Bolt
主要功能:
1> 获取数据
2> 处理数据(字符串),将其(按空格)分隔
3> 将处理后的数据发送出去
Count Bolt
主要功能:
1> 获取数据
2> 处理数据(字符),定义全局 Map 进行个数统计
TopologyDriver 类 (Main)
1> 构造 Topology,定义 Spout 和 Bolt 之间的数据流向
2> Topology 配置
3> 将 Topology 提交给 Storm 运行
Spout
package org;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import org.apache.storm.shade.org.apache.commons.io.IOUtils;
import org.apache.storm.shade.org.apache.commons.io.LineIterator;
import org.apache.storm.shade.org.apache.commons.lang.StringUtils;
import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichSpout;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Values;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Spout extends BaseRichSpout{
private static final long serialVersionUID = 76587948080518253L;
private static final Logger logger = LoggerFactory.getLogger(Spout.class);
private SpoutOutputCollector collector;
private LineIterator lineIterator;
private int index = 1;
// 打开文件
public void open(Map stormConf, TopologyContext context, SpoutOutputCollector collector) {
this.collector = collector;
InputStream in = null;
try
{
in = new FileInputStream(new File("test.txt"));
if (in != null) {
this.lineIterator = IOUtils.lineIterator(in, "utf-8");
}
}
catch (Exception e) {
e.printStackTrace();
}
}
public void nextTuple() {
if (lineIterator != null && lineIterator.hasNext()) {
// 1 从文件中(按行)读取字符串
String line = lineIterator.next();
if (StringUtils.isNotEmpty(line)) {
// 2 将数据发送出去
Values values = new Values(line);
collector.emit(values, index++);
}
}
}
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("line"));
}
// 3 校验数据处理结果,并打印输出结果(成功时)
public void ack(Object msgId) {
logger.info(" ### A message has been done successfully !!! the msgId is " + (Integer)msgId);
logger.info("### The result is " + CountBolt.countedWord.toString());
}
// 3 校验数据处理结果,并打印输出结果(失败时)
public void fail(Object msgId) {
logger.error(" !!! A message was failed. the msgId is " + (Integer)msgId);
}
}
Split Bolt
package org;
import org.apache.storm.shade.org.apache.commons.lang.StringUtils;
import org.apache.storm.topology.BasicOutputCollector;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseBasicBolt;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;
public class SplitBolt extends BaseBasicBolt {
private static final long serialVersionUID = -5517905015897808351L;
public void execute(Tuple input, BasicOutputCollector collector) {
// 1 获取数据
String pickLine = input.getStringByField("line");
if (StringUtils.isNotEmpty(pickLine)) {
// 2 处理数据(字符串),将其(按空格)分隔
String[] splitStrs = pickLine.split(" ");
for (String str : splitStrs) {
// 3 将处理后的数据发送出去
// values对象会帮助我们生成一个list
Values values = new Values(str, 1);
collector.emit(values);
}
}
}
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("word", "num"));
}
}
Count Bolt
package org;
import java.util.HashMap;
import java.util.Map;
import org.apache.storm.topology.BasicOutputCollector;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseBasicBolt;
import org.apache.storm.tuple.Tuple;
public class CountBolt extends BaseBasicBolt{
private static final long serialVersionUID = 5085772150587505422L;
public static Map countedWord = new HashMap();
public void execute(Tuple input, BasicOutputCollector collector) {
// 1 获取数据
String pickWord = input.getStringByField("word");
Integer pickNum = input.getIntegerByField("num");
// 2 处理数据(字符),定义全局 Map 进行个数统计
Integer num = countedWord.get(pickWord);
if (num == null || num == 0) {
countedWord.put(pickWord, pickNum);
} else {
countedWord.put(pickWord, pickNum.intValue() + num.intValue());
}
}
public void declareOutputFields(OutputFieldsDeclarer declarer) {
}
}
Topology
package org;
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.generated.StormTopology;
import org.apache.storm.topology.TopologyBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TopologyDriver {
private static final Logger logger = LoggerFactory.getLogger(TopologyDriver.class);
public static void main(String[] args) throws Exception {
logger.error("#### I am starting ...");
// 1 构造 Topology,定义 Spout 和 Bolt 之间的数据流向
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout("spout", new Spout());
builder.setBolt("split_bolt", new SplitBolt()).shuffleGrouping("spout");
builder.setBolt("count_and_print_bolt", new CountBolt(), 2).shuffleGrouping("split_bolt").setNumTasks(4);
StormTopology topology = builder.createTopology();
// 2 Topology 配置
Config conf = new Config();
//conf.setNumWorkers(2); //设置worker数量(即进程数)
// 3 将 Topology 提交给 Storm 运行
// 远程模式
//StormSubmitter.submitTopology("wordcount", conf, topology);
// 本地模式
LocalCluster cluster = new LocalCluster();
cluster.submitTopology("wordcount", conf, topology);
}
}
在 Spout 中我们定义的是读取 “test.txt” 文件中的字符串。
所以,我们需要在该工程的workspace的根目录下创建这个文件,并添加一些英文内容。
在第1章节的时候,我们提到程序运行分为:本地模式和远程模式。
这个主要是在Class TopologyDriver
在“将 Topology 提交给 Storm 运行”时决定的。
// 远程模式
StormSubmitter.submitTopology("wordcount", conf, topology);
// 本地模式
LocalCluster cluster = new LocalCluster();
cluster.submitTopology("wordcount", conf, topology);
直接在Eclipse运行即可。
注意:
1> 使用 StormSubmitter.submitTopology() 提交 Topology
2> 修改 “test.txt”文件的路径为目标机上的路径
将工程导出为 Jar 文件。
“File”-> “Export”->选择”Jave”下的”JAR File”
剩下的一路”Next”即可。
将该 Jar 文件和 “test.txt” 拷贝到目标机上(已安装并运行Storm)
启动
使用命令:
# storm jar wordcount.jar org.TopologyDriver
查看输出结果
由于我们的程序是托管给Storm运行的,所以在我们无法再控制台看到打印的日志。
打印的日志存放在:
.../apache-storm-x.x.x/logs/workers-artifacts/wordcount-x-xxxxxxxx/xxxx/worker.log