2021SC@SDUSC
stream groupings介绍和WordCountTopology介绍
2021SC@SDUSC
Stream groupings 流分组
在Storm中有8种流分组方式,通过实现CustomStreamGroupingj接口,可以实现一种风格流分组方式:
Storm 定义了八种内置数据流分组的方式:
1、Shuffle grouping(随机分组):这种方式会随机分发 tuple 给bolt 的各个 task,每个bolt 实例接收到的相同数量的 tuple 。
2、Fields grouping(按字段分组):根据指定字段的值进行分组。比如说,一个数据流根据“ word”字段进行分组,所有具有相同“ word ”字段值的 tuple 会路由到同一个 bolt 的 task 中。
3、All grouping(全复制分组):将所有的 tuple 复制后分发给所有 bolt task。每个订阅数据流的 task 都会接收到 tuple 的拷贝。
4、Globle grouping(全局分组):这种分组方式将所有的 tuples 路由到唯一一个 task 上。Storm 按照最小的 task ID 来选取接收数据的 task 。注意,当使用全局分组方式时,设置 bolt 的 task 并发度是没有意义的(spout并发有意义),因为所有 tuple 都转发到同一个 task 上了。使用全局分组的时候需要注意,因为所有的 tuple 都转发到一个 JVM 实例上,可能会引起 Storm 集群中某个 JVM 或者服务器出现性能瓶颈或崩溃。
5、None grouping(不分组):在功能上和随机分组相同,是为将来预留的。
6、Direct grouping(指向型分组):数据源会调用 emitDirect() 方法来判断一个 tuple 应该由哪个 Storm 组件来接收。只能在声明了是指向型的数据流上使用。
7、Local or shuffle grouping (本地或随机分组):和随机分组类似,但是,会将 tuple 分发给同一个 worker 内的bolt task (如果 worker 内有接收数据的 bolt task )。其他情况下,采用随机分组的方式。取决于topology 的并发度,本地或随机分组可以减少网络传输,从而提高 topology 性能。
8、Partial Key grouping : 流按分组中指定的字段(如字段分组)进行分区,但在两个下游 bolt 之间进行负载平衡,从而在传入数据倾斜时提供更好的资源利用率。
WordCountTopology
WordCountTopology是一个基本的Storm Topology, 由三个组件构成:RandomSentenceSpout
SplitSentence
WordCount
RandomSentenceSpout
这个类定义了一个Spout, 它继承自BaseRichSpout。BaseRichSpout是一个实现了IRichBolt接口的虚类,这个接口是Storm中的一个主要接口。它的nextTuple方法随机地从一个句子数组中选出一个句子发送出去,declareOutputFields方法声明了该Spout输出的消息模式,这里输出只
有一列,字段名是word:
public class RandomSentenceSpout extends BaseRichSpout {
private static final Logger LOG = LoggerFactory.getLogger(RandomSentenceSpout.class);
SpoutOutputCollector collector;
Random rand;
@Override
public void open(Map<String, Object> conf, TopologyContext context, SpoutOutputCollector collector) {
this.collector = collector;
rand = new Random();
}
@Override
public void nextTuple() {
Utils.sleep(100);
String[] sentences = new String[]{
sentence("the cow jumped over the moon"), sentence("an apple a day keeps the doctor away"),
sentence("four score and seven years ago"), sentence("snow white and the seven dwarfs"), sentence("i am at two with nature")
};
final String sentence = sentences[rand.nextInt(sentences.length)];
LOG.debug("Emitting tuple: {}", sentence);
collector.emit(new Values(sentence));
}
protected String sentence(String input) {
return input;
}
@Override
public void ack(Object id) {
}
@Override
public void fail(Object id) {
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("word"));
}
public static class TimeStamped extends RandomSentenceSpout {
private final String prefix;
public TimeStamped() {
this("");
}
public TimeStamped(String prefix) {
this.prefix = prefix;
}
@Override
protected String sentence(String input) {
return prefix + currentDate() + " " + input;
}
}
}
SplitSentence
该类定义了一个Bolt,它继承自BaseBasicBolt。BaseBasicBolt是一个实现了IBasicBolt接口的虚类。execute方法是Bolt真正处理业务逻辑的地方,它将从Spout收到的句子按照空格分割,然后把每一个单词作为一条信息发送出去
WordCount
类WordCount跟SplitSentence类似,也定义了一个Bolt。这个类对收到的所有单词进行计数统计,execute方法更新收到单词的缓存数,并将当前该单词及其对应的数目发送出去,declareOutputFields方法声明该Bolt的输出消息格式,cleanup方法在该Topology被停掉的时候被调用( 不保证一定能够调用到 ),它将当前缓存的所有单词及数目信息打印到日志中
public class WordCountBolt 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);
collector.emit(new Values(word, count));
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("word", "count"));
}
}
WordCountTopology
类WordCountTopology是真正定义Topology的地方
public class WordCountTopology extends ConfigurableTopology {
public static void main(String[] args) throws Exception {
ConfigurableTopology.start(new WordCountTopology(), args);
}
@Override
protected int run(String[] args) throws Exception {
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout("spout", new RandomSentenceSpout(), 5);
builder.setBolt("split", new SplitSentence(), 8).shuffleGrouping("spout");
builder.setBolt("count", new WordCountBolt(), 12).fieldsGrouping("split", new Fields("word"));
conf.setDebug(true);
String topologyName = "word-count";
conf.setNumWorkers(3);
if (args != null && args.length > 0) {
topologyName = args[0];
}
return submit(topologyName, conf, builder);
}
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;
}
}
}
首先创建一个TopologyBuilder对象,这个类是用来构建基本Topology
然后设置Topology的Spout, 它的id是spout , 创建一个RandomSentenceSpout对象作为Spout对象,并行度设置为5。
然后设置Topology的Bolt, 它的id是split, 创建一个SplitSentence对象作为Bolt
对象,并行度设置为8。它接收spout发出的消息,其分组策略是随机分组( ShuffleGrouping ), 即spout的多个实例会随机分发消息到split的各个实例上。
再设置Topology的另一个Bolt,它的id是count,这里创建一个WordCount对象作为Bolt对象,并行度设置为12。它接收split发出的消息,其分组策略是域分组( Fields Grouping ), 即split的各个实例会按照消息word列所对应的值决定将消息发送到count的哪个实例中,所有word列值相同的消息会被发到同一个count节点中处理。