原文链接:http://www.bubuko.com/infodetail-467560.html
一、概要
1.1 Storm(简介)
Storm是一个实时的可靠地分布式流计算框架。
具体就不多说了,举个例子,它的一个典型的大数据实时计算应用场景:从Kafka消息队列读取消息(可以是logs,clicks,sensor data)、通过Storm对消息进行计算聚合等预处理、把处理结果持久化到NoSQL数据库或者HDFS做进一步深入分析。
1.2 Trident(简介)
Trident是对Storm的更高一层的抽象,除了提供一套简单易用的流数据处理API之外,它以batch(一组tuples)为单位进行处理,这样一来,可以使得一些处理更简单和高效。
我们知道把Bolt的运行状态仅仅保存在内存中是不可靠的,如果一个node挂掉,那么这个node上的任务就会被重新分配,但是之前的状态是无法恢复的。因此,比较聪明的方式就是把storm的计算状态信息持久化到database中,基于这一点,trident就变得尤为重要。因为在处理大数据时,我们在与database打交道时通常会采用批处理的方式来避免给它带来压力,而trident恰恰是以batch groups的形式处理数据,并提供了一些聚合功能的API。
二、Trident API 实践
Trident其实就是一套API,但现阶段网上关于Trident API中各个函数的用法含义资料不多,下面我就根据一些英文资料和自己的理解,详细介绍一下Trident API各个函数的用法和含义。
2.1 each() 方法
作用:操作batch中的每一个tuple内容,一般与Filter或者Function函数配合使用。
下面通过一个例子来介绍each()方法,假设我们有一个FakeTweetsBatchSpout,它会模拟一个Stream,随机产生一个个消息。我们可以通过设置这个Spout类的构造参数来改变这个Spout的batch Size的大小。
2.1.1 Filter类:过滤tuple
一个通过actor字段过滤消息的Filter:
public static class PerActorTweetsFilter extends BaseFilter {
String actor;
public PerActorTweetsFilter(String actor) {
this.actor = actor;
}
@Override
public boolean isKeep(TridentTuple tuple) {
return tuple.getString(0).equals(actor);
}
}
Topology:
topology.newStream(“spout”, spout)
.each(new Fields(“actor”, “text”), new PerActorTweetsFilter(“dave”))
.each(new Fields(“actor”, “text”), new Utils.PrintFilter());
从上面例子看到,each()方法有一些构造参数
第一个构造参数:作为Field Selector,一个tuple可能有很多字段,通过设置Field,我们可以隐藏其它字段,仅仅接收指定的字段(其它字段实际还在)。
第二个是一个Filter:用来过滤掉除actor名叫"dave"外的其它消息。
2.1.2 Function类:加工处理tuple内容
一个能把tuple中text内容变成大写的Function:
public static class UppercaseFunction extends BaseFunction {
@Override
public void execute(TridentTuple tuple, TridentCollector collector) {
collector.emit(new Values(tuple.getString(0).toUpperCase()));
}
}
Topology:
topology.newStream(“spout”, spout)
.each(new Fields(“actor”, “text”), new PerActorTweetsFilter(“dave”))
.each(new Fields(“text”, “actor”), new UppercaseFunction(), new Fields(“uppercased_text”))
.each(new Fields(“actor”, “text”, “uppercased_text”), new Utils.PrintFilter());
首先,UppercaseFunction函数的输入是Fields("text", "actor"),其作用是把其中的"text"字段内容都变成大写。
其次,它比Filter多出一个输出字段,作用是每个tuple在经过这个Function函数处理后,输出字段都会被追加到tuple后面,在本例中,执行完Function之后的tuple内容多了一个”uppercased_text”,并且这个字段排在最后面。
2.1.3 Field Selector与project
我们需要注意的是,上面每个each()方法的第一个Field字段仅仅是隐藏掉没有指定的字段内容,实际上被隐藏的字段依然还在tuple中,如果想要彻底丢掉它们,我们就需要用到project()方法。
投影操作作用是仅保留Stream指定字段的数据,比如有一个Stream包含如下字段: [“a”, “b”, “c”, “d”],运行如下代码:
java mystream.project(new Fields(“b”, “d”))
则输出的流仅包含 [“b”, “d”]字段。
2.2 parallelismHint()方法和partitionBy()
2.2.1 parallelismHint()
指定Topology的并行度,即用多少线程执行这个任务。我们可以稍微改一下我们的Filter,通过打印当前任务的partitionIndex来区分当前是哪个线程。
Filter:
public static class PerActorTweetsFilter extends BaseFilter {
private int partitionIndex;
private String actor;
public PerActorTweetsFilter(String actor) {
this.actor = actor;
}
@Override
public void prepare(Map conf, TridentOperationContext context) {
this.partitionIndex = context.getPartitionIndex();
}
@Override
public boolean isKeep(TridentTuple tuple) {
boolean filter = tuple.getString(0).equals(actor);
if(filter) {
System.err.println(“I am partition [” + partitionIndex + “] and I have kept a tweet by: ” + actor);
}
return filter;
}
}
Topology:
topology.newStream(“spout”, spout)
.each(new Fields(“actor”, “text”), new PerActorTweetsFilter(“dave”))
.parallelismHint(5)
.each(new Fields(“actor”, “text”), new Utils.PrintFilter());
如果我们指定执行Filter任务的线程数量为5,那么最终的执行结果会如何呢?看一下我们的测试结果:
I am partition [4] and I have kept a tweet by: dave
I am partition [3] and I have kept a tweet by: dave
I am partition [0] and I have kept a tweet by: dave
I am partition [2] and I have kept a tweet by: dave
I am partition [1] and I have kept a tweet by: dave
我们可以很清楚的发现,一共有5个线程在执行Filter。
如果我们想要2个Spout和5个Filter怎么办呢?如下面代码所示,实现很简单。
topology.newStream(“spout”, spout)
.parallelismHint(2)
.shuffle()
.each(new Fields(“actor”, “text”), new PerActorTweetsFilter(“dave”))
.parallelismHint(5)
.each(new Fields(“actor”, “text”), new Utils.PrintFilter());
2.2.2 partitionBy()和重定向操作(repartitioning operation)
我们注意到上面的例子中用到了shuffle(),shuffle()是一个重定向操作。那什么是重定向操作呢?重定向定义了我们的tuple如何被route到下一处理层,当然不同的层之间可能会有不同的并行度,shuffle()的作用是把tuple随机的route下一层的线程中,而partitionBy()则根据我们的指定字段按照一致性哈希算法route到下一层的线程中,也就是说,如果我们用partitionBy()的话,同一个字段名的tuple会被route到同一个线程中。
比如,如果我们把上面代码中的shuffle()改成partitionBy(new Fields(“actor”)),猜一下结果会怎样?
I am partition [2] and I have kept a tweet by: dave
I am partition [2] and I have kept a tweet by: dave
I am partition [2] and I have kept a tweet by: dave
I am partition [2] and I have kept a tweet by: dave
测试结果正如我们上面描述的那样,相同字段的tuple被route到了同一个partition中。
重定向操作有如下几种:
shuffle:通过随机分配算法来均衡tuple到各个分区
broadcast:每个tuple都被广播到所有的分区,这种方式在drcp时非常有用,比如在每个分区上做stateQuery
partitionBy:根据指定的字段列表进行划分,具体做法是用指定字段列表的hash值对分区个数做取模运算,确保相同字段列表的数据被划分到同一个分区
global:所有的tuple都被发送到一个分区,这个分区用来处理整个Stream
batchGlobal:一个Batch中的所有tuple都被发送到同一个分区,不同的Batch会去往不同的分区
Partition:通过一个自定义的分区函数来进行分区,这个自定义函数实现了 backtype.storm.grouping.CustomStreamGrouping
2.3 聚合(Aggregation)
我们前面讲过,Trident的一个很重要的特点就是它是以batch的形式处理tuple的。我们可以很容易想到的针对一个batch的最基本操作应该就是聚合。Trident提供了聚合API来处理batches,来看一个例子:
2.3.1 Aggregator:
public static class LocationAggregator extends BaseAggregator