【Storm】Spout的storm-starter及Grouping策略、并发度讲解、网站浏览量和用户数统计

maven先安装好。

以下讲storm-starter的使用。

1、从github下载官方的storm-starter例子包,是maven工程,

      地址 https://github.com/nathanmarz/storm-starter

2、把文件解压复制到workspace目录下,用cmd命令行,在该文件目录下运行mvn eclipse:eclipse,生成eclipse所用的文件,使得maven工程变成eclipse可用的工程。

3、导入到eclipse。新建源码文件夹lesson。把上一节storm入门案例工程的lesson包,整个复制到storm-starter-master的lesson源码文件夹下。

4、选中项目右键,Run as maven package,用maven打包。在target文件夹下有2个jar包。

storm-starter-0.0.1-SNAPSHOT.jar是不含依赖,只含有工程代码,较小。

storm-starter-0.0.1-SNAPSHOT-jar-with-dependencies.jar 包含依赖,较大,通常需要由依赖的包。

5、官方提供的例子

 

分组策略(Stream Grouping)

stream grouping 用来定义一个 stream 应该如何分配给 Bolts 上面的多个Tasks,也就是分配给Bolts上面的多个Executors(多线程,并发度)。

1、Storm 里面有 6种类型的 stream grouping:

注:1)、2)、5)最常用,其他基本不用。

单线程等于All Grouping

1)Shuffle Grouping:随机分组,随机派发 stream 里面的 tuple,保证每个 bolt 接收到的 tuple 数目大致相同。通过轮询实现,保证平均分配。

2)Fields Grouping:按字段分组,比如按 userid 来分组,具有同样 userid 的 tuple 会被分到相同的 bolts,而不同的 userid 则会被分配到不同的 bolts。

3)All Grouping:广播发送,对于每一个 tuple,所有的 bolts 都会收到。

4)Global Grouping:全局分组,这个 tuple 被分配到 storm 中的一个 bolt 的其中一个 task。再具体一点就是分配给 id 值最低的那个 task。

5)None Grouping:不分组,这个分组的意思是说 stream 不关心到底谁会收到它的 tuple。目前这种分组和 Shuffle Grouping 是一样的效果,但是多线程下不平均分配。

6)Direct Grouping:直接分组,这是一种比较特别的分组方法,用这种分组意味着消息的发送者指定由消息接收者的哪个 task 处理这个消息。只有被声明为 Direct Stream 的消息流可以声明这种分组方法。而且这种消息 tuple 必须使用 emitDirect 方法来发射。消息处理者可以通过 TopologyContext 来获取处理它的消息的 task 的 id (OutputCollector.emit 方法也会返回 task 的 id)。

7)Local or Shuffle Grouping:如果目标 bolt 有一个或者多个 task 在同一个工作进程中,tuple 将会被随机发送给这些 tasks。否则,和普通的 Shuffle Grouping 行为一致。

2、测试

1)Shuffle Grouping 轮循的方式。

在lesson的Main.java改代码。把bolt并发数改为2,也就是bolt会有2个线程。

		// bolt的方法有3个参数,
		// bolt的id(String类型),实例,并发数。大量数据场景并行数设置大一些
		// bolt的数据来源于spout,名称要和上文setSpout的id一致,否则不能获取到数据
		// shuffle发射规则,后续详讲
		topoBuilder.setBolt("bolt", new MyBolt(), 2).shuffleGrouping("spout");

lesson的MyBolt.java改代码。打印的内容增加当前的线程名。

			if (null != valStr) {
				num++;
				System.out.println(Thread.currentThread().getName() + ", lines : " + num + ", sessionId: " + valStr.split("\t")[1]);
			}

控制台打印的内容,显示有2个bolt线程,每个线程接收到的个数相同。每个线程打印出来的总行数相加,才等于track.log文件的总行数。

Thread-22-bolt, lines : 1, sessionId: 5CFBA5BD76BACF436ACA9DCC8
Thread-50-bolt, lines : 1, sessionId: 5D16C7A886E2P2AE3EA29FC3E
Thread-22-bolt, lines : 2, sessionId: 5D16C7A886E2P2AE3EA29FC3E
Thread-50-bolt, lines : 2, sessionId: 5C3FBA728FD7D264B80769B23
Thread-22-bolt, lines : 3, sessionId: 5D16C1F5191CF9371Y32B58CF
Thread-50-bolt, lines : 3, sessionId: 5C16BC4MB91B85661FE22F413
.
.
.
Thread-22-bolt, lines : 23, sessionId: 5GFBAT3D3100A7A7255027A70
Thread-50-bolt, lines : 23, sessionId: 5D16C1EB1C7A751AE03201C3F
Thread-50-bolt, lines : 24, sessionId: 5B16C0F7215109AG43528BA2D
Thread-22-bolt, lines : 24, sessionId: 5N16C2FE51E5619C2A1244215
Thread-22-bolt, lines : 25, sessionId: 5X16BCA8823AC4BD9CD196A5D
Thread-50-bolt, lines : 25, sessionId: 5C3FBA728FD7D264B80769B23

2)spout的并发数为2,bolt并发数为1.

topoBuilder.setSpout("spout", new MySpout(), 2);
topoBuilder.setBolt("bolt", new MyBolt(), 1).shuffleGrouping("spout");

控制台打印的内容显示,只有1个bolt线程,符合预期。但是bolt读取到的总行数是track.log文件行数的2倍。这是因为spout有2个线程,所以track.log被读取了2次。说明把文件当做spout数据来源是,shout的线程数只能是1.

Thread-23-bolt, lines : 1, sessionId: 5CFBA5BD76BACF436ACA9DCC8
Thread-23-bolt, lines : 2, sessionId: 5CFBA5BD76BACF436ACA9DCC8
Thread-23-bolt, lines : 3, sessionId: 5D16C7A886E2P2AE3EA29FC3E
Thread-23-bolt, lines : 4, sessionId: 5D16C7A886E2P2AE3EA29FC3E
Thread-23-bolt, lines : 5, sessionId: 5D16C7A886E2P2AE3EA29FC3E
.
.
.
Thread-23-bolt, lines : 96, sessionId: 5N16C2FE51E5619C2A1244215
Thread-23-bolt, lines : 97, sessionId: 5X16BCA8823AC4BD9CD196A5D
Thread-23-bolt, lines : 98, sessionId: 5X16BCA8823AC4BD9CD196A5D
Thread-23-bolt, lines : 99, sessionId: 5C3FBA728FD7D264B80769B23
Thread-23-bolt, lines : 100, sessionId: 5C3FBA728FD7D264B80769B23

3)Non Grouping

spout并发数为1,bolt并发数为2.

topoBuilder.setSpout("spout", new MySpout(), 1);
topoBuilder.setBolt("bolt", new MyBolt(), 2).noneGrouping("spout");

控制台打印内容如下,bolt有2个线程,线程名为Thread-23-bolt的计数器是20,线程名为Thread-50-bolt的计数器是30。说明在non grouping模式下,是不平均分配的。

Thread-23-bolt, lines : 2, sessionId: 5D16C7A886E2P2AE3EA29FC3E
Thread-50-bolt, lines : 1, sessionId: 5D16C7A886E2P2AE3EA29FC3E
Thread-23-bolt, lines : 3, sessionId: 5C3FBA728FD7D264B80769B23
Thread-23-bolt, lines : 4, sessionId: 5D16C1F5191CF9371Y32B58CF
Thread-50-bolt, lines : 2, sessionId: 5C16BC4MB91B85661FE22F413
.
.
.
Thread-23-bolt, lines : 17, sessionId: 5D16C1EB1C7A751AE03201C3F
Thread-23-bolt, lines : 18, sessionId: 5B16C0F7215109AG43528BA2D
Thread-50-bolt, lines : 30, sessionId: 5N16C2FE51E5619C2A1244215
Thread-23-bolt, lines : 19, sessionId: 5X16BCA8823AC4BD9CD196A5D
Thread-23-bolt, lines : 20, sessionId: 5C3FBA728FD7D264B80769B23

4)Fields Grouping 策略,

  回顾 fields grouping 策略的作用:

(1)过滤,从源端(spout或上一级bolt)多输出Fields中选择某些field;

(2)相同的tuple会分发给同一个Executor或task处理。

典型场景:去重操作,join(企业用得不多,需要用到2个数据源,且2个数据源要同时,不能相差太久)

1个spout,2个bolt。

topoBuilder.setSpout("spout", new MySpout(), 1);
// Field grouping有2个参数。第一个是spout名称,第二个是field名称
topoBuilder.setBolt("bolt", new MyBolt(), 2).fieldsGrouping("spout", new Fields("log"));
		

效果和Non Grouping差不多。

Thread-50-bolt, lines : 1, sessionId: 5CFBA5BD76BACF436ACA9DCC8
Thread-50-bolt, lines : 2, sessionId: 5D16C7A886E2P2AE3EA29FC3E
Thread-50-bolt, lines : 3, sessionId: 5D16C7A886E2P2AE3EA29FC3E
Thread-50-bolt, lines : 4, sessionId: 5C3FBA728FD7D264B80769B23
Thread-50-bolt, lines : 5, sessionId: 5D16C1F5191CF9371Y32B58CF
.
.
.
Thread-50-bolt, lines : 26, sessionId: 5B16C0F7215109AG43528BA2D
Thread-21-bolt, lines : 22, sessionId: 5N16C2FE51E5619C2A1244215
Thread-21-bolt, lines : 23, sessionId: 5X16BCA8823AC4BD9CD196A5D
Thread-50-bolt, lines : 27, sessionId: 5C3FBA728FD7D264B80769B23

5)All grouping策略模式

1个spout,2个bolt。

topoBuilder.setSpout("spout", new MySpout(), 1);
// all grouping 广播方式
topoBuilder.setBolt("bolt", new MyBolt(), 2).allGrouping("spout");

spout会把每个数据分发给每一个下级的bolt,每个bolt线程获取到的行数都是一样的。开发时广播方式不常用。

Thread-23-bolt, lines : 1, sessionId: 5CFBA5BD76BACF436ACA9DCC8
Thread-49-bolt, lines : 1, sessionId: 5CFBA5BD76BACF436ACA9DCC8
Thread-23-bolt, lines : 2, sessionId: 5D16C7A886E2P2AE3EA29FC3E
Thread-49-bolt, lines : 2, sessionId: 5D16C7A886E2P2AE3EA29FC3E
.
.
.
Thread-23-bolt, lines : 49, sessionId: 5X16BCA8823AC4BD9CD196A5D
Thread-49-bolt, lines : 49, sessionId: 5X16BCA8823AC4BD9CD196A5D
Thread-23-bolt, lines : 50, sessionId: 5C3FBA728FD7D264B80769B23
Thread-49-bolt, lines : 50, sessionId: 5C3FBA728FD7D264B80769B23

6)Global Grouping 全局分组

1的spout,2个bolt。

topoBuilder.setSpout("spout", new MySpout(), 1);
// Global Grouping 全局分组
topoBuilder.setBolt("bolt", new MyBolt(), 2).globalGrouping("spout");

控制台打印的内容,有2个线程,名称分别为Thread-22-bolt 和 Thread-50-bolt。但是只有序号小的有接收到数据,序号大的没有接收到数据。Global Grouping 全局分组是把数据分配给id值最低的task。

[Thread-22-bolt] INFO  backtype.storm.daemon.executor - Prepared bolt bolt:(5)
[Thread-50-bolt] INFO  backtype.storm.daemon.executor - Prepared bolt bolt:(6)
.
.
.
Thread-22-bolt, lines : 1, sessionId: 5CFBA5BD76BACF436ACA9DCC8
Thread-22-bolt, lines : 2, sessionId: 5D16C7A886E2P2AE3EA29FC3E
Thread-22-bolt, lines : 3, sessionId: 5D16C7A886E2P2AE3EA29FC3E
.
.
.
Thread-22-bolt, lines : 47, sessionId: 5B16C0F7215109AG43528BA2D
Thread-22-bolt, lines : 48, sessionId: 5N16C2FE51E5619C2A1244215
Thread-22-bolt, lines : 49, sessionId: 5X16BCA8823AC4BD9CD196A5D
Thread-22-bolt, lines : 50, sessionId: 5C3FBA728FD7D264B80769B23

 

思考:读取文件案例思考

      示例中用的是storm读取文件,把文件作为数据源,在企业中很少见。storm是分布式应用,数据会分发到每一台supervisor执行,读本地文件只在一台机器上。

1)Spout数据源可以是数据库、文件、MQ(比如:Kafka)。
2)数据源是数据库:只适合读取数据库的配置文件,但不能读取增量数据。
3)数据源是文件:只适合测试、讲课用(因为集群是分布式集群),其他无用。
4)企业产生的 log 文件处理步骤:
  (1)读出内容写 入MQ
  (2)Storm 再处理

读文件案例说明:

1)分布式应用无法读文件;

2)spout无法并发读,开并发会重复读。

 

并发度场景分析

场景分析:

单线程下:加减乘除(其实什么都可以做),和任何类进行操作。

多线程下:可以做局部加减乘除,不适合做全部加减乘除。

多线程下适合:

a、局部加减乘除

b、做处理类Operate,如split

c、持久化,如入DB

 

 

官方案例

1、统计单词 WordCountTopology,在storm-starter-master工程的目录如下。

为了便于理解,对官方的案例进行改写。在源码文件夹lesson下,创建包WordCount,复制WordCountTopology.java到WordCount包。

2、程序分析:

(1)有1个spout,有2个bolt。

(2)MyRandomSentenceSpout多次发送数据,nextTuple函数的实现是,每次发送的数据相同。

(3)MySplit,接收到spout的数据,数据都是字符串,对字符串进行分割。

(4)WordCount,接收从split发射的数据,都是单个字符,统计每个字符的个数。

3、各个程序代码和运行结果

(1)主程序WordCountTopology,用于统计单词个数的bolt程序WordCount

package WordCount;

import backtype.storm.Config;
import backtype.storm.LocalCluster;
import backtype.storm.StormSubmitter;
import backtype.storm.task.ShellBolt;
import backtype.storm.topology.BasicOutputCollector;
import backtype.storm.topology.IRichBolt;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.topology.TopologyBuilder;
import backtype.storm.topology.base.BaseBasicBolt;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Tuple;
import backtype.storm.tuple.Values;
import storm.starter.spout.RandomSentenceSpout;

import java.util.HashMap;
import java.util.Map;

/**
 * This topology demonstrates Storm's stream groupings and multilang capabilities.
 */
public class WordCountTopology {
  public static class SplitSentence extends ShellBolt implements IRichBolt {

    public SplitSentence() {
      super("python", "splitsentence.py");
    }

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

    @Override
    public Map<String, Object> getComponentConfiguration() {
      return null;
    }
  }
  
  // 统计每个单词出现的次数
  public static class WordCount extends BaseBasicBolt {
    Map<String, Integer> counts = new HashMap<String, Integer>();

    @Override
    public void execute(Tuple tuple, BasicOutputCollector collector) {
      String word = tuple.getString(0);
      Integer count = counts.get(word);
      if (count == null)
        count = 0;
      count++;
      counts.put(word, count);
      //打印出当前线程名称,单词 和 个数
      System.out.println(Thread.currentThread().getName() + ", word = " + word + ", count = " + count);
      collector.emit(new Values(word, count));
    }

    @Override
    pub
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值