前期准备
准备搭建3节点集群,准备3个虚拟机node1,node2,node3
配置好hosts映射文件和互相的ssh免密登录
配置好JDK
storm是依赖于zookeeper的,搭建storm集群前,必须先把zookeeper集群搭建好
安装storm
1 ) 准备好storm安装包apache-storm-1.1.1.tar.gz,下载地址:
http://storm.apache.org/downloads.html
2 ) 上传解压重命名为storm到/export/server路径下
3 ) 修改配置文件 storm.yaml
cd /export/server/storm/conf/
vi storm.yaml
添加如下配置:
# 指定storm使用的zookeeper集群
storm.zookeeper.servers:
- "node1"
- "node2"
- "node3"
# 指定storm集群中的nimbus节点所在的服务器
nimbus.seeds: ["node1", "node2", "node3"]
# 指定storm文件存放目录
storm.local.dir: "/export/data/storm"
# 指定supervisor节点上,启动worker时对应的端口号,每个端口对应槽,每个槽位对应一个worker
supervisor.slots.ports:
- 6700
- 6701
- 6702
- 6703
4 ) 分发到其他节点
scp -r /export/server/storm node2:/export/server/
scp -r /export/server/storm node3:/export/server/
前台启动 (前台启动会占用窗口)
1 ) 在node1上启动 nimbus进程(主节点) 和 web UI
cd /export/server/storm/bin
storm nimbus
克隆一个node1的连接窗口
cd /export/server/storm/bin
storm ui
2 ) 在 node2 和 node3 上启动 supervisor(从节点)
cd /export/server/storm/bin
storm supervisor
后台启动 (这里直接把命令写入了脚本通过ssh实现一键启动)
#!/bin/bash
source /etc/profile
nohup /export/server/storm/bin/storm nimbus >/dev/null 2>&1 &
echo "node1 nimbus is running"
nohup /export/server/storm/bin/storm ui >/dev/null 2>&1 &
echo "node1 core is running"
for host in node2 node3
do
{
ssh $host "source /etc/profile;nohup /export/server/storm/bin/storm supervisor >/dev/null 2>&1 &"
echo "$host Supervisor is running"
}
done
nohup,表示不挂起的意思( no hang up) ,忽略所有挂断信号。要运行后台中的 nohup 命令,添加 & 到命令的尾部。
/dev/null,表示空设备文件
2>&1,2就是标准错误,1是标准输出,2>&1表示把标准错误重定向到标准输出么
&,表示即使终端关闭程序仍会运行(前提是你把程序递交到服务器上)
所以,像这样的命令nohup /export/server/storm/bin/storm nimbus >/dev/null 2>&1 &
就表示后台启动程序并且不输出启动日志文件
进入web页面查看集群
浏览器登入node1:8080
进入web页面后可以查看Nimbus主节点信息, 其中node1是主节点
如果在node2或node3上也执行了storm nimbus
命令那么其会作为主节点备份, 状态会显示为 Not a Leader :
在Topology summary(Topology信息)中可以看到Topology程序的名称, 所有者, 状态, 运行时间和程序运行的其他相关信息包括worker数量,进程数量等, 这些可以在编写程序时在代码中指定; 也可以通过点击Topology程序的名称进入下级页面查看更详细的任务信息
web页面中还有Supervisor Summary(从节点信息)和Nimbus Configuration(主节点配置)等内容…
依赖
org.apache.storm
storm-core
1.1.1
编写Spout类读取日志文件中的内容, 并把数据发送给下游Bolt类进行处理
/***
* Version:
* Description: 读取外部文件,把一行一行的数据发送给下游的bolt
* 类似于hadoop mapreduce的inputformat
***/
public class ReadFileSpout extends BaseRichSpout {
private SpoutOutputCollector spoutOutputCollector;
private BufferedReader bufferedReader;
/**
* 初始化方法, 类似于这个类的构造器, 只被运行一次
* 一般用来打开数据链接, 打开网络连接
* @param map 传入的是storm集群的配置文件和用户自定义的配置文件, 一般不用
* @param topologyContext 上下文对象, 一般不用
* @param spoutOutputCollector 数据输出的收集器,spout把数据传给此参数,由此参数传给storm框架
*/
public void open(Map map, TopologyContext topologyContext, SpoutOutputCollector spoutOutputCollector) {
try {
//本地模式
//this.bufferedReader = new BufferedReader(new FileReader(new File("D:\\wordcount.txt")));
//集群模式
this.bufferedReader = new BufferedReader(new FileReader(new File("//root//stormdata//wordcount.txt")));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
this.spoutOutputCollector = spoutOutputCollector;
}
/**
* 下一个tuple, tuple是数据传送的基本单位
* 后台有个while方法一直调用该方法, 每调用一次就发送一个tuple出去
*/
public void nextTuple() {
String line = null;
try {
//一行一行的读取文件内容,并且一行一行的发送
line = bufferedReader.readLine();
if (line != null){
spoutOutputCollector.emit(Arrays.asList(line));
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 通过字段声明发出的数据是什么
* @param outputFieldsDeclarer
*/
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
outputFieldsDeclarer.declare(new Fields("line"));
}
}
编写Bolt类对出入的内容进行单词切分
/***
* Description: 输入一行一行的数据
* 对一行数据进行切割
* 输出单词及单词出现的次数
***/
public class SplitBolt extends BaseRichBolt {
private OutputCollector outputCollector;
/**
* 初始化方法,只被运行一次
* @param map 配置文件
* @param topologyContext 上下文对象
* @param outputCollector 数据收集器
*/
@Override
public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {
this.outputCollector = outputCollector;
}
/**
* 执行业务逻辑的方法
* @param tuple 获取的上游数据
*/
@Override
public void execute(Tuple tuple) {
//获取上游句子(字段:"line")
String line = tuple.getStringByField("line");
//对句子进行切割
String[] words = line.split(" ");
//发送数据
for (String word : words) {
//需要发送单词和单词出现的次数,总共两个字段
outputCollector.emit(Arrays.asList(word, "1"));
}
}
/**
* 声明发送出去的数据
* @param outputFieldsDeclarer
*/
@Override
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
outputFieldsDeclarer.declare(new Fields("word", "num"));
}
}
编写Bolt类对单词进行计数
/***
* Description: 负责统计每个单词出现的次数, 类似于hadoop mapreduce的reduce
* 输入单词及单词出现的次数
* 输出打印在控制台
***/
public class WordCountBolt extends BaseRichBolt {
//定义一个map用于储存单词及其数量
private Map wordCountMap = new HashMap<>();
/**
* 初始化方法
* @param map 配置文件
* @param topologyContext 上下文对象
* @param outputCollector 数据收集器
*/
@Override
public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {
//由于WordCountBolt是最后一个bolt所以不需要提取出OutputCollector
}
@Override
public void execute(Tuple tuple) {
//获取信息(单词, 数量)
String word = tuple.getStringByField("word");
String num = tuple.getStringByField("num");
//使用map进行记录
//开始计数
if (wordCountMap.containsKey(word)){
//如果map里已经有这个单词,就把数量进行累加
Integer integer = wordCountMap.get(word);
wordCountMap.put(word, integer + Integer.parseInt(num));
}else {
//如果map里已经没有这个单词,就把单词和数量放入map
wordCountMap.put(word, Integer.parseInt(num));
}
//打印
System.out.println(wordCountMap);
}
@Override
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
//由于不向外发送数据,所以不用写
}
}
编写启动类对程序进行整合
/***
* Description: wordcount驱动类,用来提交任务
***/
public class WordCountTopology {
public static void main(String[] args) throws InvalidTopologyException, AuthorizationException, AlreadyAliveException {
//通过TopologyBuilder 封装任务信息
TopologyBuilder topologyBuilder = new TopologyBuilder();
//设置spout获取数据
//SpoutDeclarer setSpout(String id, IRichSpout spout, Number parallelism_hint):参数:自定义id, spout对象, 并发数量
topologyBuilder.setSpout("readfilesspout", new ReadFileSpout(), 2);
//设置splitbolt 对句子进行切割
topologyBuilder.setBolt("splitbolt", new SplitBolt(), 4).shuffleGrouping("readfilesspout");
//设置wordcountbolt 对单词进行统计
topologyBuilder.setBolt("wordcountbolt", new WordCountBolt(), 2).shuffleGrouping("splitbolt");
//准备一个配置文件
Config config = new Config();
//启动2个worker!
config.setNumWorkers(2);
//任务提交有:本地模式 和 集群模式
//本地模式
//LocalCluster localCluster = new LocalCluster();
//localCluster.submitTopology("wordcount", config, topologyBuilder.createTopology());
//集群模式,参数:Topology名字, 配置文件, Topology对象
StormSubmitter.submitTopology("wordcount2", config, topologyBuilder.createTopology());
}
}
执行程序
1 ) 选择本地模式运行
直接运行驱动类的main方法即可, 统计后的结果会直接打印在控制台
2 ) 选择上传到集群进行执行
首先通过maven的package命名将程序打好jar包
注意在storm-core的依赖中加入:
在pom文件中加入maven的打包插件:
maven-assembly-plugin
jar-with-dependencies
com.yahaha.storm.kanban.kanbanTopolgy
make-assembly
package
single
org.apache.maven.plugins
maven-compiler-plugin
3.7.0
1.8
在上传到node2或node3上, 在指定路径下要确保存在日志文件
通过storm命令执行程序:
bin/storm jar storm-wordcount-1.0-SNAPSHOT.jar com.yahaha.storm.wc.WordCountTopology
当被读取的日志文件发生实时增加时, storm程序的处理也会实时同步进行!