功能说明:设计一个topology,来实现对文档里面的单词出现的频率进行统计。本篇博客是在storm概念讲解和storm集群搭建的基础上来的
1.创建一个maven项目:
添加以来如下:
<dependency>
<groupId>org.apache.storm</groupId>
<artifactId>storm-core</artifactId>
<version>1.0.6</version>
<!--storm集群里面已经有jar包,所以添加下面的provided表示已提供jar包,所以打包的时候就要剔除相应的jar包,现在跑本地需要注释掉-->
<!--<scope>provided</scope>-->
</dependency>
创建一个MySpout类继承BaseRichSpout类,用于接收外部数据源的数据
package com.wx.storm1;
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 java.util.Map;
public class MySpout extends BaseRichSpout {
SpoutOutputCollector outputCollector;
//初始化的方法,该方法调用一次,主要由Storm框架传入SpoutOutputCollector
public void open(Map map, TopologyContext topologyContext, SpoutOutputCollector spoutOutputCollector) {
this.outputCollector=spoutOutputCollector;
}
//storm 框架在 while(true) 调用nextTuple方法
public void nextTuple() {
outputCollector.emit(new Values("I love you,wang zu xian,my names Mr.Wang"));
}
//消息源可以发送多条消息流stream,多条消息流可以理解为多种类型的数据
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
outputFieldsDeclarer.declare(new Fields("word"));
}
}
创建一个MyBolt继承于BaseRichBolt,主要是分割单词,得到的单词计数输出
package com.wx.storm1;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.IBasicBolt;
import org.apache.storm.topology.IRichBolt;
import org.apache.storm.topology.IWindowedBolt;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;
import java.util.Map;
public class MyBolt extends BaseRichBolt{
OutputCollector outputCollector;
//初始化方法
public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {
this.outputCollector=outputCollector;
}
//被storm框架 while(true) 循环调用 传入参数tuple
public void execute(Tuple tuple) {
//这里将单词分割,得到的单词计数输出
String string = tuple.getString(0);
String[] split = string.split(" ");
for (String val:split)
{
outputCollector.emit(new Values(val,1));
}
}
//输出到下一个bolt
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
outputFieldsDeclarer.declare(new Fields("word","num"));
}
}
创建一个MyBoltCount继承于BaseRichBolt ,主要是对相同的单词做统计
package com.wx.storm1;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.IBasicBolt;
import org.apache.storm.topology.IRichBolt;
import org.apache.storm.topology.IWindowedBolt;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.tuple.Tuple;
import java.util.HashMap;
import java.util.Map;
public class MyBoltCount extends BaseRichBolt {
OutputCollector outputCollector;
Map<String,Integer> map=new HashMap<String, Integer>();
//初始化方法
public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {
this.outputCollector=outputCollector;
}
//被storm框架 while(true) 循环调用 传入参数tuple
public void execute(Tuple tuple) {
//这里就将相同的单词统计出来,然后输出
String word = tuple.getString(0);
Integer num = tuple.getInteger(1);
if(!map.containsKey(word))
{
map.put(word,num);
}
else
{
Integer count=map.get(word);
map.put(word,num+count);
}
System.out.println("count:"+map);
}
//这个bolt就是尽头了,不输出
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
}
}
创建一个拓扑来分配任务:
package com.wx.storm1;
import org.apache.storm.Config;
import org.apache.storm.StormSubmitter;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.tuple.Fields;
public class WordCountStrom {
public static void main(String[] args) throws Exception
{
//Storm计算wordcount
//1.创建一个TopologyBuilder
TopologyBuilder topologyBuilder = new TopologyBuilder();
//指定Spout的类和需要task的数量,task的数量即线程的数量
topologyBuilder.setSpout("myspout",new MySpout(),2);
//指定需要的第一级bolt的类和task的数量
topologyBuilder.setBolt("mybolt1",new MyBolt(),2).shuffleGrouping("myspout"); //随机函数分组
topologyBuilder.setBolt("mybolt2",new MyBoltCount(),4).fieldsGrouping("mybolt1",new Fields("word"));//hash取模分组
//2.创建一个configuration,用来指定当前topology 需要的worker的数量
Config config=new Config();
config.setNumWorkers(3);
//3.提交任务 -----两种模式 本地模式和集群模式
//StormSubmitter.submitTopology("wordcount",config,topologyBuilder.createTopology());
LocalCluster localCluster = new LocalCluster();
localCluster.submitTopology("wordcount",config,topologyBuilder.createTopology());
}
}
测试结果:
如果要提交到集群运行:
首先需要修改pom文件,表示storm集群中已经提供jar包
其次提交拓扑的任务修改为集群模式
在idea中进行打包:双击package打包成功:
打的jar包在target目录下:ok ,上传至集群,准备运行
执行运行
集群的拓扑途中就有了:
使用命令把他kill 掉:storm kill wordcount
可以查看日志:cd /export/servers/storm/logs/
2.Storm常用操作命令
有许多简单且有用的命令可以用来管理拓扑,它们可以提交、杀死、禁用、再平衡拓扑。
1.提交任务命令格式:storm jar 【jar路径】 【拓扑包名.拓扑类名】 【拓扑名称】
bin/storm jar examples/storm-starter/storm-starter-topologies-0.9.6.jar storm.starter.WordCountTopology wordcount
2.杀死任务命令格式:storm kill 【拓扑名称】 -w 10(执行kill命令时可以通过-w [等待秒数]指定拓扑停用以后的等待时间)
storm kill topology-name -w 10
3.停用任务命令格式:storm deactivte 【拓扑名称】
storm deactivte topology-name
我们能够挂起或停用运行中的拓扑。当停用拓扑时,所有已分发的元组都会得到处理,但是spouts的nextTuple方法不会被调用。销毁一个拓扑,可以使用kill命令。它会以一种安全的方式销毁一个拓扑,首先停用拓扑,在等待拓扑消息的时间段内允许拓扑完成当前的数据流。
4.启用任务命令格式:storm activate【拓扑名称】
storm activate topology-name
5.重新部署任务命令格式:storm rebalance 【拓扑名称】
storm rebalance topology-name
再平衡使你重分配集群任务。这是个很强大的命令。比如,你向一个运行中的集群增加了节点。再平衡命令将会停用拓扑,然后在相应超时时间之后重分配工人,并重启拓扑。
3.执行原理图(Mr.Mao的图)
这里我们stream grouping的分组方式是按字段进行分组,比如按userid来分组,具有同样userid的tuple会被分到相同的Bolts里的一个task,而不同的userid则会被分配到不同的bolts里的task。
4.Stream Grouping详解
Storm里面有7种类型的stream grouping
- Shuffle Grouping: 随机分组, 随机派发stream里面的tuple,保证每个bolt接收到的tuple数目大致相同。
- Fields Grouping:按字段分组,比如按userid来分组,具有同样userid的tuple会被分到相同的Bolts里的一个task,而不同的userid则会被分配到不同的bolts里的task。
- All Grouping:广播发送,对于每一个tuple,所有的bolts都会收到。
- Global Grouping:全局分组, 这个tuple被分配到storm中的一个bolt的其中一个task。再具体一点就是分配给id值最低的那个task。
- Non Grouping:不分组,这stream grouping个分组的意思是说stream不关心到底谁会收到它的tuple。目前这种分组和Shuffle grouping是一样的效果, 有一点不同的是storm会把这个bolt放到这个bolt的订阅者同一个线程里面去执行。
- Direct Grouping: 直接分组, 这是一种比较特别的分组方法,用这种分组意味着消息的发送者指定由消息接收者的哪个task处理这个消息。只有被声明为Direct Stream的消息流可以声明这种分组方法。而且这种消息tuple必须使用emitDirect方法来发射。消息处理者可以通过TopologyContext来获取处理它的消息的task的id (OutputCollector.emit方法也会返回task的id)。
- Local or shuffle grouping:如果目标bolt有一个或者多个task在同一个工作进程中,tuple将会被随机发生给这些tasks。否则,和普通的Shuffle Grouping行为一致。
5.问题(Mr.Mao的图)
1.集群如何启动,任务如何执行?
2、集群如何通信:集群架构中的各个模块是如何通信的?拓扑程序中的各个Task是如何通信的?
nimbus与supervisor之间是通过zk进行通信的
不同机器上的worker是基于网络io的socket通信(netty)
worker内部是基于县城的通信,线程于线程通信
3.如何保证消息的不丢失 ack-fail机制如何实现的?
4.storm 启动流程分析
流式计算一般架构图
下面会学习kafka....