Storm常见模式——流聚合
转自:http://www.cnblogs.com/panfeng412/archive/2012/06/04/storm-common-patterns-of-stream-join.html
流聚合(stream join)是指将具有共同元组(tuple)字段的数据流(两个或者多个)聚合形成一个新的数据流的过程。
从定义上看,流聚合和SQL中表的聚合(table join)很像,但是二者有明显的区别:table join的输入是有限的,并且join的语义是非常明确的;而流聚合的语义是不明确的并且输入流是无限的。
数据流的聚合类型跟具体的应用有关。一些应用把两个流发出的所有的tuple都聚合起来——不管多长时间;而另外一些应用则只会聚合一些特定的tuple。而另外一些应用的聚合逻辑又可能完全不一样。而这些聚合类型里面最常见的类型是把所有的输入流进行一样的划分,这个在storm里面用fields grouping在相同字段上进行grouping就可以实现。
下面是对storm-starter(代码见:https://github.com/nathanmarz/storm-starter)中有关两个流的聚合的示例代码剖析:
先看一下入口类SingleJoinExample。
(1)这里首先创建了两个发射源spout,分别是genderSpout和ageSpout:

FeederSpout genderSpout = new FeederSpout(new Fields("id", "gender"));
FeederSpout ageSpout = new FeederSpout(new Fields("id", "age"));
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout("gender", genderSpout);
builder.setSpout("age", ageSpout);

其中genderSpout包含两个tuple字段:id和gender,ageSpout包含两个tuple字段:id和age(这里流聚合就是通过将相同id的tuple进行聚合,得到一个新的输出流,包含id、gender和age字段)。
(2)为了不同的数据流中的同一个id的tuple能够落到同一个task中进行处理,这里使用了storm中的fileds grouping在id字段上进行分组划分:
builder.setBolt("join", new SingleJoinBolt(new Fields("gender", "age")))
.fieldsGrouping("gender", new Fields("id"))
.fieldsGrouping("age", new Fields("id"));
从中可以看到,SingleJoinBolt就是真正进行流聚合的地方。下面我们来看看:
(1)SingleJoinBolt构造时接收一个Fileds对象,其中传进的是聚合后将要被输出的字段(这里就是gender和age字段),保存到变量_outFileds中。
(2)接下来看看完成SingleJoinBolt的构造后,SingleJoinBolt在真正开始接收处理tuple之前所做的准备工作(代码见prepare方法):
a)首先,将保存OutputCollector对象,创建TimeCacheMap对象,设置超时回调接口,用于tuple处理失败时fail消息;紧接着记录数据源的个数:
_collector = collector;
int timeout = ((Number) conf.get(Config.TOPOLOGY_MESSAGE_TIMEOUT_SECS)).intValue();
_pending = new TimeCacheMap<List<Object>, Map<GlobalStreamId, Tuple>>(timeout, new ExpireCallback());
_numSources = context.getThisSources().size();
b)遍历TopologyContext中不同数据源,得到所有数据源(这里就是genderSpout和ageSpout)中公共的Filed字段,保存到变量_idFields中(例子中就是id字段),同时将_outFileds中字段所在数据源记录下来,保存到一张HashMap中_fieldLocations,以便聚合后获取对应的字段值。

Set<String> idFields = null;
for(GlobalStreamId source: context.getThisSources().keySet()) {
Fields fields = context.getComponentOutputFields(source.get_componentId(), source.get_streamId());
Set<String> setFields = new HashSet<String>(fields.toList());
if(idFields==null) idFields = setFields;
else idFields.retainAll(setFields); for(String outfield: _outFields) {
for(String sourcefield: fields) {
if(outfield.equals(sourcefield)) {
_fieldLocations.put(outfield, source);
}
}
}
}
_idFields = new Fields(new ArrayList<String>(idFields)); if(_fieldLocations.size()!=_outFields.size()) {
throw new RuntimeException("Cannot find all outfields among sources");
}

(3)好了,下面开始两个spout流的聚合过程了(代码见execute方法):
首先,从tuple中获取_idFields字段,如果不存在于等待被处理的队列_pending中,则加入一行,其中key是获取到的_idFields字段,value是一个空的HashMap<GlobalStreamId, Tuple>对象,记录GlobalStreamId到Tuple的映射。
List<Object> id = tuple.select(_idFields);
GlobalStreamId streamId = new GlobalStreamId(tuple.getSourceComponent(), tuple.getSourceStreamId());
if(!_pending.containsKey(id)) {
_pending.put(id, new HashMap<GlobalStreamId, Tuple>());
}
从_pending队列中,获取当前GlobalStreamId streamId对应的HashMap对象parts中:
Map<GlobalStreamId, Tuple> parts = _pending.get(id);
如果streamId已经包含其中,则抛出异常,接收到同一个spout中的两条一样id的tuple,否则将该streamid加入parts中:
if(parts.containsKey(streamId)) throw new RuntimeException("Received same side of single join twice");
parts.put(streamId, tuple);
如果parts已经包含了聚合数据源的个数_numSources时,从_pending队列中移除这条记录,然后开始构造聚合后的结果字段:依次遍历_outFields中各个字段,从_fieldLocations中取到这些outFiled字段对应的GlobalStreamId,紧接着从parts中取出GlobalStreamId对应的outFiled,放入聚合后的结果中。

if(parts.size()==_numSources) {
_pending.remove(id);
List<Object> joinResult = new ArrayList<Object>();
for(String outField: _outFields) {
GlobalStreamId loc = _fieldLocations.get(outField);
joinResult.add(parts.get(loc).getValueByField(outField));
}

最后通过_collector将parts中存放的tuple和聚合后的输出结果发射出去,并ack这些tuple已经处理成功。
_collector.emit(new ArrayList<Tuple>(parts.values()), joinResult);
for(Tuple part: parts.values()) {
_collector.ack(part);
}
}
否则,继续等待两个spout流中这个streamid都到齐后再进行聚合处理。
(4)最后,声明一下输出字段(代码见declareOutputFields方法):
declarer.declare(_outFields);
Storm常见模式——流聚合的更多相关文章
- Storm常见模式——分布式RPC
Storm常见模式——分布式RPC 本文翻译自:https://github.com/nathanmarz/storm/wiki/Distributed-RPC,作为学习Storm DRPC的资料,转 ...
- Storm入门(九)Storm常见模式之流聚合
流聚合(stream join)是指将具有共同元组(tuple)字段的数据流(两个或者多个)聚合形成一个新的数据流的过程. 从定义上看,流聚合和SQL中表的聚合(table join)很像,但是二者有 ...
- Storm常见模式——批处理
Storm对流数据进行实时处理时,一种常见场景是批量一起处理一定数量的tuple元组,而不是每接收一个tuple就立刻处理一个tuple,这样可能是性能的考虑,或者是具体业务的需要. 例如,批量查询或 ...
- Twitter Storm: storm的一些常见模式
这篇文章列举出了storm topology里面的一些常见模式: 流聚合(stream join) 批处理(Batching) BasicBolt 内存内缓存 + fields grouping 组合 ...
- Storm分布式实时流计算框架相关技术总结
Storm分布式实时流计算框架相关技术总结 Storm作为一个开源的分布式实时流计算框架,其内部实现使用了一些常用的技术,这里是对这些技术及其在Storm中作用的概括介绍.以此为基础,后续再深入了解S ...
- HCNA配置手工负载分担模式链路聚合
一.配置手工负载分担模式链路聚合 链路聚合(Link Aggregation)是将—组物理接口捆绑在一起作为一个逻辑接口来增加带宽的一种方法,又称为多接口负载均衡组(Load Sharing Grou ...
- 浅议NetMQ常见模式和消息加密机制
浅议NetMQ常见模式和消息加密机制 概述 在传统企业级开发中,消息队列机制已经成为一种非常常见的技术实现手段,而基于NetMQ则看起来有点像一朵"奇葩",看起来从名字似乎是一个消 ...
- HCNA配置静态LACP模式链路聚合
1.静态LACP模式 静态LACP模式是一种利用LACP协议进行聚合参数协商.确定活动接口和非活动接口的链路聚合方式.该模式下,需手工创建Eth-Trunk,手工加入Eth-Trunk成员接口,由LA ...
- elasticsearch 常见查询及聚合的JAVA API
ES 常见查询 (1)根据ID 进行单个查询 GetResponse response = client.prepareGet("accounts", "person&q ...
随机推荐
- 洛谷.3121.审查(AC自动机 链表)
题目链接 //删掉一个单词需要前移一段位置,用链表维护就好了 复杂度O(sum(len)) #include <cstdio> #include <cstring> #defi ...
- Python3练习题系列(09)——物以类聚,人以群分
目标: 用类管理同类事物 解析: 用到“class”的编程语言被称作“Object Oriented Programming(面向对象编程)”语言.首先你需要做出“东西”来,然后你“告诉”这些东西去完 ...
- linux中内存超出后可以这样
http://www.cnblogs.com/hongten/archive/2012/11/16/java_PermGen_space.html
- zookeepercli - Command Line Interface for ZooKeeper
简介 ZooKeeper命令行界面(CLI)用于与ZooKeeper进行交互以用于开发目的.它对于调试很有用. 要执行ZooKeeper CLI操作,首先打开你的ZooKeeper服务器(“bin / ...
- 迭代函数:zip、enumerate,list解析
#encoding:utf-8 """ 并行迭代: zip enumerate 获取元素及下标 list解析 iter """ #zip # ...
- Voltage Level-Shifter Output Waveform
http://www.cypress.com/knowledge-base-article/interfacing-sram-jtag-signals-using-voltage-level-shif ...
- javascript 原生常用api 数组方法大全
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- yum install --downloadonly 下载依赖包到本地 但不安装
如果手动去一个个找依赖是很困难的,即便已经知道名字.版本,下面就依赖系统自带的命令完成该步骤 以java为例,其他安装包只要替换包名 yum install --downloadonly --down ...
- 微软BI 之SSIS 系列 - Execute SQL Task 中的 Single Row 与 Full Result Set 的处理技巧
开篇介绍 Execute SQL Task 这个控件在微软BI ETL 项目中使用的频率还是非常高的,也是大部分入门 SSIS 初学者最早接触到的几个控制流控件. 我们通常使用 Execute SQL ...
- 为Docker容器设置http代理
以下内容复制自:传送门 ,可以直接去该地址查看. HTTP/HTTPS proxy The Docker daemon uses the HTTP_PROXY, HTTPS_PROXY, and NO ...