Storm实战之TopN

TopN这种统计场景很常见,例如,统计出搜索热度最高的词,点击率最高的广告等,现在有了Hadoop、Storm这些工具之后,很方便地就能得到结果。
这里以Storm为例,简易地实现了TopN单词的统计,由于刚刚入门,代码写得比较简单。
首先,在多台机器上运行多个bolt,每个bolt负责计算一部分word的TopN,最后有一个全局的bolt,合并上一步的结果,最后得出全局的TopN。

点击(此处)折叠或打开

  1. package test.storm.topology;

  2. import test.storm.bolt.WordCounter;
  3. import test.storm.bolt.WordWriter;
  4. import test.storm.spout.WordReader;
  5. import backtype.storm.Config;
  6. import backtype.storm.StormSubmitter;
  7. import backtype.storm.generated.AlreadyAliveException;
  8. import backtype.storm.generated.InvalidTopologyException;
  9. import backtype.storm.topology.TopologyBuilder;
  10. import backtype.storm.tuple.Fields;

  11. public class WordTopN {
  12.     public static void main(String[] args) throws AlreadyAliveException, InvalidTopologyException {
  13.         if (args == null || args.length < 1) {
  14.             System.err.println("Usage: N");
  15.             System.err.println("such as : 10");
  16.             System.exit(-1);
  17.         }

  18.         TopologyBuilder builder = new TopologyBuilder();
  19.         builder.setSpout("wordreader", new WordReader(), 2);
  20.         builder.setBolt("wordcounter", new WordCounter(), 2).fieldsGrouping("wordreader", new Fields("word"));
  21.         builder.setBolt("wordwriter", new WordWriter()).globalGrouping("wordcounter");

  22.         Config conf = new Config();
  23.         conf.put("N", args[0]);

  24.         conf.setDebug(false);
  25.         StormSubmitter.submitTopology("topN", conf, builder.createTopology());

  26.     }
  27. }
这里需要注意的几点是,第一个bolt的分组策略是 fieldsGrouping ,按照字段分组,这一点很重要,它能保证相同的word被分发到同一个bolt上,
像做wordcount、TopN之类的应用就要使用这种分组策略。
最后一个bolt的分组策略是 globalGrouping ,全局分组, tuple 会被分配到一个bolt用来汇总。
为了提高并行度,spout和第一个bolt均设置并行度为2(我这里测试机器性能不是很高)。

点击(此处)折叠或打开

  1. package test.storm.spout;

  2. import java.util.Map;
  3. import java.util.Random;
  4. import java.util.concurrent.atomic.AtomicInteger;

  5. import backtype.storm.spout.SpoutOutputCollector;
  6. import backtype.storm.task.TopologyContext;
  7. import backtype.storm.topology.OutputFieldsDeclarer;
  8. import backtype.storm.topology.base.BaseRichSpout;
  9. import backtype.storm.tuple.Fields;
  10. import backtype.storm.tuple.Values;

  11. public class WordReader extends BaseRichSpout {
  12.     private static final long serialVersionUID = 2197521792014017918L;
  13.     private SpoutOutputCollector collector;
  14.     private static AtomicInteger i = new AtomicInteger();
  15.     private static String[] words = new String[] { \"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\", \"i\", \"j\", \"k\", \"l\", \"m\",
  16.             \"n\", \"o\", \"p\", \"q\", \"r\", \"s\", \"t\", \"u\", \"v\", \"w\", \"x\", \"y\", \"z\" };

  17.     @Override
  18.     public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
  19.         this.collector = collector;
  20.     }

  21.     @Override
  22.     public void nextTuple() {
  23.         if (i.intValue() < 100) {
  24.             Random rand = new Random();
  25.             String word = words[rand.nextInt(words.length)];
  26.             collector.emit(new Values(word));
  27.             i.incrementAndGet();
  28.         }
  29.     }

  30.     @Override
  31.     public void declareOutputFields(OutputFieldsDeclarer declarer) {
  32.         declarer.declare(new Fields("word"));
  33.     }
  34. }
spout的作用是随机发送word,发送100次,由于并行度是2,将产生2个spout实例,所以这里的计数器使用了static的 AtomicInteger来保证线程安全。


点击(此处)折叠或打开

  1. package test.storm.bolt;

  2. import java.util.ArrayList;
  3. import java.util.Collections;
  4. import java.util.Comparator;
  5. import java.util.HashMap;
  6. import java.util.List;
  7. import java.util.Map;
  8. import java.util.Map.Entry;
  9. import java.util.concurrent.ConcurrentHashMap;

  10. import backtype.storm.task.OutputCollector;
  11. import backtype.storm.task.TopologyContext;
  12. import backtype.storm.topology.IRichBolt;
  13. import backtype.storm.topology.OutputFieldsDeclarer;
  14. import backtype.storm.tuple.Fields;
  15. import backtype.storm.tuple.Tuple;
  16. import backtype.storm.tuple.Values;

  17. public class WordCounter implements IRichBolt {
  18.     private static final long serialVersionUID = 5683648523524179434L;
  19.     private static Map<String, Integer> counters = new ConcurrentHashMap<String, Integer>();
  20.     private volatile boolean edit = true;

  21.     @Override
  22.     public void prepare(final Map stormConf, TopologyContext context, final OutputCollector collector) {
  23.         new Thread(new Runnable() {
  24.             @Override
  25.             public void run() {
  26.                 while (true) {
  27.                     //5秒后counter不再变化,可以认为spout已经发送完毕
  28.                     if (!edit) {
  29.                         if (counters.size() > 0) {
  30.                             List<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>();
  31.                             list.addAll(counters.entrySet());
  32.                             Collections.sort(list, new ValueComparator());

  33.                             //向下一个bolt发送前N个word
  34.                             for (int i = 0; i < list.size(); i++) {
  35.                                 if (< Integer.parseInt(stormConf.get("N").toString())) {
  36.                                     collector.emit(new Values(list.get(i).getKey() + ":" + list.get(i).getValue()));
  37.                                 }
  38.                             }
  39.                         }

  40.                         //发送之后,清空counters,以防spout再次发送word过来
  41.                         counters.clear();
  42.                     }

  43.                     edit = false;
  44.                     try {
  45.                         Thread.sleep(5000);
  46.                     } catch (InterruptedException e) {
  47.                         e.printStackTrace();
  48.                     }
  49.                 }
  50.             }
  51.         }).start();
  52.     }

  53.     @Override
  54.     public void execute(Tuple tuple) {
  55.         String str = tuple.getString(0);
  56.         if (counters.containsKey(str)) {
  57.             Integer c = counters.get(str) + 1;
  58.             counters.put(str, c);
  59.         } else {
  60.             counters.put(str, 1);
  61.         }

  62.         edit = true;
  63.     }

  64.     private static class ValueComparator implements Comparator<Map.Entry<String, Integer>> {
  65.         @Override
  66.         public int compare(Entry<String, Integer> entry1, Entry<String, Integer> entry2) {
  67.             return entry2.getValue() - entry1.getValue();
  68.         }
  69.     }

  70.     @Override
  71.     public void declareOutputFields(OutputFieldsDeclarer declarer) {
  72.         declarer.declare(new Fields("word_count"));
  73.     }

  74.     @Override
  75.     public void cleanup() {
  76.     }

  77.     @Override
  78.     public Map<String, Object> getComponentConfiguration() {
  79.         return null;
  80.     }
  81. }
在WordCounter里面有个线程安全的容器 ConcurrentHashMap ,来存储word以及对应的次数。在 prepare 方法里启动一个线程,长期监听edit的状态,监听间隔是5秒,
当edit为false,即execute方法不再执行、容器不再变化,可以认为spout已经发送完毕了,可以开始排序取TopN了。这里使用了一个volatile edit(回忆一下volatile的使用场景:
对变量的修改不依赖变量当前的值,这里设置true or false,显然不相互依赖)。


点击(此处)折叠或打开

  1. package test.storm.bolt;

  2. import java.io.FileWriter;
  3. import java.io.IOException;
  4. import java.util.Map;

  5. import backtype.storm.task.TopologyContext;
  6. import backtype.storm.topology.BasicOutputCollector;
  7. import backtype.storm.topology.OutputFieldsDeclarer;
  8. import backtype.storm.topology.base.BaseBasicBolt;
  9. import backtype.storm.tuple.Tuple;

  10. public class WordWriter extends BaseBasicBolt {
  11.     private static final long serialVersionUID = -6586283337287975719L;
  12.     private FileWriter writer = null;

  13.     public WordWriter() {
  14.     }

  15.     @Override
  16.     public void prepare(Map stormConf, TopologyContext context) {
  17.         try {
  18.             writer = new FileWriter("/data/tianzhen/output/" + this);
  19.         } catch (IOException e) {
  20.             e.printStackTrace();
  21.         }
  22.     }

  23.     @Override
  24.     public void execute(Tuple input, BasicOutputCollector collector) {
  25.         String s = input.getString(0);
  26.         try {
  27.             writer.write(s);
  28.             writer.write("\n");
  29.             writer.flush();
  30.         } catch (IOException e) {
  31.             e.printStackTrace();
  32.         } finally {
  33.             //writer不能close,因为execute需要一直运行
  34.         }
  35.     }

  36.     @Override
  37.     public void declareOutputFields(OutputFieldsDeclarer declarer) {

  38.     }
  39. }
最后一个bolt做全局的汇总,这里我偷了懒,直接将结果写到文件了,省略截取TopN的过程,因为我这里就一个supervisor节点,所以结果是正确的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值