Storm的wordCounter计数器详解

原文:http://www.maoxiangyi.cn/index.php/archives/362 拓扑

 package cn.jd.storm;
  
 
 
 import backtype.storm.Config;
 
 import backtype.storm.LocalCluster;
 
 import backtype.storm.topology.TopologyBuilder;
 
 import backtype.storm.tuple.Fields;
 
 /**
  
 					 * 功能说明:
 
 					 * 设计一个topology,来实现对一个句子里面的单词出现的频率进行统计。
 
 					 * 整个topology分为三个部分:
 
 					 *         WordReader:数据源,负责发送单行文本记录(句子)
 
 					 *         WordNormalizer:负责将单行文本记录(句子)切分成单词
 
 					 *         WordCounter:负责对单词的频率进行累加
 
 					 * 
 
 					 * @author 毛祥溢
 
 					 * Email:frank@maoxiangyi.cn
 
 					 * 2013-8-26 下午5:59:06
 
 					 */
 
 public class TopologyMain {
 
 
 
 /**
  
 					     * @param args 文件路径
 
 					     */
 
 public static void main(String[] args)throws Exception {
 
 // Storm框架支持多语言,在JAVA环境下创建一个拓扑,需要使用TopologyBuilder进行构建
 
 					        TopologyBuilder builder = new TopologyBuilder();
 
 /* WordReader类,主要是将文本内容读成一行一行的模式
  
 					         * 消息源spout是Storm里面一个topology里面的消息生产者。
 
 					         * 一般来说消息源会从一个外部源读取数据并且向topology里面发出消息:tuple。
 
 					         * Spout可以是可靠的也可以是不可靠的。
 
 					         * 如果这个tuple没有被storm成功处理,可靠的消息源spouts可以重新发射一个tuple,但是不可靠的消息源spouts一旦发出一个tuple就不能重发了。
 
 					         * 
 
 					         * 消息源可以发射多条消息流stream。多条消息流可以理解为多中类型的数据。
 
 					         * 使用OutputFieldsDeclarer.declareStream来定义多个stream,然后使用SpoutOutputCollector来发射指定的stream。
 
 					         * 
 
 					         * Spout类里面最重要的方法是nextTuple。要么发射一个新的tuple到topology里面或者简单的返回如果已经没有新的tuple。
 
 					         * 要注意的是nextTuple方法不能阻塞,因为storm在同一个线程上面调用所有消息源spout的方法。
 
 					         * 
 
 					         * 另外两个比较重要的spout方法是ack和fail。storm在检测到一个tuple被整个topology成功处理的时候调用ack,否则调用fail。storm只对可靠的spout调用ack和fail。
 
 					         */
 
 					        builder.setSpout("word-reader",new WordReader());
 
 /* WordNormalizer类,主要是将一行一行的文本内容切割成单词
  
 					         * 
 
 					         * 所有的消息处理逻辑被封装在bolts里面。Bolts可以做很多事情:过滤,聚合,查询数据库等等。
 
 					         * Bolts可以简单的做消息流的传递。复杂的消息流处理往往需要很多步骤,从而也就需要经过很多bolts。
 
 					         * 比如算出一堆图片里面被转发最多的图片就至少需要两步:
 
 					         *     第一步算出每个图片的转发数量。
 
 					         *     第二步找出转发最多的前10个图片。(如果要把这个过程做得更具有扩展性那么可能需要更多的步骤)。
 
 					         * 
 
 					         * Bolts可以发射多条消息流, 使用OutputFieldsDeclarer.declareStream定义stream,使用OutputCollector.emit来选择要发射的stream。
 
 					         * Bolts的主要方法是execute, 它以一个tuple作为输入,bolts使用OutputCollector来发射tuple。
 
 					         * bolts必须要为它处理的每一个tuple调用OutputCollector的ack方法,以通知Storm这个tuple被处理完成了,从而通知这个tuple的发射者spouts。 
 
 					         * 一般的流程是: bolts处理一个输入tuple,  发射0个或者多个tuple, 然后调用ack通知storm自己已经处理过这个tuple了。storm提供了一个IBasicBolt会自动调用ack。
 
 					         * 
 
 					         * 
 
 					         */
 
 					        builder.setBolt("word-normalizer", new WordNormalizer()).shuffleGrouping("word-reader");
 
 /*
  
 					         * 上面的代码和下面的代码中都设定了数据分配的策略stream grouping
 
 					         * 定义一个topology的其中一步是定义每个bolt接收什么样的流作为输入。stream grouping就是用来定义一个stream应该如果分配数据给bolts上面的多个tasks。
 
 					         * 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行为一致。
 
 					         * 
 
 					         */
 
 					        builder.setBolt("word-counter", new WordCounter(),1).fieldsGrouping("word-normalizer", new Fields("word"));
 
 
 
 /*
  
 					         * storm的运行有两种模式: 本地模式和分布式模式.
 
 					         * 1) 本地模式:
 
 					         *         storm用一个进程里面的线程来模拟所有的spout和bolt. 本地模式对开发和测试来说比较有用。 
 
 					         *         你运行storm-starter里面的topology的时候它们就是以本地模式运行的, 你可以看到topology里面的每一个组件在发射什么消息。
 
 					         * 2) 分布式模式:
 
 					         *         storm由一堆机器组成。当你提交topology给master的时候, 你同时也把topology的代码提交了。
 
 					         *         master负责分发你的代码并且负责给你的topolgoy分配工作进程。如果一个工作进程挂掉了, master节点会把认为重新分配到其它节点。
 
 					         * 下面是以本地模式运行的代码:
 
 					         * 
 
 					         * Conf对象可以配置很多东西, 下面两个是最常见的:
 
 					         *        TOPOLOGY_WORKERS(setNumWorkers) 定义你希望集群分配多少个工作进程给你来执行这个topology. 
 
 					         *            topology里面的每个组件会被需要线程来执行。每个组件到底用多少个线程是通过setBolt和setSpout来指定的。
 
 					         *            这些线程都运行在工作进程里面. 每一个工作进程包含一些节点的一些工作线程。
 
 					         *            比如, 如果你指定300个线程,60个进程, 那么每个工作进程里面要执行6个线程, 而这6个线程可能属于不同的组件(Spout, Bolt)。
 
 					         *            你可以通过调整每个组件的并行度以及这些线程所在的进程数量来调整topology的性能。
 
 					         *        TOPOLOGY_DEBUG(setDebug), 当它被设置成true的话, storm会记录下每个组件所发射的每条消息。
 
 					         *            这在本地环境调试topology很有用, 但是在线上这么做的话会影响性能的。
 
 					         */
 
 Config conf = new Config();
 
 					        conf.setDebug(true);
 
 					        conf.setNumWorkers(2);
 
 					        conf.put("wordsFile","/root/workspace1/com.jd.storm.demo/src/main/resources/words.txt");
 
 					        conf.setDebug(true);
 
 					        conf.put(Config.TOPOLOGY_MAX_SPOUT_PENDING, 1);
 
 /*
  
 					         * 定义一个LocalCluster对象来定义一个进程内的集群。提交topology给这个虚拟的集群和提交topology给分布式集群是一样的。
 
 					         * 通过调用submitTopology方法来提交topology, 它接受三个参数:要运行的topology的名字,一个配置对象以及要运行的topology本身。
 
 					         * topology的名字是用来唯一区别一个topology的,这样你然后可以用这个名字来杀死这个topology的。前面已经说过了, 你必须显式的杀掉一个topology, 否则它会一直运行。
 
 					         */
 
 					        LocalCluster cluster = new LocalCluster();
 
 					        cluster.submitTopology("wordCounterTopology", conf, builder.createTopology());
 
 Thread.sleep(1000);
 
 					        cluster.killTopology("wordCounterTopology");
 
 					        cluster.shutdown();
 
 }
 
 
 
 } 

读句子

 package cn.jd.storm;
  
 
 
 import java.io.BufferedReader;
 
 import java.io.FileNotFoundException;
 
 import java.io.FileReader;
 
 import java.util.Map;
 
 
 
 import backtype.storm.spout.SpoutOutputCollector;
 
 import backtype.storm.task.TopologyContext;
 
 import backtype.storm.topology.OutputFieldsDeclarer;
 
 import backtype.storm.topology.base.BaseRichSpout;
 
 import backtype.storm.tuple.Fields;
 
 import backtype.storm.tuple.Values;
 
 /**
  
 					 * 
 
 					 * 功能说明:
 
 					 *         主要是将文件内容读出来,一行一行
 
 					 * 
 
 					 *     Spout类里面最重要的方法是nextTuple。
 
 					 *     要么发射一个新的tuple到topology里面或者简单的返回如果已经没有新的tuple。
 
 					 *     要注意的是nextTuple方法不能阻塞,因为storm在同一个线程上面调用所有消息源spout的方法。
 
 					 *     另外两个比较重要的spout方法是ack和fail。
 
 					 *     storm在检测到一个tuple被整个topology成功处理的时候调用ack,否则调用fail。
 
 					 *  storm只对可靠的spout调用ack和fail。
 
 					 * 
 
 					 * @author 毛祥溢
 
 					 * Email:frank@maoxiangyi.cn
 
 					 * 2013-8-26 下午6:05:46
 
 					 */
 
 public class WordReader extends BaseRichSpout {
 
 
 
 private SpoutOutputCollector collector;
 
 private FileReader fileReader;
 
 private String filePath;
 
 private boolean completed = false;
 
 //storm在检测到一个tuple被整个topology成功处理的时候调用ack,否则调用fail。
 
 public void ack(Object msgId) {
 
 System.out.println("OK:"+msgId);
 
 }
 
 public void close() {}
 
 //storm在检测到一个tuple被整个topology成功处理的时候调用ack,否则调用fail。
 
 public void fail(Object msgId) {
 
 System.out.println("FAIL:"+msgId);
 
 }
 
 
 
 /*
  
 					   * 在SpoutTracker类中被调用,每调用一次就可以向storm集群中发射一条数据(一个tuple元组),该方法会被不停的调用
 
 					   */
 
 public void nextTuple() {
 
 if(completed){
 
 try {
 
 Thread.sleep(1000);
 
 } catch (InterruptedException e) {
 
 }
 
 return;
 
 }
 
 String str;
 
 BufferedReader reader =new BufferedReader(fileReader);
 
 try{
 
 while((str = reader.readLine()) != null){
 
 System.out.println("WordReader类 读取到一行数据:"+ str);
 
 this.collector.emit(new Values(str),str);
 
 System.out.println("WordReader类 发射了一条数据:"+ str);
 
 }
 
 }catch(Exception e){
 
 throw new RuntimeException("Error reading tuple",e);
 
 }finally{
 
 					            completed = true;
 
 }
 
 }
 
 
 
 public void open(Map conf, TopologyContext context,SpoutOutputCollector collector) {
 
 try {
 
 this.fileReader = new FileReader(conf.get("wordsFile").toString());
 
 } catch (FileNotFoundException e) {
 
 throw new RuntimeException("Error reading file ["+conf.get("wordFile")+"]");
 
 }
 
 this.filePath    = conf.get("wordsFile").toString();
 
 this.collector = collector;
 
 }
 
 /**
  
 					     * 定义字段id,该id在简单模式下没有用处,但在按照字段分组的模式下有很大的用处。
 
 					     * 该declarer变量有很大作用,我们还可以调用declarer.declareStream();来定义stramId,该id可以用来定义更加复杂的流拓扑结构
 
 					     */
 
 public void declareOutputFields(OutputFieldsDeclarer declarer) {
 
 					        declarer.declare(new Fields("line"));
 
 }
 
 } 

 
 将句子切割成单词
 

 package cn.jd.storm;
  
 
 
 import backtype.storm.topology.BasicOutputCollector;
 
 import backtype.storm.topology.OutputFieldsDeclarer;
 
 import backtype.storm.topology.base.BaseBasicBolt;
 
 import backtype.storm.tuple.Fields;
 
 import backtype.storm.tuple.Tuple;
 
 import backtype.storm.tuple.Values;
 
 /**
  
 					 * 
 
 					 * 功能说明:
 
 					 *     将一行文本切割成单词,并封装collector中发射出去
 
 					 * 
 
 					 * @author 毛祥溢
 
 					 * Email:frank@maoxiangyi.cn
 
 					 * 2013-8-26 下午6:05:59
 
 					 */
 
 public class WordNormalizer extends BaseBasicBolt {
 
 
 
 public void cleanup() {
 
 System.out.println("将一行文本切割成单词,并封装collector中发射出去 ---完毕!");
 
 }
 
 
 
 /**
  
 					     * 接受的参数是WordReader发出的句子,即input的内容是句子
 
 					     * execute方法,将句子切割形成的单词发出
 
 					     */
 
 public void execute(Tuple input, BasicOutputCollector collector) {
 
 String sentence = input.getString(0);
 
 String[] words = sentence.split(" ");
 
 System.out.println("WordNormalizer类 收到一条数据,这条数据是:    "+ sentence);
 
 for(String word : words){
 
 					            word = word.trim();
 
 if(!word.isEmpty()){
 
 					                word = word.toLowerCase();
 
 System.out.println("WordNormalizer类 收到一条数据,这条数据是:    "+ sentence+"数据正在被切割,切割出来的单词是 "+ word);
 
 					                collector.emit(new Values(word));
 
 }
 
 }
 
 }
 
 
 
 /**
  
 					    * 定义字段id,该id在简单模式下没有用处,但在按照字段分组的模式下有很大的用处。
 
 					    * 该declarer变量有很大作用,我们还可以调用declarer.declareStream();来定义stramId,该id可以用来定义更加复杂的流拓扑结构
 
 					    */
 
 public void declareOutputFields(OutputFieldsDeclarer declarer) {
 
 					        declarer.declare(new Fields("word"));
 
 }
 
 } 

计数器WordCounter

 package cn.jd.storm;
  
 
 
 import java.util.HashMap;
 
 import java.util.Map;
 
 
 
 import backtype.storm.task.TopologyContext;
 
 import backtype.storm.topology.BasicOutputCollector;
 
 import backtype.storm.topology.OutputFieldsDeclarer;
 
 import backtype.storm.topology.base.BaseBasicBolt;
 
 import backtype.storm.tuple.Tuple;
 
 /**
  
 					 * 
 
 					 * 功能说明:
 
 					 *     实现计数器的功能,第一次将collector中的元素存放在成员变量counters(Map)中.
 
 					 *     如果counters(Map)中已经存在该元素,getValule并对Value进行累加操作。
 
 					 * 
 
 					 * @author 毛祥溢
 
 					 * Email:frank@maoxiangyi.cn
 
 					 * 2013-8-26 下午6:06:07
 
 					 */
 
 public class WordCounter extends BaseBasicBolt {
 
 
 
 private static final long serialVersionUID = 5678586644899822142L;
 
 Integer id;
 
 String name;
 
 //定义Map封装最后的结果
 
 Map<String, Integer> counters;
 
 
 
 /**
  
 					     * 在spout结束时被调用,将最后的结果显示出来
 
 					     * 
 
 					     * 結果:
 
 					     * -- Word Counter [word-counter-2] --
 
 					     * really: 1
 
 					     * but: 1
 
 					     * application: 1
 
 					     * is: 2
 
 					     * great: 2
 
 					     */
 
 					    @Override
 
 public void cleanup() {
 
 System.out.println("-- Word Counter ["+name+"-"+id+"] --");
 
 for(Map.Entry<String, Integer> entry : counters.entrySet()){
 
 System.out.println(entry.getKey()+": "+entry.getValue());
 
 }
 
 System.out.println("实现计数器的功能 --完畢!");
 
 }
 
 
 
 /**
  
 					     * 初始化操作
 
 					     */
 
 					    @Override
 
 public void prepare(Map stormConf, TopologyContext context) {
 
 this.counters = new HashMap<String, Integer>();
 
 this.name = context.getThisComponentId();
 
 this.id = context.getThisTaskId();
 
 }
 
 
 
 public void declareOutputFields(OutputFieldsDeclarer declarer) {}
 
 
 
 /**
  
 					     * 实现计数器的功能,第一次将collector中的元素存放在成员变量counters(Map)中.
 
 					     * 如果counters(Map)中已经存在该元素,getValule并对Value进行累加操作。
 
 					     */
 
 public void execute(Tuple input, BasicOutputCollector collector) {
 
 String str = input.getString(0);
 
 System.out.println("WordCounter 计数器收到单词 "+ str);
 
 if(!counters.containsKey(str)){
 
 					            counters.put(str, 1);
 
 }else{
 
 Integer c = counters.get(str) + 1;
 
 					            counters.put(str, c);
 
 }
 
 }
 
 } 

数据格式

storm ni great he he xi wang test haha heihei very are mao xiang yi jd

运行日志

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java一日一条

2017年高频率的互联网校园招聘面试题

参加了2017年校招,面试了阿里、百度、腾讯、滴滴、美团、网易、去哪儿等公司,个人是客户端 Android 方向,总结了面试过程中频率出现较高的题目,希望对大家...

1642
来自专栏FD的专栏

一步步理解python的异步IO

看到越来越多的大佬都在使用python的异步IO,协程等概念来实现高效的IO处理过程,可是我对这些概念还不太懂,就学习了一下。 因为是初学者,在理解上有很多不到...

1432
来自专栏青玉伏案

Java中的网络编程

​  Java中的网路编程主要是Java的Socket编程,属于JavaEE中的高级的部分,以下内容是对java网路编程的一个小结,代码都是经过编译调试的   ...

1896
来自专栏JavaEdge

高性能队列——Disruptor总论1 背景2 Java内置队列3 ArrayBlockingQueue的问题4 Disruptor的设计方案代码样例性能等待策略Log4j 2应用场景

这里所说的队列是系统内部的内存队列,而不是Kafka这样的分布式队列 Disruptor特性限于3.3.4

1893
来自专栏大数据智能实战

Hbase的后缀过滤查询

HBase原生自带了对RowKey的很多种查询策略。通过这个过滤器可以在HBase中的数据的多个维度(行,列,数据版本)上进行对数据的筛选操作,也就是说过滤器最...

4987
来自专栏xingoo, 一个梦想做发明家的程序员

Elasticsearch聚合 之 Terms

之前总结过metric聚合的内容,本篇来说一下bucket聚合的知识。Bucket可以理解为一个桶,他会遍历文档中的内容,凡是符合要求的就放入按照要求创建的桶...

3976
来自专栏Spark学习技巧

Hbase源码系列之BufferedMutator的Demo和源码解析

一,基本介绍 BufferedMutator主要用来异步批量的将数据写入一个hbase表,就像Htable一样。通过Connection获取一个实例。Map/r...

1.3K10
来自专栏JackieZheng

RabbitMQ入门-Routing直连模式

Hello World模式,告诉我们如何一对一发送和接收消息; Work模式,告诉我们如何多管齐下高效的消费消息; Publish/Subscribe模式,告...

26610
来自专栏数说工作室

5分钟懂模块 | 【SAS Says·扩展篇】IML:4.模块

【SAS Says · 扩展篇】IML:创建模块 这是“CIR模型利率期限结构拟合”中的一段代码,出自《金融计算与建模》(朱世武,367页): ? 注意用红色框...

2716
来自专栏Python、Flask、Django

Python读写Json数据

1712

扫码关注云+社区

领取腾讯云代金券