一 storm的集群部署
博客地址:http://www.panchengming.com/2018/01/26/pancm70/
二 storm的常用操作
有许多简单且有用的命令可以用来管理拓扑,它们可以提交、杀死、禁用、再平衡拓扑。
(1).提交任务命令格式:storm jar 【jar路径】 【拓扑包名.拓扑类名】 【拓扑名称】
bin/storm jar examples/storm-starter/storm-starter-topologies-0.9.6.jar storm.starter.WordCountTopology wordcount
拓扑类中定义了topology结构,这是整个处理程序的启动开关。storm接收到topology后会根据它的结构分配机器和端口号生成相应的worker,executor,task等实体处理实时数据。
(2).杀死任务命令格式:storm kill 【拓扑名称】 -w 10(执行kill命令时可以通过-w [等待秒数]指定拓扑停用以后的等待时间)
storm kill topology-name -w
(3).停用任务命令格式:storm deactivte 【拓扑名称】
storm deactivte topology-name
我们能够挂起或停用运行中的拓扑。当停用拓扑时,所有已分发的元组都会得到处理,但是spouts的nextTuple方法不会被调用。销毁一个拓扑,可以使用kill命令。它会以一种安全的方式销毁一个拓扑,首先停用拓扑,在等待拓扑消息的时间段内允许拓扑完成当前的数据流。
(4).启用任务命令格式:storm activate【拓扑名称】
storm activate topology-name
(5).重新部署任务命令格式:storm rebalance 【拓扑名称】
storm rebalance topology-name
再平衡使你重分配集群任务。这是个很强大的命令。比如,你向一个运行中的集群增加了节点。再平衡命令将会停用拓扑,然后在相应超时时间之后重分配工人,并重启拓扑。
三 storm代码编写
3.1 topology的创建
编写main程序,方法含义注释中有
内部过程
- 上传代码并做校验(/data/nimbus/inbox);前面已经讲过上传的命令
- 建立本地目录(/data/nimbus/stormdist/topology-id/);
- 建立zookeeper上的心跳目录;
- 计算topology的工作量(parallelism hint),分配task-id并写入zookeeper;
- 把task分配给supervisor执行;
- 在supervisor中定时检查是否有新的task,下载新代码、删除老代码,剩下的工作交个小弟worker;
- 在worker中把task拿到,看里面有哪些spout/Bolt,然后计算需要给哪些task发消息并建立连接;
- 在nimbus将topology终止的时候会将zookeeper上的相关信息删除;
public static void main(String[] args) {
// TODO Auto-generated method stub
//定义一个拓扑
TopologyBuilder builder=new TopologyBuilder();
//设置一个Executeor(线程),默认一个
builder.setSpout(str1, new TestSpout());
//设置一个Executeor(线程),和一个task(不设置task时默认是1和executor相同)
builder.setBolt(str2, new
TestBolt(),1).setNumTasks(1).shuffleGrouping(str1);
Config conf = new Config();
conf.put("test", "test");
try{
//运行拓扑
if(args !=null&&args.length>0){ //有参数时,表示向集群提交作业,并把第一个参数当做topology名称
System.out.println("远程模式");
StormSubmitter.submitTopology(args[0], conf, builder.createTopology());
} else{//没有参数时,本地提交
System.out.println("本地模式");
LocalCluster cluster = new LocalCluster();
cluster.submitTopology("111" ,conf, builder.createTopology() );
Thread.sleep(10000);
// 关闭本地集群
cluster.shutdown();
}
}catch (Exception e){
e.printStackTrace();
}
}
3.2 spout的创建
一般是实现 IRichSpout 或继承BaseRichSpout该类,然后实现该方法。 这里我们继承BaseRichSpout这个类,该类需要实现这几个主要的方法:
一、open
open()方法中是在ISpout接口中定义,在Spout组件初始化时被调用。 有三个参数,它们的作用分别是:
- Storm配置的Map;
- topology中组件的信息;
- 发射tuple的方法;
代码示例:
@Override
public void open(Map map, TopologyContext arg1, SpoutOutputCollector collector) {
System.out.println("open:"+map.get("test"));
this.collector = collector;
}
二、nextTuple
nextTuple()方法是Spout实现的核心。 也就是主要执行方法,用于输出信息,通过collector.emit方法发射。
这里我们的数据信息已经写死了,所以这里我们就直接将数据进行发送。 这里设置只发送两次。 代码示例:
@Override
public void nextTuple() {
if(count<=2){
System.out.println("第"+count+"次开始发送数据...");
this.collector.emit(new Values(message));
}
count++;
}
三、declareOutputFields
declareOutputFields是在IComponent接口中定义,用于声明数据格式。 即输出的一个Tuple中,包含几个字段。
因为这里我们只发射一个,所以就指定一个。如果是多个,则用逗号隔开。 代码示例:
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
System.out.println("定义格式...");
declarer.declare(new Fields(field));
}
四、ack
ack是在ISpout接口中定义,用于表示Tuple处理成功。
代码示例:
@Override
public void ack(Object obj) {
System.out.println("ack:"+obj);
}
五、fail
fail是在ISpout接口中定义,用于表示Tuple处理失败。
代码示例:
@Override
public void fail(Object obj) {
System.out.println("失败:"+obj);
}
六、close
close是在ISpout接口中定义,用于表示Topology停止。
代码示例:
@Override
public void close() {
System.out.println("关闭...");
}
3.3 bolt的创建
Bolt是这样一种组件,它把元组作为输入,然后产生新的元组作为输出。实现一个bolt时,通常需要实现IRichBolt接口。Bolts对象由客户端机器创建,序列化为拓扑,并提交给集群中的主机。然后集群启动工人进程反序列化bolt,调用prepare,最后开始处理元组。具体参数含义和spout相同不过多解释
//为bolt声明输出模式
declareOutputFields(OutputFieldsDeclarer declarer)
//仅在bolt开始处理元组之前调用
prepare(java.util.Map stormConf, TopologyContext context, OutputCollector collector)
//处理输入的单个元组
execute(Tuple input)
//在bolt即将关闭时调用
cleanup()
四 storm的并行度计算
conf.setNumWorkers(2); // 该Topology运行在Supervisor节点的2个Worker进程中
topologyBuilder.setSpout("blue-spout", new BlueSpout(), 2); // 设置并行度为2,则Task个数为2*1
topologyBuilder.setBolt("green-bolt", new GreenBolt(), 2)
.setNumTasks(4)
.shuffleGrouping("blue-spout"); // 设置并行度为2,设置Task个数为4 ,则Task个数为4
topologyBuilder.setBolt("yellow-bolt", new YellowBolt(), 6)
.shuffleGrouping("green-bolt"); // 设置并行度为6,则Task个数为6*1
那么,下面我们看Storm是如何计算一个Topology运行时的并行度,并分配到2个Worker中的:
- 计算Task总数:2乘1+4+6乘1=12(总计创建12个Task实例)
- 计算运行时Topology并行度:10/2=5(每个Worker对应5个Executor,Supervisor一般指定了端口数创建的worker数也是一定的,当topology需要的worker数大于实际的storm剩余时只会分配目前系统剩余最大的worker数)
- 将12个Task分配到2个Worker中的5*2个Executor中:应该是每个Worker上5个Executor,将6个Task分配到5个Executor中
- 每个Worker中分配6个Task,应该是分配3个Yellow Task、2个Green Task、1个Blue Task
- Storm内部优化:会把同类型的Task尽量放到同一个Executor中运行
- 分配过程:从Task个数最少的开始,1个Blue Task只能放到一个Executor,总计1个Executor被占用;2个Green Task可以放到同一个Executor中,总计2个Executor被占用;最后看剩下的3个Yellow Task能否分配到5-2=3个Executor中,显然每个Yellow Task对应一个Executor
五 storm的工作过程理解(计算单词数)
5.1 主程序编写
import backtype.storm.Config;
import backtype.storm.LocalCluster;
import backtype.storm.StormSubmitter;
import backtype.storm.generated.AlreadyAliveException;
import backtype.storm.generated.InvalidTopologyException;
import backtype.storm.topology.TopologyBuilder;
import backtype.storm.tuple.Fields;
public class WordCountTopologMain {
public static void main(String[] args) throws AlreadyAliveException, InvalidTopologyException {
//1、准备一个TopologyBuilder
TopologyBuilder topologyBuilder = new TopologyBuilder();
topologyBuilder.setSpout("mySpout",new MySpout(),2);
topologyBuilder.setBolt("mybolt1",new MySplitBolt(),2).shuffleGrouping("mySpout");
topologyBuilder.setBolt("mybolt2",new MyCountBolt(),4).fieldsGrouping("mybolt1", new Fields("word"));
// topologyBuilder.setBolt("mybolt2",new MyCountBolt(),4).shuffleGrouping("mybolt1");
//2、创建一个configuration,用来指定当前topology 需要的worker的数量
Config config = new Config();
config.setNumWorkers(2);
//3、提交任务 -----两种模式 本地模式和集群模式
StormSubmitter.submitTopology("mywordcount",config,topologyBuilder.createTopology());
LocalCluster localCluster = new LocalCluster(); localCluster.submitTopology("mywordcount",config,topologyBuilder.createTopology());
}
}
5.2 spout编写
import backtype.storm.spout.SpoutOutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.IRichSpout;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.topology.base.BaseRichSpout;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Values;
import java.util.Map;
public class MySpout extends BaseRichSpout {
SpoutOutputCollector collector;
//初始化方法
public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
this.collector = collector;
}
//storm 框架在 while(true) 调用nextTuple方法
public void nextTuple() {
collector.emit(new Values("i am lilei love hanmeimei"));
}
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("love"));
}
}
5.3 bolt编写
5.3.1 分割单词
import backtype.storm.task.OutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.IBasicBolt;
import backtype.storm.topology.IRichBolt;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.topology.base.BaseRichBolt;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Tuple;
import backtype.storm.tuple.Values;
import java.util.Map;
public class MySplitBolt extends BaseRichBolt {
OutputCollector collector;
//初始化方法
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
this.collector = collector;
}
// 被storm框架 while(true) 循环调用 传入参数tuple
public void execute(Tuple input) {
String line = input.getString(0);
String[] arrWords = line.split(" ");
for (String word:arrWords){
collector.emit(new Values(word,1));
}
}
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("word","num"));
}
}
5.3.2 统计单词数
import backtype.storm.task.OutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.IBasicBolt;
import backtype.storm.topology.IRichBolt;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.topology.base.BaseRichBolt;
import backtype.storm.tuple.Tuple;
import java.util.HashMap;
import java.util.Map;
public class MyCountBolt extends BaseRichBolt {
OutputCollector collector;
Map<String, Integer> map = new HashMap<String, Integer>();
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
this.collector = collector;
}
public void execute(Tuple input) {
String word = input.getString(0);
Integer num = input.getInteger(1);
System.out.println(Thread.currentThread().getId() + " word:"+word);
if (map.containsKey(word)){
Integer count = map.get(word);
map.put(word,count + num);
}else {
map.put(word,num);
}
// System.out.println("count:"+map);
}
public void declareOutputFields(OutputFieldsDeclarer declarer) {
//不輸出
}
}
5.4 工作流程解析
1 . 首先nimbus接收到topology进行资源分配并把分配信息发布到zookerper,其他supervisor通过监听zookerper完成资源初始化,创建两个worker(前提是supervisor中含有topology中设定的worker数,否则会在剩下的supervisor中分配worker(JVM,相当于一个进程))
案例中需要2个worker,storm满足要求随机分配了两个worker。其中spout、bolt1、bolt2的并发度分别是1+1+2的线程组合,并且每个线程中只含有一个spou/bolt产生的task实体。(如果设定了task数时的分配情况在第四点并行度分析里有介绍)
2. 在主程序中同时也设定了数据在现行topology中的分组规则,spout中获取数据源发送到bolt1的task时采用随机分组,也就是说两个worker中的bolt1实体b1-0和b1-1都能收到来自两个spout的task的信息(i am lilei love hanmeimei),并完成单词分割发送到下一级单词统计bolt2的task实体中,这里采用的关键字分组,同一个单词的数据只用同一个bolt2产生的task实体去统计,这样就不会造成一个单词的统计数据会分布在多个map中,最后可能还需要再建一级bolt只产生一个task实体用来将各个bolt2的实体统计的单词数量汇总。
假如采用随机分组:可能b2-0和b2-2中统计了5个“i”和6个“love”。b2-1和b2-3统计了2个“i”和2个“love”,最后实际统计的数是两部分总和,也就是7个“i”和8个“love”。同时当同一个worker中的b2-0和b2-2的成员变量共享也就是map是同一个,如果b2-0和b2-2同时去统计存储“i”的话就会存在线程安全问题(使用redis存储可以解决这个问题)
3. 对上一个单词统计分组问题的补充:在storm中一个jvm里同一个bolt/spout产生的task实例共享同一个反序列化对象,也就是说上图中由bolt2产生的在同一个worker中b2-1和bolt2-3用来统计单词数的map集合是同一个,换句话说就是说b2-1和bolt2-3分别统计的单词数据存在同一个map对象中,它们map对象的引用指向的是栈中的同一个map对象。
(不同worker中同一个bolt产生的task实体的成员变量不共享,同一个worker中同一个bolt产生的task实体成员变量共享)
测试错误总结
1 Caused by: java.lang.RuntimeException: java.io.IOException: Found multiple d
这个错很可能四jar包冲突,storm-core的核心包在storm服务器上有,不需要上传
2 not attempt to authenticate using SASL (unknown error)
这是把本地运行模式打包了没有改成集群模式运行。