在本文中,我们从头开始,通过在flink上运行一个流分析程序来学习如何使用flink。
Wikipedia提供了一个IRC频道,这个频道会记录所有在维基百科上编辑的内容。我们通过flink来读取这个频道,并计算每个用户在一个时间窗口内编辑的字节数。这个很简单,在flink中实现只需要几分钟。但这个可以给我们一个了解flink并编写更复杂计算程序的台阶。
开启一个maven工程
我们通过flink提供的maven脚手架来创建我们的工程骨架,如果通过脚手架来创建工程可以通过 Java API Quickstart 来了解,本次我们的脚手架工程命令如下:
$ mvn archetype:generate \
-DarchetypeGroupId=org.apache.flink \
-DarchetypeArtifactId=flink-quickstart-java \
-DarchetypeVersion=1.7.2 \
-DgroupId=wiki-edits \
-DartifactId=wiki-edits \
-Dversion=0.1 \
-Dpackage=wikiedits \
-DinteractiveMode=false
上述命令我们可以根据需求来修改groupId,artifactId和package。
工程生成后,我们需要添加Flink Wikipedia connector依赖,这样我们就可以在程序中使用它,在我们pom.xml中,dependencies如下:
<dependencies>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-java</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java_2.11</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-clients_2.11</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-wikiedits_2.11</artifactId>
<version>${flink.version}</version>
</dependency>
</dependencies>
其中,flink-connector-wikiedits_2.11
是我们额外添加的依赖。
编写一个Flink程序
现在是编码时间,打开我们常用的IDE,导入我们刚才创建的maven工程。创建WikipediaAnalysis.java文件。
package wikiedits;
public class WikipediaAnalysis {
public static void main(String[] args) throws Exception {
}
}
这个代码很基础,但我们会往里面填充需要的内容。注意,我不会将import语句列出,因为这些IDE会自动帮我们处理。在这个文章的结尾,我们展示全部代码包含import语句。如果你想将代码粘贴到你的编辑器,你可以跳过这段。
第一步,我们需要创建一个StreamExecutionEnvironment
(或者ExecutionEnvironment
如果你想写一个批处理任务)。这个类用于设置执行参数和创建输入源,用来从外部系统读取数据。现在我们可以在main方法中添加如下代码。
StreamExecutionEnvironment see = StreamExecutionEnvironment.getExecutionEnvironment();
下一步,我们需要创建一个一个输入源,从Wikipedia IRC log中读取数据。
DataStream<WikipediaEditEvent> edits = see.addSource(new WikipediaEditsSource());
这行语句创建了一个包含WikipediaEditEvent的DataStream,它允许我们进一步处理。本次我们的目的是计算每个用户在一个特定时间范围内新增或移除的字节数,我们假设是5秒。因此,我们首先需要通过用户名来标记数据源,也就是说后续的操作需要考虑用户名。在我们这个例子中,统计一个时间窗口内编辑的字符是对每一个用户单独统计。在Flink中,提供了KeySelector
这个类,方便我们标记一个流。如下:
KeyedStream<WikipediaEditEvent, String> keyedEdits = edits
.keyBy(new KeySelector<WikipediaEditEvent, String>() {
@Override
public String getKey(WikipediaEditEvent event) {
return event.getUser();
}
});
通过上面的代码,我们会获取到一个带有key(用户名)的stream,
我们现在可以在上面添加时间窗口及计算。时间窗口会筛选流上的一段内容,我们可以在上面进行计算。窗口是必须的,当我们是在一个无尽的流上进行聚合时。在我们这个例子中,我们统计没5秒内用户编辑的字节数。
DataStream<Tuple2<String, Long>> result = keyedEdits
.timeWindow(Time.seconds(5))
.fold(new Tuple2<>("", 0L), new FoldFunction<WikipediaEditEvent, Tuple2<String, Long>>() {
@Override
public Tuple2<String, Long> fold(Tuple2<String, Long> acc, WikipediaEditEvent event) {
acc.f0 = event.getUser();
acc.f1 += event.getByteDiff();
return acc;
}
});
首先调用timeWindow()
,j假设我们希望在一个5秒的滚动时间窗口(无重叠),第二步在每个流动窗口上,对每个key调用一个Fold transformation
。在我们这个例子中,我们从一个初始值("", 0L)
开始,并且在每个时间窗口内对每个用户累加编辑变动的字节数。每个用户每5秒钟的计算结果包含在Tuple2<String, Long>
内。
剩下我们需要做的事情即打印流到控制台并执行整个程序。
result.print();
see.execute();
最后一句execute是必须的, 它会启动一个真正的flink任务。所有的操作,包括创建输入源,转换,输出源。这些会构成一张内部操作图,只有当execute()被调用,这个图上的操作才会被集群或本机执行。
完整代码如下:
package wikiedits;
import org.apache.flink.api.common.functions.FoldFunction;
import org.apache.flink.api.java.functions.KeySelector;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.connectors.wikiedits.WikipediaEditEvent;
import org.apache.flink.streaming.connectors.wikiedits.WikipediaEditsSource;
public class WikipediaAnalysis {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment see = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<WikipediaEditEvent> edits = see.addSource(new WikipediaEditsSource());
KeyedStream<WikipediaEditEvent, String> keyedEdits = edits
.keyBy(new KeySelector<WikipediaEditEvent, String>() {
@Override
public String getKey(WikipediaEditEvent event) {
return event.getUser();
}
});
DataStream<Tuple2<String, Long>> result = keyedEdits
.timeWindow(Time.seconds(5))
.fold(new Tuple2<>("", 0L), new FoldFunction<WikipediaEditEvent, Tuple2<String, Long>>() {
@Override
public Tuple2<String, Long> fold(Tuple2<String, Long> acc, WikipediaEditEvent event) {
acc.f0 = event.getUser();
acc.f1 += event.getByteDiff();
return acc;
}
});
result.print();
see.execute();
}
}
你现在可以在你的IDE或命令行中运行这个程序,使用maven如下:
$ mvn clean package
$ mvn exec:java -Dexec.mainClass=wikiedits.WikipediaAnalysis
第一行命令构建我们的工程,第二行执行我们的main函数,输出结果和下面类似:
1> (Fenix down,114)
6> (AnomieBOT,155)
8> (BD2412bot,-3690)
7> (IgnorantArmies,49)
3> (Ckh3111,69)
5> (Slade360,0)
7> (Narutolovehinata5,2195)
6> (Vuyisa2001,79)
4> (Ms Sarah Welch,269)
4> (KasparBot,-245)
每行前面的数字告诉我们是哪个并行输出源实例输出的。
从这个例子中,我们应该能学会如何启动一个flink工程。下面我们来学习一下进阶内容,如何将数据写入kafka中。
进阶:如何将数据写入Kafka
首先,需要确保我们本地已经安装并启动了flink和kafka程序。
第一步,我们需要添加Flink Kafka connector依赖,这样我们就能使用kafka输出源。在pom.xml中添加如下依赖:
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka-0.11_2.11</artifactId>
<version>${flink.version}</version>
</dependency>
下一步,我们修改我们程序,移除print()
输出源,用Kafka输出源代替,这个新的代码如下:
result
.map(new MapFunction<Tuple2<String,Long>, String>() {
@Override
public String map(Tuple2<String, Long> tuple) {
return tuple.toString();
}
})
.addSink(new FlinkKafkaProducer011<>("localhost:9092", "wiki-result", new SimpleStringSchema()));
相关的类我们需要导入:
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer011;
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.api.common.functions.MapFunction;
我们之所以将Tuple2<String, Long>转成String,是因为kafka写入String比较方便。然后我们需要添加一个kafka输出源,你可能需要根据实际配置修改你的hostname和port。“wiki-result” 是我们对输出流的命名。为了使工程能够在flink集群上运行,我们需要用maven将代码打包成jar。
$ mvn clean package
打包的jar在target子目录,我们稍后会用到它。
现在我们可以启动一个flink cluster,这样才能往kafka里面写数据。
$ cd my/flink/directory
$ bin/start-cluster.sh
我们也需要创建一个kafka topic。
$ cd my/kafka/directory
$ bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic wiki-results
现在我们可以运行我们的jar文件了。
$ cd my/flink/directory
$ bin/flink run -c wikiedits.WikipediaAnalysis path/to/wikiedits-0.1.jar
你可以看到2个独立操作启动运行,只有2个是因为在窗口结束后的的操作算一个,在flink中,我们称这个为链式。
你也可以在命令行中观察kafka消费,查看结果。
bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic wiki-result