trident api

文章转载自: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操作。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值