文章转载自:http://blog.csdn.net/xqy1522/article/details/8520274
Storm Trident的核心数据模型是一批一批被处理的“流”,“流”在集群的分区在集群的节点上,对“流”的操作也是并行的在每个分区上进行。
Trident有五种对“流”的操作:
1. 不需要网络传输的本地批次运算
2. 需要网络传输的“重分布”操作,不改变数据的内容
3. 聚合操作,网络传输是该操作的一部分
4. “流”分组(grouby)操作
5. 合并和关联操作
批次本地操作:
批次本地操作不需要网络传输,本格分区(partion)的运算是互相独立的
Functions(函数)
函数接收一些字段并发射(emit)零个或更多“元组”。输出的字段附加在原始的元组上面。如果一个函数不发射数据,原始的数据被过滤。如果发射(emit)多个元组,原始的元组被重复的冗余到输出元组上。例如:
public class MyFunction extends BaseFunction { public void execute(TridentTuple tuple, TridentCollector collector) { for(int i=0; i < tuple.getInteger(0); i++) { collector.emit(new Values(i)); } } } |
假设有一个叫“mystream”输入流有[“a”,“b”,“c“]三个字段
[1, 2, 3] [4, 1, 6] [3, 0, 8] |
如果运行下面的代码:
mystream.each(new Fields("b"), new MyFunction(), new Fields("d"))) |
运行的结果将会有4个字段[“a”,“b”,“c”,“d”],如下:
[1, 2, 3, 0] [1, 2, 3, 1] [4, 1, 6, 0] |
Filters(过滤)
Filters接收一个元组(tuple),决定是否需要继续保留这个元组。,比如:
public class MyFilter extends BaseFunction {
public booleanisKeep(TridentTuple tuple) { return tuple.getInteger(0) == 1 && tuple.getInteger(1) == 2; } } |
假设有如下输入:
[1, 2, 3] [2, 1, 1] [2, 3, 4] |
运行下面的代码:
mystream.each(new Fields("b","a"), new MyFilter()) |
结果将会如下:
[2, 1, 1] |
分区汇聚
分区汇总是在每个批次的分区上运行的函数,和函数不同的是,分区汇总发射(emit)的数据覆盖了原始的tuple。考虑下面的例子:
mystream.partitionAggregate(new Fields("b"), new Sum(), new Fields("sum")) |
假设输入的“流”包含【“a”,“b”】两个字段,并且按照如下分区
Partition 0: ["a", 1] ["b", 2] Partition 1: ["a", 3] ["c", 8] Partition 2: ["e", 1] ["d", 9] ["d", 10 |
输出的“流”将只包含一个叫“sum”的字段:
Partition 0: [3] Partition 1: [11] Partition 2: [20] |
这里有定义了三种不同的聚合接口:CombinerAggreator,ReduceAggregator和Aggregate。
CombinerAggregator:
public interface CombinerAggregator<T> extends Serializable {
T init(TridentTuple tuple);
T combine(T val1, T val2);
T zero();
} |
一个CombinerAggregator返回一个单独的元组,这个元组值有一个字段。CombinerAggregator在每个tuple上运行init,使用combine去联合结果直到只有一个tuple剩余。如果批次没有数据,运行zero函数。比如,下面实现了一个Count
public class Count implements CombinerAggregator<Long> {
public Long init(TridentTuple tuple) {
return 1L;
} public Long combine(Long val1, Long val2) {
return val1 + val2; } public Long zero() {
return 0L;
} } |
可以看到,CombinerAggregator的优势使用聚合函数代替分区聚合。在这种情况下,trident自动优化成,在网络传输前合一做局部的汇总。(类似于mapreduce的combine)。
RducerAggregator接口如下:
public interface ReducerAggregator<T> extends Serializable {
T init();
T reduce(T curr, TridentTuple tuple);
} |
RducerAggregator在初始化的时候产生一个值,每个输入的元组在这个值的基础上进行迭代并输出一个单独的值,例如下面定义了一个Count的reduceAggegator:
public class Count implements ReducerAggregator<Long> {
public Long init() {
return 0L;
} public Long reduce(Long curr, TridentTuple tuple) {
return curr + 1;
} } |
ReducerAggregator可以和persistentAggregate一起使用,后面会讲到。
更加通用聚合接口是Aggregator,如下:
public interface Aggregator<T> extends Operation {
T init(Object batchId, TridentCollector collector);
voidaggregate(T state, TridentTuple tuple, TridentCollector collector); voidcomplete(T state, TridentCollector collector); } |
Aggregator可以发射任何数量的输出tuple,这些tuple可以包含多个字段(fields)。可以在执行过程的任何点发射输出。聚合按照下面的方式执行:
1. Init函数在执行批次操作之前被调用,并返回一个state对象,这个额对象将会会传入到aggregate和complete函数中。
2. Aggregate会对批次中每个tuple调用,这个方法可以跟新state也可以发射(emit)tuple。
3. 当这个批次分区的数据执行结束后调用complete函数。
下面是一个使用Aggregate事项的Count
public class CountAgg extends BaseAggregator<CountState> {
static class CountState {
long count = 0; } public CountState init(Object batchId, TridentCollector collector) {
return new CountState();
} public voidaggregate(CountState state, TridentTuple tuple, TridentCollector collector) { state.count+=1; } public voidcomplete(CountState state, TridentCollector collector) { collector.emit(new Values(state.count)); } } |
如果想同事执行多个聚合,可以使用如下的调用链
mystream.chainedAgg()
.partitionAggregate(new Count(), new Fields("count")) .partitionAggregate(new Fields("b"), new Sum(), new Fields("sum")) .chainEnd()
|
这个代码将会在每个分区上执行count和sum聚合。输出将包含【“count”,“sum”】字段。
StateQuery和partitionPersist
stateQuery和partitionPersistent查询和跟新状态。可以参考trident state doc。https://github.com/nathanmarz/storm/wiki/Trident-state
投影(projection)
投影操作是对数据上进行列裁剪,如果你有一个流有【“a”,“b”,“c”,“d”】四个字段,执行下面的代码:
mystream.project(new Fields("b","d")) |
输出流将只有【“b”,“d”】两个字段。
重分区(repartition)操作
重分区操作是通过一个函数改变元组(tuple)在task之间的分布。也可以调整分区数量(比如,如果并发的hint在repartition之后变大)重分区(repatition)需要网络传输。,线面是重分区函数:
1. Shuffle:使用随机算法在目标分区中选一个分区发送数据
2. Broadcast:每个元组重复的发送到所有的目标分区。这个在DRPC中很有用。如果你想做在每个分区上做一个statequery。
3. paritionBy:根据一系列分发字段(fields)做一个语义的分区。通过对这些字段取hash值并对目标分区数取模获取目标分区。paritionBy保证相同的分发字段(fields)分发到相同的目标分区。
4. global:所有的tuple分发到相同的分区。这个分区所有的批次相同。
5. batchGobal:本批次的所有tuple发送到相同的分区,不通批次可以在不通的分区。
6. patition:这个函数接受用户自定义的分区函数。用户自定义函数事项 backtype.storm.grouping.CustomStreamGrouping接口。
聚合操作
Trident有aggregate和persistentAggregate函数对流做聚合。Aggregate在每个批次上独立运行,persistentAggregate聚合流的所有的批次并将结果存储下来。
在一个流上做全局的聚合,可以使用reducecerAggregator或者aggretator,这个流先被分成一个分区,然后聚合函数在这个分区上运行。如果使用CombinerAggreator,Trident贤惠在每个分区上做一个局部的汇总,然后重分区冲为一个分区,在网络传输结束后完成聚合。CombinerAggreator非常有效,在尽可能的情况下多使用。
下面是一个做批次内聚合的例子:
mystream.aggregate(new Count(), new Fields("count")) |
和partitionAggregate一样,聚合的aggregate也可以串联。如果将CombinerAggreator和非CombinerAggreator串联,trident就不能做局部汇总的优化。
流分组操作
GroupBy操作根据特殊的字段对流进行重分区,分组字段相同的元组(tuple)被分到同一个分区,下面是个GroupBy的例子:
如果对分组的流进行聚合,聚会会对每个组聚合而不是这个批次聚合。(和关系型数据库的groupby相同)。PersistentAggregate也可以在分组的流哈桑运行,这种情况下结果将会存储在MapState里面,key是分组字段。可以查看https://github.com/nathanmarz/storm/wiki/Trident-state。
和普通聚合一样,分组流的聚合也可以串联。
合并和关联
最后一部分API是将不通的流合并,最简单的方式就是合并(meger)多个流成为一个流。可以使用tridentTopology#meger,如下:
topology.merge(stream1, stream2, stream3);
|
Trident合并的流字段会一第一个流的字段命名。
另一个中合并流的方法是join。类似SQL的join都是对固定输入的。而流的输入是不固定的,所以不能按照sql的方法做join。Trident中的join只会在spout发出的每个批次见进行。
下面是个join的例子,一个流包含字段【“key”,“val1”,“val2”】,另一个流包含字段【“x”,“val1”】:
topology.join(stream1, new Fields("key"), stream2, new Fields("x"), new Fields("key","a","b","c")); |
Stream1的“key”和stream2的“x”关联。Trident要求所有的字段要被名字,因为原来的名字将会会覆盖。Join的输入会包含:
1. 首先是join字段。例子中stream1中的“key”对应stream2中的“x”。
2. 接下来,会吧非join字段依次列出来,排列顺序按照传给join的顺序。例子中“a”,“b”对应stream1中的“val1”和“wal2”,“c”对应stream2中的“val1”。
当join的流分别来自不通的spout,这些spout会同步发射的批次,也就是说,批次处理会包含每个spout发射的tuple。
有人可能会问怎么做“windowedjoin”,join的一边和另一边最近一个小时的数据做join运算。
为了实现这个,你可以使用patitionPersist和stateQuery。最近一个小时的数据可以按照join字段做key存储下改变,在join的过程中可以查询存储的额数据完成join操作。