34-Storm学习-Storm教程:纯手敲WordCount程序


storm核心的基本原理,上一篇我们大概都了解了一下。

33-Storm学习-史上最通俗易懂Storm教程:大白话介绍Storm

现在我们,写一下代码,去体验一下storm的程序是怎么开发的,通过了解了代码之后,再回头去看一下之前讲解的一些基本原理,就清楚了一些。

案列做一个单词计数器。

你可以认为,storm源源不断的接收到一些句子,然后你需要实时的统计出句子中每个单词的出现次数

(1)搭建工程环境




  4.0.0
  storm-wordcount
  jar

  storm-wordcount

  
    UTF-8
  

  
    
      org.apache.storm
      storm-core
      1.1.0
    
    
      commons-collections
      commons-collections
      3.2.1
    
    
      com.google.guava
      guava
    
  

  
    src/main/java
    test/main/java

    
        
            org.apache.maven.plugins
            maven-shade-plugin
            
                true
                
                    
                        *:*
                        
                            META-INF/*.SF
                            META-INF/*.sf
                            META-INF/*.DSA
                            META-INF/*.dsa
                            META-INF/*.RSA
                            META-INF/*.rsa
                            META-INF/*.EC
                            META-INF/*.ec
                            META-INF/MSFTSIG.SF
                            META-INF/MSFTSIG.RSA
                        
                    
                
            
            
                
                    package
                    
                        shade
                    
                    
                        
                            
                            
                            
                        
                    
                
            
        

      
        org.codehaus.mojo
        exec-maven-plugin
        1.2.1
        
          
            
              exec
            
          
        
        
          java
          true
          false
          compile
          
        
      
    
  

(2)编写代码

/**
 * 单词计数拓扑
 */
public class WordCountTopology {

    /**
     * Spout - 继承基础的BaseRichSpout,尝试去数据源获取数据,比如说从kafka中消费数据
     */
    public static class RandomSentenceSpout extends BaseRichSpout {

      SpoutOutputCollector _collector;
      Random _rand;

      /**
       * open方法是对Spout 进行初始化的
       * 比如创建线程池、数据库连接池、构造一个httpclient
       * 在open方法初始化的时候,会传入一个SpoutOutputCollector,这个类就是用来发射数据的。
       */
      @Override
      public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
        _collector = collector;
        //构造一个随机生产对象
        _rand = new Random();
      }

      /**
       * 这个Spout之前说过,最终会运行在task中,某个worker进程的某个excuter执行内部的某个task
       * 这个task会无限循环的去调用nextTuple()方法
       * 这就可以不断发射最新的数据出去,形成一个数据流Stream
       */
      @Override
      public void nextTuple() {
        Utils.sleep(100);
        String[] sentences = new String[]{sentence("the cow jumped over the moon"), sentence("an apple a day keeps the doctor away"),
                sentence("four score and seven years ago"), sentence("snow white and the seven dwarfs"), sentence("i am at two with nature")};
        // 从sentences.length的长度中随机获取一个整数
        final String sentence = sentences[_rand.nextInt(sentences.length)];
        // 发射数据
        _collector.emit(new Values(sentence));
      }

      protected String sentence(String input) {
        return input;
      }

      @Override
      public void ack(Object id) {
      }

      @Override
      public void fail(Object id) {
      }

      /**
       * declareOutputFields方法:定义一个你发出去的每个tuple中的每个field的名称
       */
      @Override
      public void declareOutputFields(OutputFieldsDeclarer declarer) {
        declarer.declare(new Fields("sentence"));
      }

    }

  /**
   * bolt : spout将数据传给bolt,
   * 每个bolt同样是发送到某个worker进程的某个excuter执行内部的某个task
   *
   */
  public static class SplitSentence implements IRichBolt {

    private static final long serialVersionUID = 6604009953652729483L;
        
        private OutputCollector collector;
        
        /**
         * 对于bolt来说,第一个方法,就是prepare方法,初始化的方法
         * 传入的OutputCollector,这个也是Bolt的这个tuple的发射器
         */
        @SuppressWarnings("rawtypes")
        public void prepare(Map conf, TopologyContext context, OutputCollector collector) {
            this.collector = collector;
        }
        
        /**
         * execute方法
         * 就是说,每次接收到一条数据后,就会交给这个executor方法来执行
         * 
         */
        public void execute(Tuple tuple) {
            String sentence = tuple.getStringByField("sentence"); 
            String[] words = sentence.split(" "); 
            for(String word : words) {
                collector.emit(new Values(word)); 
            }
        }

        /**
         * 定义发射出去的tuple,每个field的名称
         */
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            declarer.declare(new Fields("word"));   
        }
  }

  
    public static class WordCount extends BaseRichBolt {

        private static final long serialVersionUID = 7208077706057284643L;
        
        private static final Logger LOGGER = LoggerFactory.getLogger(WordCount.class);

        private OutputCollector collector;
        private Map wordCounts = new HashMap();
        
        @SuppressWarnings("rawtypes")
        public void prepare(Map conf, TopologyContext context, OutputCollector collector) {
            this.collector = collector;
        }
        
        public void execute(Tuple tuple) {
            String word = tuple.getStringByField("word");
            
            Long count = wordCounts.get(word);
            if(count == null) {
                count = 0L;
            }
            count++;
            
            wordCounts.put(word, count);
            
            LOGGER.info("【单词计数】" + word + "出现的次数是" + count);  
            
            collector.emit(new Values(word, count));
        }

        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            declarer.declare(new Fields("word", "count"));    
        }
        
    }


    public static void main(String[] args) {
        //1、准备任务信息
        //Storm框架支持多语言,在JAVA环境下创建一个拓扑,需要使用TopologyBuilder进行构建
        // 在main方法中,会去将spout和bolts组合起来,构建成一个拓扑
        TopologyBuilder builder = new TopologyBuilder();

    
        // 这里的第一个参数的意思,就是给这个spout设置一个名字
        // 第二个参数的意思,就是创建一个spout的对象
        // 第三个参数的意思,就是设置spout的executor有几个
        builder.setSpout("RandomSentence", new RandomSentenceSpout(), 2);
        builder.setBolt("SplitSentence", new SplitSentence(), 5)
                .setNumTasks(10)
                .shuffleGrouping("RandomSentence");
        // 这个很重要,就是说,相同的单词,从SplitSentence发射出来时,
            一定会进入到下游的指定的同一个task中
        // 只有这样子,才能准确的统计出每个单词的数量
        // 比如你有个单词,hello,下游task1接收到3个hello,task2接收到2个hello
        // 所以这5个hello,应该全都进入一个task,这样出来的就是5个hello。
        builder.setBolt("WordCount", new WordCount(), 10)
                .setNumTasks(20)
                .fieldsGrouping("SplitSentence", new Fields("word"));  
        
        //2、任务提交
        //提交给谁?提交什么内容?
        Config config = new Config();
    
        // 说明是在命令行执行,打算提交到storm集群上去
        if(args != null && args.length > 0) {
            //定义你希望集群分配多少个工作进程给你来执行这个topology
            config.setNumWorkers(3);  
            try {
                StormSubmitter.submitTopology(args[0], config, builder.createTopology());  
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            // 说明是在eclipse里面本地运行
            config.setMaxTaskParallelism(20);  
            
            LocalCluster cluster = new LocalCluster();
            cluster.submitTopology("WordCountTopology", config, builder.createTopology());  
            
            Utils.sleep(60000); 
            
            cluster.shutdown();
        }
    }
}

本地启动也可以看到效果。

你可能感兴趣的:(#,Storm实时性大数据处理,#)