核心概念
DataSet and DataStream
Flink具有特殊类DataSet并DataStream在程序中表示数据。您可以将它们视为可以包含重复项的不可变数据集合。在DataSet数据有界的情况下(批处理),对于一个DataStream元素的数量可以是无界的(流处理)。
这些集合(数据)在某些关键方面与常规Java集合不同。首先,它们是不可变的,这意味着一旦创建它们就无法添加或删除元素。你也不能简单地检查里面的元素。
集合最初通过source添加源创建和新的集合从这些通过将它们使用API方法如衍生map,filter等等。
flink编程模型
Flink程序看起来像是转换数据集合的常规程序。每个程序包含相同的基本部分:
- 获得一个execution environment
- 加载/创建初始数据
- 指定此数据的转换
- 指定放置计算结果的位置
- 触发程序执行
延迟执行
所有Flink程序都是懒惰地执行:当执行程序的main方法时,数据加载和转换不会直接发生。而是创建每个操作并将其添加到程序的计划中。当execute()执行环境上的调用显式触发执行时,实际执行操作。程序是在本地执行还是在集群上执行取决于执行环境的类型
延迟执行使您可以构建Flink作为一个整体计划单元执行的复杂程序。
指定key
某些转换(join,coGroup,keyBy,groupBy)要求在元素集合上定义key。其他转换(Reduce,GroupReduce,Aggregate,Windows)允许数据在应用之前在key上分组。
DataSet指定key
DataSet<...> input = // [...]
DataSet<...> reduced = input
.groupBy(/*define key here*/)
.reduceGroup(/*do something*/);
使用DataStream指定key
DataStream<...> input = // [...]
DataStream<...> windowed = input
.keyBy(/*define key here*/)
.window(/*window specification*/);
Flink的数据模型不基于键值对。因此,您无需将数据集类型物理打包到键和值中。键是“虚拟的”:它们被定义为作用在实际数据上的函数通过group操作符。
注意:在下面的讨论中,我们将使用DataStreamAPI和keyBy。对于DataSet API,您只需要替换为DataSet和groupBy。
为元组指定key
最简单的情况是在元组的一个或多个字段上对元组进行分组:
//元组在第一个字段(整数类型)上分组。
val input: DataStream[(Int, String, Long)] = // [...]
val keyed = input.keyBy(0)
//在这里,我们将元组分组在由第一个和第二个字段组成的复合键上。
val input: DataSet[(Int, String, Long)] = // [...]
val grouped = input.groupBy(0,1)
//关于嵌套元组的注释:如果你有一个带有嵌套元组的DataStream,例如:
DataStream<Tuple3<Tuple2<Integer, Float>,String,Long>> ds;
//指定keyBy(0)将导致系统使用full Tuple2作为键(以Integer和Float为键)。如果要指定到嵌套中Tuple2种的键,则必须使用下面解释的字段表达式键。
使用Field Expressions指定key
您可以使用基于字符串的字段表达式来引用嵌套字段,并定义用于 grouping, sorting, joining或coGrouping的键。
字段表达式可以非常轻松地选择(嵌套)复合类型中的字段,例如Tuple和POJO类型。
在下面的示例中,我们有一个WCPOJO,其中包含两个字段“word”和“count”。要按字段分组word,我们只需将其名称传递给keyBy()函数即可。
// some ordinary POJO (Plain old Java Object)
class WC(var word: String, var count: Int) {
def this() { this("", 0L) }
}
val words: DataStream[WC] = // [...]
val wordCounts = words.keyBy("word").window(/*window specification*/)
// or, as a case class, which is less typing
case class WC(word: String, count: Int)
val words: DataStream[WC] = // [...]
val wordCounts = words.keyBy("word").window(/*window specification*/)
具体java代码案例
package com.kun.flink.java.chapter03;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.util.Collector;
/**
* 使用java api来开发Flink的实时处理应用程序
*
* wc统计的数据源自socket
*/
public class StreamingWCJavaAPP {
public static void main(String[] args) throws Exception {
//step1:获取执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
//step2:读取数据
DataStreamSource<String> text = env.socketTextStream("hadoop",9999);
//step3:transform
//每一行数据按照指定的分隔符拆分
text.flatMap(new FlatMapFunction<String, WC>() {
@Override
public void flatMap(String s, Collector<WC> collector) throws Exception {
String[] strings = s.toLowerCase().split(",");
for(String string:strings){
if(string.length()>0){
//为每个单词附上次数1
collector.collect(new WC(string,1));
}
}
}
})
//合并操作
.keyBy("word")
.timeWindow(Time.seconds(5))
.sum("count")
.print()
;
env.execute("StreamingWCJavaAPP");
}
public static class WC{
private String word;
private int count;
public WC(){}
public WC(String word, int count) {
this.word=word;
this.count=count;
}
@Override
public String toString() {
return "WC{" +
"word='" + word + '\'' +
", count=" + count +
'}';
}
public String getWord() {
return word;
}
public void setWord(String word) {
this.word = word;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
}
测试输入
结果
2> WC{word='b', count=1}
6> WC{word='a', count=3}
1> WC{word=', count=1}
4> WC{word='c', count=2}
具体scala代码案例
package com.kun.flink.scala.chapter02
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.api.windowing.time.Time
object StreamingWCScalaAPP {
def main(args: Array[String]): Unit = {
val env = StreamExecutionEnvironment.getExecutionEnvironment
val text=env.socketTextStream("hadoop",9999)
import org.apache.flink.streaming.api.scala._
text.flatMap(_.split(","))
.map(x=>WC(x,1))
.keyBy("word")
.timeWindow(Time.seconds(5))
.sum("count").print()
env.execute("StreamingWCScalaAPP")
}
case class WC(word: String, count: Int)
}
测试输入
结果
7> WC(ccc,1)
6> WC(a,5)
2> WC(b,1)
使用键选择器功能指定key
java小代码
// some ordinary POJO
public class WC {public String word; public int count;}
DataStream<WC> words = // [...]
KeyedStream<WC> keyed = words
.keyBy(new KeySelector<WC, String>() {
public String getKey(WC wc) { return wc.word; }
});
scala小代码
// some ordinary case class
case class WC(word: String, count: Int)
val words: DataStream[WC] = // [...]
val keyed = words.keyBy( _.word )
指定 Transformation Functions
1、实现一个function(java)
class MyMapFunction implements MapFunction<String, Integer> {
public Integer map(String value) { return Integer.parseInt(value); }
};
//自己实现new MyMapFunction()这个接口
data.map(new MyMapFunction());
2、匿名类(java)
可以直接new出来这个function
data.map(new MapFunction<String, Integer> () {
public Integer map(String value) { return Integer.parseInt(value); }
});
3、使用Lambdas表达式(java和scala)
4、Rich functions(java和scala)
支持的数据类型
- Java Tuples and Scala Case Classes
- Java POJOs
- Primitive Types
- Regular Classes
- Values
- Hadoop Writables
- Special Types