一  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程序,方法含义注释中有

内部过程

  1. 上传代码并做校验(/data/nimbus/inbox);前面已经讲过上传的命令
  2. 建立本地目录(/data/nimbus/stormdist/topology-id/);
  3. 建立zookeeper上的心跳目录;
  4. 计算topology的工作量(parallelism hint),分配task-id并写入zookeeper;
  5. 把task分配给supervisor执行;
  6. 在supervisor中定时检查是否有新的task,下载新代码、删除老代码,剩下的工作交个小弟worker;
  7. 在worker中把task拿到,看里面有哪些spout/Bolt,然后计算需要给哪些task发消息并建立连接;
  8. 在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组件初始化时被调用。 有三个参数,它们的作用分别是:

  1. Storm配置的Map;
  2. topology中组件的信息;
  3. 发射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 工作流程解析

简述storm中的stream groupings有哪几种方式_System

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)

这是把本地运行模式打包了没有改成集群模式运行。