刚遇到storm分组策略中的Field Grouping,费解了一些时间,简单做下笔记。
首先先讲下storm的元件组成吧,避免以后连基本的一个storm应用程序的流程都忘记了。
一.主要组件:tuple,stream,spout,bolt,topology
tuple(元组):元组(Tuple),是消息传递的基本单元,是一个命名的值列表,元组中的字段可以是任何类型的对象。Storm使用元组作为其数据模型,元组支持所有的基本类型、字符串和字节数组作为字段值,只要实现类型的序列化接口就可以使用该类型的对象。元组本来应该是一个key-value的Map,但是由于各个组件间传递的元组的字段名称已经事先定义好,所以只要按序把元组填入各个value即可,所以元组是一个value的List。
stream(流):流是Storm的核心抽象,是一个无界的元组系列。源源不断传递的元组就组成了流,在分布式环境中并行地进行创建和处理。
spout(水龙头) : 发送器,通过nextTuple方法不断往bolt写进数据
bolt(转接头): 在拓扑中所有处理都在Bolt中完成,Bolt是流的处理节点,从一个拓扑接收数据,然后执行进行处理的组件。Bolt可以完成过滤、业务处理、连接运算、连接与访问数据库等任何操作。
Bolt是一个被动的角色,七接口中有一个execute()方法,在接收到消息后会调用此方法,用户可以在其中执行自己希望的操作。
Bolt可以完成简单的流的转换,而完成复杂的流的转换通常需要多个步骤,因此需要多个Bolt。
Bolt可以发出超过一个的流。
topology(拓扑):一个topology为一个实时应用程序,与离线处理中的mapreduce中的job相对应。
二.分组策略:
这里只描述两种常用的分组策略:Shuffle Group , Field Group
1.Shuffle Group : 由名字可知,该分组为随机分组,它能尽可能地去对所有分组的bolt进行平均分配tuple,例如说发送了spout30个tuple,有4个bolt线程,那么每个bolt能接受到的tuple元组会尽可能地平均分配
2.Field Group:该分组为按字段分组,详细的话看代码:
以wordCount为例:
public class WordCountSpout extends BaseRichSpout {
private SpoutOutputCollector collector;
public void open(Map map, TopologyContext topologyContext, SpoutOutputCollector spoutOutputCollector) {
this.collector = spoutOutputCollector;
}
public void nextTuple() {
collector.emit(new Values("i am ximenqing love jinlian"));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
outputFieldsDeclarer.declare(new Fields("love"));
}
}
我们发送不断发送一句话来统计每个word出现的次数,首先通过nextTuple发送,并且睡眠一段时间(不然的话发送太猛)
public class WordCountSplitBolt extends BaseRichBolt {
private OutputCollector collector;
public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {
this.collector = outputCollector;
}
public void execute(Tuple tuple) {
String value = tuple.getString(0);
String[] split = value.split(" ");
for (String s : split) {
collector.emit(new Values(s, 1));
}
}
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
outputFieldsDeclarer.declare(new Fields("word", "num"));
}
}
spout发送之后,接受的自然就是bolt了,这里是第一个bolt,主要是进行切分单词的,所以这里bolt接收到数据就是刚才spout发送的那一句话,然后对这句话进行切分成一个个的单词,每一个单词发送一次,注意,在下面的declareOutputFields方法中声明了两个字段分别为word和num,这里的word对应的是上面execute方法的new values(s,1)中的s,即values对象里面的值与fields中的值是一一对应的。
bolt也能发送数据到下面的bolt,这样就连成了一条源源不断的链了,下面是第二个bolt,主要是进行单词数量汇总的:
public class WordCountBolt extends BaseRichBolt {
private Map<String, Integer> map = new HashMap<String, Integer>();
public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {
}
public void execute(Tuple tuple) {
String word = tuple.getString(0);
int num = tuple.getInteger(1);
Integer count = map.get(word);
if (count == null) {
count = 1;
map.put(word, count);
}else{
map.put(word,count + 1);
}
System.err.println(Thread.currentThread().getId() + " word:" + word + " num:" + map.toString());
}
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
}
}
自然地这里用map来收集,key为word,value为num,这一个bolt为该topology的最后一个bolt
深入看一下不同的字段分组情况,这里引用一下网上的一篇文章:
https://yq.aliyun.com/ziliao/310484
1.现在又两个字段,按照第一个字段分组
spout:
bolt:
topology:
打印结果:
这里可以看出,因为我们是以第一个字段进行分组的,所以第一个字段相同的一定会分到同一个bolt,但是同一个bolt中的字段不一定相同,发送的数据就像(k,v),类似的,我们来回想下mapreduce,这里的第一个bolt发送数据给第二个bolt的处理相当mapreduce中的map阶段,第二个bolt相当于reduce。
2.以第二个字段为分组依据:
与第一种情况相同,不过是以第二个字段相同的分到一组而已,这里可以看上面引用的文章。
3.两个字段为分组依据:
其实也是一样的,当两个字段相同的时候,即(k,v)这一对数据,k和v都相同的才会分到同一个bolt中,其余的将会随机分到不同的bolt中,
打印结果:
个人感觉storm中的字段分组策略与mapreduce的思想是很相同的,不过mapreduce进行数据的输出输入规定为(k,v),在reduce中进行汇聚的是根据key是否相同,而Field Grouping 可以对多个字段进行分组,看你是对哪几个字段进行分组,这几个字段相同的就分到同一个bolt中,如果mapreduce中也要对几个字段分组的话就只能以这几个字段作为自定义对象的属性来作为key去在reduce端进行汇聚了。
以上仅是个人菜鸟作出的对Field Grouping分组策略的简单总结作为笔记,有误敬请原谅。