任务描述
本关任务:使用 Storm Trident 完成一个过滤器的实现。
相关知识
为了完成本关任务,你需要掌握以下 Trident API: 1.Functions(函数) 2.Filters(过滤器) 3.map and flatMap(地图和平面地图) 4.peek(窥视)
Functions(函数)
加工处理 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("text", "actor"), new UppercaseFunction(), new Fields("uppercased_text"))
首先, UppercaseFunction 函数的输入是 Fields("text", "actor"),其作用是把其中的" text "字段内容都变成大写。
其次,作用是每个 tuple 在经过这个 Function 函数处理后,输出字段都会被追加到 tuple 后面,在本例中,执行完 Function 之后的 tuple 内容多了一个“ uppercased_text ”,并且这个字段排在最后面。
Filters(过滤器)
一个通过 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() 方法有一些构造参数 第一个构造参数:作为 Field Selector ,一个 tuple 可能有很多字段,通过设置 Field ,我们可以隐藏其它字段,仅仅接收指定的字段(其它字段实际还在)。
第二个是一个 Filter :用来过滤掉除 actor 名叫” dave “外的其它消息。
map and flatMap(地图和平面地图)
map
返回一个 stream
, 它包含将给定的 mapping function
(映射函数)应用到 stream 的 tuples
的结果. 这个可以用来对 tuples 应用 one-one transformation (一一变换).
例如, 如果有一个 stream of words (单词流), 并且您想将其转换为 stream of upper case words (大写字母的流), 你可以定义一个 mapping function
(映射函数)如下:
public class UpperCase extends MapFunction {
@Override
public Values execute(TridentTuple input) {
return new Values(input.getString(0).toUpperCase());
}
}
然后可以将 mapping function
(映射函数)应用于 stream 以产生 stream of uppercase words (大写字的流).
mystream.map(new UpperCase())
flatMap
类似于 map
, 但具有将 one-to-many transformation (一对多变换)应用于 values of the stream (流的值)的效果, 然后将所得到的元素 flattening (平坦化)为新的 stream 。
例如, 如果有 stream of sentences (句子流), 并且您想将其转换成 stream of words (单词流), 你可以定义一个 flatMap 函数如下:
public class Split extends FlatMapFunction {
@Override
public Iterable<Values> execute(TridentTuple input) {
List<Values> valuesList = new ArrayList<>();
for (String word : input.getString(0).split(" ")) {
valuesList.add(new Values(word));
}
return valuesList;
}
}
然后可以将 flatMap
函数应用于 stream of sentences (句子流)以产生一个 stream of words (单词流)
mystream.flatMap(new Split())
当然这些操作可以被 chained
(链接), 因此可以从如下的 stream of sentences (句子流)中获得 stream of uppercase words (大写字的流),
mystream.flatMap(new Split()).map(new UpperCase())
如果不将 output fields (输出字段)作为 parameter
(参数)传递, 则 map
和 flatMap
会将 input fields (输入字段)保留为 output fields (输出字段).
如果要使用 MapFunction
或 FlatMapFunction
使用 new output fields (新的输出字段)替换 old fields (旧字段), 您可以使用附加的 Fields
参数调用map / flatMap
, 如下所示,
mystream.map(new UpperCase(), new Fields("uppercased"))
Output stream (输出流)只有一个 output field (输出字段) ”uppercased“ , 而不管以前的流有什么输出字段. 同样的事情适用于 flatMap, 所以以下是有效的,
mystream.flatMap(new Split(), new Fields("word"))
peek(窥视)
peek 可用于在每个 trident 元组流过流时对它们执行附加操作。当元组流过管道中的某个点时,这对于调试以查看元组很有用。
例如,下面的代码将打印将单词转换为大写的结果,然后再将它们传递给 groupBy 。
mystream.flatMap(new Split()).map(new UpperCase())
.peek(new Consumer() {
@Override
public void accept(TridentTuple input) {
System.out.println(input.getString(0));
}
})
.groupBy(new Fields("word"))
.persistentAggregate(new MemoryMapState.Factory(), new Count(), new Fields("count"))
编程要求
根据提示,在右侧编辑器补充代码,编写一个过滤器的操作和实现,要求过滤出 tuple 中值为 the 的 tuple 。
测试说明
平台会对你编写的代码进行测试:
预计输入:
the cow jumped over the moon
the man went to the store and bought some candy
four score and seven years ago
how many apples can you eat
预计输出:
emit word is :the
Filter word is:the and return type is:true
emit word is :cow
emit word is :jumped
emit word is :over
emit word is :the
Filter word is:the and return type is:true
emit word is :moon
emit word is :the
Filter word is:the and return type is:true
emit word is :man
emit word is :went
emit word is :to
emit word is :the
Filter word is:the and return type is:true
emit word is :store
emit word is :and
emit word is :bought
emit word is :some
emit word is :candy
emit word is :four
emit word is :score
emit word is :and
emit word is :seven
emit word is :years
emit word is :ago
emit word is :how
emit word is :many
emit word is :apples
emit word is :can
emit word is :you
emit word is :eat
代码如下:
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.generated.StormTopology;
import org.apache.storm.trident.TridentTopology;
import org.apache.storm.trident.operation.BaseFilter;
import org.apache.storm.trident.operation.BaseFunction;
import org.apache.storm.trident.operation.TridentCollector;
import org.apache.storm.trident.testing.FixedBatchSpout;
import org.apache.storm.trident.tuple.TridentTuple;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Values;
/* 部分输出
emit word is :the
Filter word is:the and return type is:true
emit word is :cow
emit word is :jumped
emit word is :over
emit word is :the
Filter word is:the and return type is:true
emit word is :moon
* */
public class FunctionFilter {
public static void main(String[] args){
TridentTopology topology = new TridentTopology();
FixedBatchSpout spout = new FixedBatchSpout(new Fields("sentence"), 1,
new Values("the cow jumped over the moon"),
new Values("the man went to the store and bought some candy"),
new Values("four score and seven years ago"),
new Values("how many apples can you eat"));
//不循环发送数据
spout.setCycle(false);
//****请根据提示补全Topology程序****//
/*********begin*********/
topology.newStream("spout1", spout)
//topology的newStream 方法从输入源中读取数据, 并在 topology 中创建一个新的数据流 batch-spout
.each(new Fields("sentence"), new Split(), new Fields("word"))
.groupBy(new Fields("word"));
//使用.each()方法,sentence tuple经过split()方法后输出word tuple
//使用.each()方法,new Fields()保留setence tuple和word tuple ,经过WordFilter() 过滤 单词 the
/*********end*********/
StormTopology stormTopology = topology.build();
LocalCluster cluster = new LocalCluster();
Config conf = new Config();
cluster.submitTopology("soc", conf,stormTopology);
}
//Filter过滤器
public static class WordFilter extends BaseFilter {
String actor;
public WordFilter(String actor) {
this.actor = actor;
}
@Override
public boolean isKeep(TridentTuple tuple) {
//如果元组的值和 actor 相等(这里的actor是“the”)
if(tuple.getString(1).equals(actor)){
//输出 Filter word is:the and return type is:true
System.out.println("Filter word is:"+tuple.getString(1) + " and return type is:"+tuple.getString(1).equals(actor));
}
return tuple.getString(1).equals(actor);
}
}
// Function函数
public static class Split extends BaseFunction {
public void execute(TridentTuple tuple, TridentCollector collector) {
String sentence = tuple.getString(0);
//把句子以空格切分为单词
//每一个 sentence tuple 可能会被转换成多个 word tuple,
//比如说 "the cow jumped over the moon" 这个句子会被转换成 6 个 "word" tuples
for(String word: sentence.split(" ")) {
System.out.println("emit word is :"+word);
collector.emit(new Values(word));
}
}
}
}