一、前言

  针对大叔据实时处理的入门,除了使用WordCount示例之外,还需要相对更深入点的示例来理解Storm,因此,本篇博文利用Storm实现了频繁项集挖掘的案例,以方便更好的入门Storm。

二、基础知识

  2.1 频繁二项集挖掘

  如顾客去超市购物时,牙膏和牙刷基本上都是摆放在一起,因为购买牙膏时,很有可能会购买牙刷。另外,“啤酒与尿布”的案例则是对订单进行分析挖掘后发现的规律,将啤酒和尿布一起摆放会促进啤酒的销量。

  2.2 算法设计

  本示例中不考虑太复杂的挖掘算法,只考虑将两个商品组合后的挖掘,设计如下

    · 将每笔订单的商品按照两两分组。

    · 将每个分组的频度进行统计(不考虑商品的次序)。

    · 根据频度计算支持度(每个组合出现的频率越高,更有可能是频繁组合)和置信度(商品组合出现的置信程度)。

    · 设置支持度和置信度阈值,过滤不达标的数据。

  2.3 Storm设计思路

    · 使用Redis作为存储订单数据的数据库。

    · 使用Spout从Redis中读取订单数据。

    · 使用Bolt计算分组频度。

    · 使用Bolt计算支持度和置信度。

    · 使用Bolt筛选结果并存储到Redis中。

  2.4 拓扑结构图

  根据程序思路设计如下所示的拓扑结构,其组件在之后进行介绍。

  

三、设计实现

  3.1 实现步骤

  1. 产生订单数据

  通过模拟程序产生订单数据,并存储Redis中,即使用OrderGenerator来生成订单数据并存入Redis中,每个订单有四种不同商品及其数量组成。

  2. 接入订单数据

  通过OrderSpout读取Redis中的订单数据,以供拓扑结构下游的Bolt使用。

  3. 对订单中商品进行分组

  通过SplitBolt对订单中的商品进行分组,两两分组并构建商品对,发送元组至下游Bolt。

  4. 统计商品对总数

  使用PairTotalCountBolt对所有商品对数量进行统计(用于计算支持度),并发送元组至下游Bolt。

  5. 统计商品对及其出现次数

  使用PairCountBolt对商品对出现的次数进行统计,并发送元组至下游Bolt。

  6. 计算商品对支持度

  使用SupportComputeBolt对商品对的支持度进行计算,并发送元组至下游Bolt。

  7. 计算商品对置信度

  使用ConfidenceComputeBolt对商品对的置信度进行计算,并发送元组至下游Bolt。

  8. 过滤符合条件的商品对

  使用FilterBolt对符合条件的商品对进行过滤并存入redis,并发送元组至下游Bolt。

    3.1 源码分析

  下面给出拓扑结构中的各组件的源码并进行分析。

  1. OrderSpout   

  1. package com.hust.grid.leesf.ordertest.spout;
  2.  
  3. import java.util.Map;
  4.  
  5. import org.json.simple.JSONArray;
  6. import org.json.simple.JSONObject;
  7. import org.json.simple.JSONValue;
  8.  
  9. import com.hust.grid.leesf.ordertest.common.ConfKeys;
  10. import com.hust.grid.leesf.ordertest.common.FieldNames;
  11.  
  12. import backtype.storm.spout.SpoutOutputCollector;
  13. import backtype.storm.task.TopologyContext;
  14. import backtype.storm.topology.OutputFieldsDeclarer;
  15. import backtype.storm.topology.base.BaseRichSpout;
  16. import backtype.storm.tuple.Fields;
  17. import backtype.storm.tuple.Values;
  18. import redis.clients.jedis.Jedis;
  19.  
  20. /**
  21. * 数据源,从redis读取订单
  22. *
  23. * @author leesf
  24. *
  25. */
  26. public class OrderSpout extends BaseRichSpout {
  27. private static final long serialVersionUID = 1L;
  28.  
  29. private SpoutOutputCollector collector;
  30. private Jedis jedis;
  31. private String host;
  32. private int port;
  33.  
  34. public void open(@SuppressWarnings("rawtypes") Map conf, TopologyContext context, SpoutOutputCollector collector) {
  35. this.collector = collector;
  36. this.host = conf.get(ConfKeys.REDIS_HOST).toString();
  37. this.port = Integer.parseInt(conf.get(ConfKeys.REDIS_PORT).toString());
  38. connectToRedis();
  39. }
  40.  
  41. private void connectToRedis() {
  42. jedis = new Jedis(host, port);
  43. jedis.connect();
  44. }
  45.  
  46. public void nextTuple() {
  47. String content = jedis.rpop("orders"); // 获取一条订单数据
  48.  
  49. if (null == content || "nil".equals(content)) { // 若无,则等待300ms
  50. try {
  51. Thread.sleep(300);
  52. } catch (InterruptedException e) {
  53. e.printStackTrace();
  54. }
  55. } else { // 对订单数据进行转化
  56. JSONObject object = (JSONObject) JSONValue.parse(content);
  57. String id = object.get(FieldNames.ID).toString(); // 获取ID
  58. JSONArray items = (JSONArray) object.get(FieldNames.ITEMS); // 获取订单中的商品
  59.  
  60. for (Object obj : items) { // 遍历订单中的商品
  61. JSONObject item = (JSONObject) obj;
  62. String name = item.get(FieldNames.NAME).toString(); // 商品名称
  63. int count = Integer.parseInt(item.get(FieldNames.COUNT).toString()); // 商品数量
  64. collector.emit(new Values(id, name, count)); // 发射订单号、商品名称、商品数量
  65.  
  66. if (jedis.hexists("itemCounts", name)) { // redis中存在name字段
  67. jedis.hincrBy("itemCounts", name, 1); // 商品对应数量(订单中多个商品当作1个)增加1
  68. } else { // redis中不存在name字段
  69. jedis.hset("itemCounts", name, "1"); // 将name字段的值(商品数量)设置为1
  70. }
  71. }
  72. }
  73. }
  74.  
  75. public void declareOutputFields(OutputFieldsDeclarer declarer) {
  76. // 声明发射元组字段
  77. declarer.declare(new Fields(FieldNames.ID, FieldNames.NAME, FieldNames.COUNT));
  78. }
  79. }

OrderSpout

  说明:OrderSpout会从redis中读取订单数据,并遍历订单中每个商品并发射,同时会统计商品数据并存入redis。

  2. CommandSpout

  1. package com.hust.grid.leesf.ordertest.spout;
  2.  
  3. import java.util.Map;
  4.  
  5. import com.hust.grid.leesf.ordertest.common.FieldNames;
  6.  
  7. import backtype.storm.spout.SpoutOutputCollector;
  8. import backtype.storm.task.TopologyContext;
  9. import backtype.storm.topology.OutputFieldsDeclarer;
  10. import backtype.storm.topology.base.BaseRichSpout;
  11. import backtype.storm.tuple.Fields;
  12. import backtype.storm.tuple.Values;
  13.  
  14. /**
  15. * 统计支持度和置信度
  16. *
  17. * @author leesf
  18. */
  19. public class CommandSpout extends BaseRichSpout {
  20. private static final long serialVersionUID = 1L;
  21.  
  22. private SpoutOutputCollector collector;
  23.  
  24. public void open(@SuppressWarnings("rawtypes") Map conf, TopologyContext context, SpoutOutputCollector collector) {
  25. this.collector = collector;
  26. }
  27.  
  28. public void nextTuple() {
  29. // 休眠5S后发射“statistics”
  30. try {
  31. Thread.sleep(5000);
  32. } catch (InterruptedException e) {
  33. e.printStackTrace();
  34. }
  35.  
  36. collector.emit(new Values("statistics"));
  37. }
  38.  
  39. public void declareOutputFields(OutputFieldsDeclarer declarer) {
  40. // 声明元组字段
  41. declarer.declare(new Fields(FieldNames.COMMAND));
  42. }
  43. }

CommandSpout

  说明:下游Bolt根据其发射的元组信息来统计支持度和置信度,其每5秒发射一次统计信号。

  3. SplitBolt 

  1. package com.hust.grid.leesf.ordertest.bolt;
  2.  
  3. import java.util.ArrayList;
  4. import java.util.HashMap;
  5. import java.util.List;
  6. import java.util.Map;
  7.  
  8. import com.hust.grid.leesf.ordertest.common.FieldNames;
  9.  
  10. import backtype.storm.task.OutputCollector;
  11. import backtype.storm.task.TopologyContext;
  12. import backtype.storm.topology.OutputFieldsDeclarer;
  13. import backtype.storm.topology.base.BaseRichBolt;
  14. import backtype.storm.tuple.Fields;
  15. import backtype.storm.tuple.Tuple;
  16. import backtype.storm.tuple.Values;
  17.  
  18. /**
  19. * 对订单中的商品进行两两组合并发送
  20. *
  21. * @author leesf
  22. *
  23. */
  24. public class SplitBolt extends BaseRichBolt {
  25. private static final long serialVersionUID = 1L;
  26.  
  27. private OutputCollector collector;
  28. private Map<String, List<String>> orderItems; // 存储订单及其商品
  29.  
  30. public void prepare(@SuppressWarnings("rawtypes") Map conf, TopologyContext context, OutputCollector collector) {
  31. this.collector = collector;
  32. orderItems = new HashMap<String, List<String>>();
  33. }
  34.  
  35. public void execute(Tuple tuple) {
  36. // 获取订单号和商品名称
  37. String id = tuple.getStringByField(FieldNames.ID);
  38. String newItem = tuple.getStringByField(FieldNames.NAME);
  39.  
  40. if (!orderItems.containsKey(id)) { // 不包含该订单
  41. // 新生商品链表
  42. ArrayList<String> items = new ArrayList<String>();
  43. // 添加商品
  44. items.add(newItem);
  45.  
  46. orderItems.put(id, items);
  47.  
  48. return;
  49. }
  50. // 包含订单,取出订单中包含的商品
  51. List<String> items = orderItems.get(id);
  52. for (String existItem : items) { // 遍历商品
  53. // 将元组中提取的商品与订单中已存在的商品组合后发射
  54. collector.emit(createPair(newItem, existItem));
  55. }
  56. // 添加新的商品
  57. items.add(newItem);
  58. }
  59.  
  60. private Values createPair(String item1, String item2) { // 按照指定顺序生成商品对
  61. if (item1.compareTo(item2) > 0) {
  62. return new Values(item1, item2);
  63. }
  64.  
  65. return new Values(item2, item1);
  66. }
  67.  
  68. public void declareOutputFields(OutputFieldsDeclarer declarer) {
  69. // 声明元组字段
  70. declarer.declare(new Fields(FieldNames.ITEM1, FieldNames.ITEM2));
  71. }
  72. }

SplitBolt

  说明:其将每个订单的两两商品进行组合,然后发射。

  4. PairTotalCountBolt 

  1. package com.hust.grid.leesf.ordertest.bolt;
  2.  
  3. import java.util.Map;
  4.  
  5. import com.hust.grid.leesf.ordertest.common.FieldNames;
  6.  
  7. import backtype.storm.task.OutputCollector;
  8. import backtype.storm.task.TopologyContext;
  9. import backtype.storm.topology.OutputFieldsDeclarer;
  10. import backtype.storm.topology.base.BaseRichBolt;
  11. import backtype.storm.tuple.Fields;
  12. import backtype.storm.tuple.Tuple;
  13. import backtype.storm.tuple.Values;
  14.  
  15. /**
  16. * 计算商品对总数
  17. *
  18. * @author leesf
  19. *
  20. */
  21. public class PairTotalCountBolt extends BaseRichBolt {
  22. private static final long serialVersionUID = 1L;
  23.  
  24. private OutputCollector collector;
  25. private int totalCount; // 商品对总数
  26.  
  27. public void prepare(@SuppressWarnings("rawtypes") Map conf, TopologyContext context, OutputCollector collector) {
  28. this.collector = collector;
  29. totalCount = 0;
  30. }
  31.  
  32. public void execute(Tuple tuple) {
  33. totalCount++; // 每收到一个元组,便增加商品对总数
  34. collector.emit(new Values(totalCount)); // 发射商品对总数
  35. }
  36.  
  37. public void declareOutputFields(OutputFieldsDeclarer declarer) {
  38. // 声明元组字段
  39. declarer.declare(new Fields(FieldNames.TOTAL_COUNT));
  40. }
  41. }

PairTotalCountBolt

  说明:其用于统计所有商品对的数量(用于后面支持度的计算)。

  5. PairCountBolt

  1. package com.hust.grid.leesf.ordertest.bolt;
  2.  
  3. import java.util.HashMap;
  4. import java.util.Map;
  5.  
  6. import com.hust.grid.leesf.ordertest.common.FieldNames;
  7. import com.hust.grid.leesf.ordertest.common.ItemPair;
  8.  
  9. import backtype.storm.task.OutputCollector;
  10. import backtype.storm.task.TopologyContext;
  11. import backtype.storm.topology.OutputFieldsDeclarer;
  12. import backtype.storm.topology.base.BaseRichBolt;
  13. import backtype.storm.tuple.Fields;
  14. import backtype.storm.tuple.Tuple;
  15. import backtype.storm.tuple.Values;
  16.  
  17. /**
  18. * 计算商品对出现的次数
  19. *
  20. * @author leesf
  21. *
  22. */
  23. public class PairCountBolt extends BaseRichBolt {
  24. private static final long serialVersionUID = 1L;
  25.  
  26. private OutputCollector collector;
  27. private Map<ItemPair, Integer> pairCounts; // 存储商品对及其出现的次数
  28.  
  29. public void prepare(@SuppressWarnings("rawtypes") Map conf, TopologyContext context, OutputCollector collector) {
  30. this.collector = collector;
  31. this.pairCounts = new HashMap<ItemPair, Integer>();
  32. }
  33.  
  34. public void execute(Tuple tuple) {
  35. String item1 = tuple.getStringByField(FieldNames.ITEM1);
  36. String item2 = tuple.getStringByField(FieldNames.ITEM2);
  37.  
  38. ItemPair itemPair = new ItemPair(item1, item2);
  39. int pairCount = 0;
  40.  
  41. if (pairCounts.containsKey(itemPair)) { // 包含商品对
  42. // 取出商品对出现的次数
  43. pairCount = pairCounts.get(itemPair);
  44. }
  45. // 更新出现次数
  46. pairCount++;
  47.  
  48. pairCounts.put(itemPair, pairCount);
  49.  
  50. collector.emit(new Values(item1, item2, pairCount));
  51. }
  52.  
  53. public void declareOutputFields(OutputFieldsDeclarer declarer) {
  54. // 声明元组字段
  55. declarer.declare(new Fields(FieldNames.ITEM1, FieldNames.ITEM2, FieldNames.PAIR_COUNT));
  56. }
  57. }

PairCountBolt

  说明:其用于统计每个商品对出现的次数,然后发射。

  6. SupportComputeBolt  

  1. package com.hust.grid.leesf.ordertest.bolt;
  2.  
  3. import java.util.HashMap;
  4. import java.util.Map;
  5.  
  6. import com.hust.grid.leesf.ordertest.common.FieldNames;
  7. import com.hust.grid.leesf.ordertest.common.ItemPair;
  8.  
  9. import backtype.storm.task.OutputCollector;
  10. import backtype.storm.task.TopologyContext;
  11. import backtype.storm.topology.OutputFieldsDeclarer;
  12. import backtype.storm.topology.base.BaseRichBolt;
  13. import backtype.storm.tuple.Fields;
  14. import backtype.storm.tuple.Tuple;
  15. import backtype.storm.tuple.Values;
  16.  
  17. /**
  18. * 计算商品对的支持度
  19. *
  20. * @author leesf
  21. *
  22. */
  23. public class SupportComputeBolt extends BaseRichBolt {
  24. private static final long serialVersionUID = 1L;
  25.  
  26. private OutputCollector collector;
  27. private Map<ItemPair, Integer> pairCounts; // 存储商品对及其出现的次数
  28. private int pairTotalCount; // 商品对总数
  29.  
  30. public void prepare(@SuppressWarnings("rawtypes") Map conf, TopologyContext context, OutputCollector collector) {
  31. this.collector = collector;
  32. pairCounts = new HashMap<ItemPair, Integer>();
  33. pairTotalCount = 0;
  34. }
  35.  
  36. /**
  37. * 由于SupportComputeBolt订阅了多个流,其需要根据不同的字段做出不同的行为
  38. */
  39. public void execute(Tuple tuple) {
  40. if (tuple.getFields().get(0).equals(FieldNames.TOTAL_COUNT)) { // 对应PairTotalCountBolt
  41. // 取出商品对总数量
  42. pairTotalCount = tuple.getIntegerByField(FieldNames.TOTAL_COUNT);
  43. } else if (tuple.getFields().size() == 3) { // 对应PairCountBolt
  44. // 取出商品及其商品对出现的次数
  45. String item1 = tuple.getStringByField(FieldNames.ITEM1);
  46. String item2 = tuple.getStringByField(FieldNames.ITEM2);
  47. int pairCount = tuple.getIntegerByField(FieldNames.PAIR_COUNT);
  48. // 存储商品对及其次数
  49. pairCounts.put(new ItemPair(item1, item2), pairCount);
  50. } else if (tuple.getFields().get(0).equals(FieldNames.COMMAND)) { // 对应CommandSpout
  51. for (ItemPair itemPair : pairCounts.keySet()) { // 遍历商品对
  52. // 计算商品支持度,使用商品对出现的次数除以商品对总数量
  53. double itemSupport = (double) (pairCounts.get(itemPair).intValue()) / pairTotalCount;
  54.  
  55. collector.emit(new Values(itemPair.getItem1(), itemPair.getItem2(), itemSupport));
  56. }
  57. }
  58. }
  59.  
  60. public void declareOutputFields(OutputFieldsDeclarer declarer) {
  61. // 定义元组字段
  62. declarer.declare(new Fields(FieldNames.ITEM1, FieldNames.ITEM2, FieldNames.SUPPORT));
  63. }
  64.  
  65. }

SupportComputeBolt

  说明:计算每个商品对的支持度,并且发射支持度。

  7. ConfidenceComputeBolt 

  1. package com.hust.grid.leesf.ordertest.bolt;
  2.  
  3. import java.util.HashMap;
  4. import java.util.Map;
  5.  
  6. import com.hust.grid.leesf.ordertest.common.ConfKeys;
  7. import com.hust.grid.leesf.ordertest.common.FieldNames;
  8. import com.hust.grid.leesf.ordertest.common.ItemPair;
  9.  
  10. import backtype.storm.task.OutputCollector;
  11. import backtype.storm.task.TopologyContext;
  12. import backtype.storm.topology.OutputFieldsDeclarer;
  13. import backtype.storm.topology.base.BaseRichBolt;
  14. import backtype.storm.tuple.Fields;
  15. import backtype.storm.tuple.Tuple;
  16. import backtype.storm.tuple.Values;
  17. import redis.clients.jedis.Jedis;
  18.  
  19. /**
  20. * 计算商品对的置信度
  21. *
  22. * @author leesf
  23. */
  24. public class ConfidenceComputeBolt extends BaseRichBolt {
  25. private static final long serialVersionUID = 1L;
  26.  
  27. private OutputCollector collector;
  28. private Map<ItemPair, Integer> pairCounts; // 存储商品对及其出现的次数
  29.  
  30. private String host;
  31. private int port;
  32. private Jedis jedis;
  33.  
  34. public void prepare(@SuppressWarnings("rawtypes") Map conf, TopologyContext context, OutputCollector collector) {
  35. this.collector = collector;
  36. this.host = conf.get(ConfKeys.REDIS_HOST).toString();
  37. this.port = Integer.parseInt(conf.get(ConfKeys.REDIS_PORT).toString());
  38. pairCounts = new HashMap<ItemPair, Integer>();
  39. connectToRedis();
  40. }
  41.  
  42. private void connectToRedis() {
  43. jedis = new Jedis(host, port);
  44. jedis.connect();
  45. }
  46.  
  47. /**
  48. * 由于ConfidenceComputeBolt订阅了多个流,其需要根据元组不同的字段做出不同的行为
  49. */
  50. public void execute(Tuple tuple) {
  51. if (tuple.getFields().size() == 3) { // 对应PairCountBolt
  52. // 取出商品对及其出现次数
  53. String item1 = tuple.getStringByField(FieldNames.ITEM1);
  54. String item2 = tuple.getStringByField(FieldNames.ITEM2);
  55. int pairCount = tuple.getIntegerByField(FieldNames.PAIR_COUNT);
  56.  
  57. pairCounts.put(new ItemPair(item1, item2), pairCount);
  58. } else if (tuple.getFields().get(0).equals(FieldNames.COMMAND)) { // 对应CommandSpout,需要进行统计
  59. for (ItemPair itemPair : pairCounts.keySet()) { // 遍历商品对
  60. // 从redis中取出商品对中商品出现的次数
  61. double item1Count = Integer.parseInt(jedis.hget("itemCounts", itemPair.getItem1()));
  62. double item2Count = Integer.parseInt(jedis.hget("itemCounts", itemPair.getItem2()));
  63. double itemConfidence = pairCounts.get(itemPair).intValue();
  64.  
  65. // 计算商品对置信度
  66. if (item1Count < item2Count) {
  67. itemConfidence /= item1Count;
  68. } else {
  69. itemConfidence /= item2Count;
  70. }
  71.  
  72. collector.emit(new Values(itemPair.getItem1(), itemPair.getItem2(), itemConfidence));
  73. }
  74. }
  75. }
  76.  
  77. public void declareOutputFields(OutputFieldsDeclarer declarer) {
  78. // 声明元组字段
  79. declarer.declare(new Fields(FieldNames.ITEM1, FieldNames.ITEM2, FieldNames.CONFIDENCE));
  80. }
  81. }

  说明:计算商品对的置信度,并且发射置信度。

  8. FilterBolt

  1. package com.hust.grid.leesf.ordertest.bolt;
  2.  
  3. import java.util.Map;
  4.  
  5. import org.json.simple.JSONObject;
  6.  
  7. import com.hust.grid.leesf.ordertest.common.ConfKeys;
  8. import com.hust.grid.leesf.ordertest.common.FieldNames;
  9. import com.hust.grid.leesf.ordertest.common.ItemPair;
  10.  
  11. import backtype.storm.task.OutputCollector;
  12. import backtype.storm.task.TopologyContext;
  13. import backtype.storm.topology.OutputFieldsDeclarer;
  14. import backtype.storm.topology.base.BaseRichBolt;
  15. import backtype.storm.tuple.Fields;
  16. import backtype.storm.tuple.Tuple;
  17. import backtype.storm.tuple.Values;
  18. import redis.clients.jedis.Jedis;
  19.  
  20. /**
  21. * 过滤符合条件的商品对并存入redis
  22. *
  23. * @author leesf
  24. *
  25. */
  26. public class FilterBolt extends BaseRichBolt {
  27. private static final long serialVersionUID = 1L;
  28.  
  29. // 商品对的支持度和置信度
  30. private static final double SUPPORT_THRESHOLD = 0.01;
  31. private static final double CONFIDENCE_THRESHOLD = 0.01;
  32.  
  33. private OutputCollector collector;
  34.  
  35. private Jedis jedis;
  36. private String host;
  37. private int port;
  38.  
  39. public void prepare(@SuppressWarnings("rawtypes") Map conf, TopologyContext context, OutputCollector collector) {
  40. this.collector = collector;
  41. this.host = conf.get(ConfKeys.REDIS_HOST).toString();
  42. this.port = Integer.parseInt(conf.get(ConfKeys.REDIS_PORT).toString());
  43. connectToRedis();
  44. }
  45.  
  46. private void connectToRedis() {
  47. jedis = new Jedis(host, port);
  48. jedis.connect();
  49. }
  50.  
  51. @SuppressWarnings("unchecked")
  52. public void execute(Tuple tuple) {
  53. // 取出商品并构造商品对
  54. String item1 = tuple.getStringByField(FieldNames.ITEM1);
  55. String item2 = tuple.getStringByField(FieldNames.ITEM2);
  56. ItemPair itemPair = new ItemPair(item1, item2);
  57. String pairString = itemPair.toString();
  58.  
  59. double support = 0;
  60. double confidence = 0;
  61.  
  62. if (tuple.getFields().get(2).equals(FieldNames.SUPPORT)) { // 对应SupportComputeBolt
  63. // 获取支持度并存入redis
  64. support = tuple.getDoubleByField(FieldNames.SUPPORT);
  65. jedis.hset("supports", pairString, String.valueOf(support));
  66. } else if (tuple.getFields().get(2).equals(FieldNames.CONFIDENCE)) { // 对应ConfidenceComputeBolt
  67. // 获取置信度并存入redis
  68. confidence = tuple.getDoubleByField(FieldNames.CONFIDENCE);
  69. jedis.hset("confidences", pairString, String.valueOf(confidence));
  70. }
  71.  
  72. if (!jedis.hexists("supports", pairString) || !jedis.hexists("confidences", pairString)) { // 商品对的支持度和置信度还未计算完成,返回
  73. return;
  74. }
  75. // 商品对的支持度和置信度已经计算完成
  76. support = Double.parseDouble(jedis.hget("supports", pairString));
  77. confidence = Double.parseDouble(jedis.hget("confidences", pairString));
  78.  
  79. if (support >= SUPPORT_THRESHOLD && confidence >= CONFIDENCE_THRESHOLD) { // 支持度和置信度超过阈值
  80. // 将该商品对信息存入redis中
  81. JSONObject pairValue = new JSONObject();
  82. pairValue.put(FieldNames.SUPPORT, support);
  83. pairValue.put(FieldNames.CONFIDENCE, confidence);
  84.  
  85. jedis.hset("recommendedPairs", pairString, pairValue.toJSONString());
  86.  
  87. collector.emit(new Values(item1, item2, support, confidence));
  88. } else { // 不高于阈值,则从redis中删除
  89. jedis.hdel("recommendedPairs", pairString);
  90. }
  91. }
  92.  
  93. public void declareOutputFields(OutputFieldsDeclarer declarer) {
  94. // 声明元组字段
  95. declarer.declare(new Fields(FieldNames.ITEM1, FieldNames.ITEM2, FieldNames.SUPPORT, FieldNames.CONFIDENCE));
  96. }
  97. }

FilterBolt

  说明:判断支持度和置信度是否超过了阈值,若超过则需要存入redis,否则,从redis中删除。

四、程序运行

  4.1. 环境依赖

  打开redis服务器、客户端(方便观看结果)和zookeeper。

  4.2. 写入订单数据

  运行OrderGenerator,生成并写入订单数据,通过redis查看,结果如下

  

  表示已经成功写入了订单数据。

  4.3. 运行任务拓扑

  运行OrderTopology,其会根据订单中的商品数据,生成并写入推荐商品对,通过redis查看,结果如下

  

  可以看到运行完成后,已经成功生成了推荐商品方案。

五、总结

  通过本篇Storm案例的学习,对于Storm的编程有了更深入的认识,同时,本项目的源代码已经上传至github,欢迎star,谢谢各位园友的观看~  

参考链接:http://www.jikexueyuan.com/course/1437.html

【Storm】Storm实战之频繁二项集挖掘(附源码)的更多相关文章

  1. 【Storm】Storm实战之频繁二项集挖掘

    一.前言 针对大叔据实时处理的入门,除了使用WordCount示例之外,还需要相对更深入点的示例来理解Storm,因此,本篇博文利用Storm实现了频繁项集挖掘的案例,以方便更好的入门Storm. 二 ...

  2. arcgis api 4.x for js 自定义 Draw 绘制手绘面以及手绘线,只针对二维视图(附源码下载)

    前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 4.x for js:esri 官网 api,里面详细的介绍 arcgis api 4.x 各个类 ...

  3. Android 音视频深入 二十 FFmpeg视频压缩(附源码下载)

    项目源码https://github.com/979451341/FFmpegCompress 这个视频压缩是通过类似在mac终端上输入FFmpeg命令来完成,意思是我们需要在Android上达到能够 ...

  4. 海量数据挖掘MMDS week2: 频繁项集挖掘 Apriori算法的改进:基于hash的方法

    http://blog.csdn.net/pipisorry/article/details/48901217 海量数据挖掘Mining Massive Datasets(MMDs) -Jure Le ...

  5. 海量数据挖掘MMDS week2: Association Rules关联规则与频繁项集挖掘

    http://blog.csdn.net/pipisorry/article/details/48894977 海量数据挖掘Mining Massive Datasets(MMDs) -Jure Le ...

  6. 海量数据挖掘MMDS week2: 频繁项集挖掘 Apriori算法的改进:非hash方法

    http://blog.csdn.net/pipisorry/article/details/48914067 海量数据挖掘Mining Massive Datasets(MMDs) -Jure Le ...

  7. 频繁项集挖掘之Aprior和FPGrowth算法

    频繁项集挖掘的应用多出现于购物篮分析,现介绍两种频繁项集的挖掘算法Aprior和FPGrowth,用以发现购物篮中出现频率较高的购物组合. 基础知识 项:“属性-值”对.比如啤酒2罐.  项集:项的集 ...

  8. 频繁项集挖掘之apriori和fp-growth

    Apriori和fp-growth是频繁项集(frequent itemset mining)挖掘中的两个经典算法,虽然都是十几年前的,但是理解这两个算法对数据挖掘和学习算法都有很大好处.在理解这两个 ...

  9. MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)

    前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...

随机推荐

  1. 初识(试)LoadRunner

    一.安装和破解 1.傻瓜式安装.[注意:最好不要默认路径安装,因为64位的win7系统安装LR11时,会默认安装到“Program files (x86)”的目录中,该目录名称有空格,会导致录制“We ...

  2. day3逻辑运算符

    今天主讲逻辑运算符 以下是老师的大纲: # + - * / % ** // # == != <> # count = count + 1      count += 1 # count = ...

  3. group by 和where 条件后面不能用刚设置的别名。

    select count(*),c_xy_bj a from z_user group by c_xy_bj     这个group by后面不能使用c_xy_bj 字段的别名a,只有外面再嵌套sel ...

  4. es6的分析总结

    1,var let const对比 1,箭头函数的总结 /** * 1,箭头函数没有this,箭头函数this没有被箭头的函数,所以不能使用call,apply,bind改变this指向 * 2,箭头 ...

  5. XX-Net的局域网共享代理方法

    局域网内有一台电脑安装了XX-net,将其共享给局域网内其他电脑,让其他电脑经这台电脑的XX-net配置访问网站. 一.电脑端操作1.在XXnet/data/gae_proxy目录下修改config. ...

  6. anaconda源配置

    1. 生成配置文件 第一次运行 conda config命令时,将会在用户的home目录创建该文件..condarc配置文件,是一种可选的(optional)运行期配置文件,其默认情况下是不存在的. ...

  7. vue-cli之打包多入口配置

    在使用vue-cli初始化vue项目时,默认打包为单入口,有时候一个项目可能会有不同入口,在这种情况下,就需要我们稍微修改下webpack配置文件了,具体步骤如下: 1.修改webpack.base. ...

  8. MUI + Spring MVC 实现多图片上传

    后台代码,主要是SpringMVC 接收多文件上传,不是用的MutilFiles 方式,主要是因为MUI 5+ 不支持文件上传的Key是同一个名字 private String saveFile(Mu ...

  9. 安装淘宝镜像cnpm时出现问题及解决方案

    问题: 解决方案: 安装完成:

  10. VMware5.5-高可用性和动态资源调度(DRS)

    高可用性 故障分类:ESX主机---虚拟机(主机通过vmtools监控)---应用程序(基本不用6.0新增了这一功能) 高可用的信号检测目前可分为两种 一.网络信号 二.存储信号 新建群集 上图的自定 ...