地址
Flink 的应用程序由流式 dataflows 构成,以一个或多个源 source 开始,一个或多个汇 sink 结束。
程序代码中的 transformation 对应于 dataflows 中的算子 operator,一对一或一对多。
Flink 程序本质是分布式并行程序,一个流有一个或多个流分区,每个算子有一个或多个算子子任务。子任务彼此独立,在不同的计算资源上运行。
算子子任务数就是算子的并行度,同一程序的不同算子可能具有不同的并行度。
flink 算子通过一对一模式或者重新分发模式(重新分区)传输数据。
自定义时间流处理:通常使用记录在数据流中的事件时间的时间戳,按事件发生顺序而不是传输顺序。
有状态流处理:状态通常按键进行分片存储,存储在本地;通常将状态存储在 JVM 堆上,也可以以结构化数据格式存储在磁盘中。
容错:通过状态快照和流重放,可以提供可容错的精确一次的语义。状态快照是异步的。
Flink 可以将任何可序列化的对象转换为流,包括基本类型和 Tuples、POJOs.
Flink 自带有 Tuple0 到 Tuple25 类型。
Tuple2<String, Integer> person = Tuple2.of("Fred", 35);
String name = person.f0; // from 0
Integer age = person.f1;
POJOs 类需要满足如下条件:
一个示例:将关于人的记录流作为输入,过滤后只包含成年人。
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>org.examplegroupId>
<artifactId>pojo-testartifactId>
<version>1.0-SNAPSHOTversion>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<flink.version>1.11.0flink.version>
<java.version>1.8java.version>
<scala.binary.version>2.11scala.binary.version>
<maven.compiler.source>${java.version}maven.compiler.source>
<maven.compiler.target>${java.version}maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.apache.flinkgroupId>
<artifactId>flink-table-api-javaartifactId>
<version>${flink.version}version>
dependency>
<dependency>
<groupId>org.apache.flinkgroupId>
<artifactId>flink-table-api-java-bridge_${scala.binary.version}artifactId>
<version>${flink.version}version>
dependency>
<dependency>
<groupId>org.apache.flinkgroupId>
<artifactId>flink-clients_${scala.binary.version}artifactId>
<version>${flink.version}version>
dependency>
dependencies>
project>
Example.java
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.api.common.functions.FilterFunction;
public class Example {
public static void main(String[] args) throws Exception {
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<Person> source = env.fromElements(
new Person("Fred", 35),
new Person("Wilma", 35),
new Person("qwq", 2)
);
DataStream<Person> adults = source.filter(new FilterFunction<Person>() {
@Override
public boolean filter(Person value) throws Exception {
return value.age >= 18;
}
});
adults.print();
env.execute();
}
public static class Person {
public String name;
public Integer age;
public Person() {};
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String toString() {
return this.name.toString() + ":age " + this.age.toString();
}
}
}
环境
每个 flink 程序都需要一个执行环境,API 将应用构建为 job graph,并附加到执行环境中。
在调用 execute() 后,graph 被打包发到 JobManager 上,后者对作业并行处理并将子任务分发给 Task Manager,在 task slot 中执行。
Source
上面代码用 env.fromElements() 构建 DataStream,还可以通过 fromCollection,比如:
List<Person> people = new ArrayList<>();
people.add(new Person("a", 35));
people.add(new Person("b", 17));
DataStream<Person> source = env.fromCollection(people);
或者用 socket: env.socketTextStream("localhost", 9999)
或者读取文件:env.readTextFile("file:///path");
最常用的数据源是低延迟、高吞吐、并行读取、高容错的数据源,比如 Kafka、Kinesis、文件系统、REST 等。
Sink
上面程序将数据直接输出到屏幕上。
常用的 sink 一般是流式文件,数据库等。
git clone https://github.com/apache/flink-training.git
cd flink-training/cd ride-cleansing
时间原因略
ETL = Extract-Transform-Load,指将数据从来源端抽取、转换、加载至目的端的过程。
(教程中这部分使用了上个练习的代码,我就直接用上面那个人类了)
map 和 flatmap 两个算子可以用来实现无状态转换的基本操作。
实现一个学生类
public static class Student extends Person {
public String degree;
public Student() {};
public Student(Person person) {
this.name = person.name;
this.age = person.age;
if(person.age < 21) {
degree = "none";
} else if(person.age < 25) {
degree = "bachelor";
} else {
degree = "i don't know";
}
}
public String toString() {
return this.name.toString() +
":age " + this.age.toString() +
":degree " + this.degree.toString();
}
}
过滤后转换
List<Person> people = new ArrayList<>();
people.add(new Person("a", 35));
people.add(new Person("x", 23));
people.add(new Person("y", 20));
people.add(new Person("t", 21));
people.add(new Person("b", 17));
DataStream<Person> source = env.fromCollection(people);
DataStream<Student> stupid = source.filter(new FilterFunction<Person>() {
@Override
public boolean filter(Person value) throws Exception {
return value.age >= 18;
}
}).map(new MapFunction<Person, Student>() {
@Override
public Student map(Person value) throws Exception {
return new Student(value);
}
});
stupid.print();
试用一下牛逼的 lambda 以及函数引用。
DataStream<Student> stupid = source.filter(
(FilterFunction<Person>) value -> value.age >= 18
).map(
(MapFunction<Person, Student>) Student::new
);
map 适用于一对一的转换,flatmap 适用于一对若干的转换。
DataStream<Student> stupid = source.flatMap(
new FlatMapFunction<Person, Student>() {
@Override
public void flatMap(Person value, Collector<Student> out) throws Exception {
if(value.age >= 18) {
out.collect(new Student(value));
}
}
});
这一步似乎不能换成 lambda,因为需要 Collector 的明确类型。
keyBy 算子可以对流进行分区,总体来说这个开销比较大。
可以直接将属性名字作为分区键。
source.keyBy("age");
这样选择键会导致编译器无法推断键字段的类型,建议选择一个合适的 KeySelector.
source = source.keyBy(new KeySelector<Person, Integer>() {
@Override
public Integer getKey(Person value) throws Exception {
return value.age;
}
});
//lambda
source = source.keyBy(value -> value.age);
通过计算方式产生键,计算方法必须是确定的。
source = source.keyBy(value -> value.age/2);
maxby 函数,针对分区流使用,将每一个对象变成历史的最大值
List<Person> people = new ArrayList<>();
people.add(new Person("a", 1));
people.add(new Person("x", 5));
people.add(new Person("y", 4));
people.add(new Person("t", 2));
people.add(new Person("b", 3));
people.add(new Person("b", 6));
DataStream<Person> source = env.fromCollection(people);
source.keyBy(value -> value.age/100).maxBy("age").print();
输出结果如下(注意这里其实就分了一个区)
6> a:age 1
6> x:age 5
6> x:age 5
6> x:age 5
6> x:age 5
6> b:age 6
状态
这里的虽然没有被用户感知,但流已经开始保存状态了。
只要应用中有状态,用户就应当考虑状态大小。
在这个例子中,状态所需空间和键数量成正比。
最后:reduce() 函数可以实现自定义聚合。
Flink 状态特性:
Rich Functions
Flink 的几种函数接口:FliterFunction, MapFunction, FlatMapFunction 只有单一的抽象方法。
为了增强功能性,提供了 Rich 变体,如 RichMapFunction,提供了以下方法
这里举了个例子,使用 ValueState 实现了一个接口,由于在 Section 1 中做过,就略了,qwq.
同样略!
笔记先到这里,剩下的晚上或者周末做,抓紧时间尝试构建了。