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) {