【笔记】Flink 官方教程 Section2 Learn Flink

地址

Section 2 Learn Flink

2.1 概览

2.1.1 教程目标

  • 如何实现流数据处理管道(pipelines)
  • Flink 如何管理状态以及为何需要管理状态
  • 如何使用事件时间(event time)来一致并准确地进行计算分析
  • 如何在源源不断的数据流上构建事件驱动的应用程序
  • Flink 如何提供具有精确一次(exactly-once)计算语义的可容错、有状态流处理

2.1.2 流处理

Flink 的应用程序由流式 dataflows 构成,以一个或多个源 source 开始,一个或多个汇 sink 结束。

程序代码中的 transformation 对应于 dataflows 中的算子 operator,一对一或一对多。

Flink 程序本质是分布式并行程序,一个流有一个或多个流分区,每个算子有一个或多个算子子任务。子任务彼此独立,在不同的计算资源上运行。

算子子任务数就是算子的并行度,同一程序的不同算子可能具有不同的并行度。

flink 算子通过一对一模式或者重新分发模式(重新分区)传输数据。

自定义时间流处理:通常使用记录在数据流中的事件时间的时间戳,按事件发生顺序而不是传输顺序。

有状态流处理:状态通常按键进行分片存储,存储在本地;通常将状态存储在 JVM 堆上,也可以以结构化数据格式存储在磁盘中。

容错:通过状态快照和流重放,可以提供可容错的精确一次的语义。状态快照是异步的。

2.2 DataStream API 简介

2.2.1 转换成流

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 类需要满足如下条件:

  1. public 且独立(没有非静态内部类)
  2. 有 public 无参构造函数
  3. 类及父类中所有不被 static/transient 修饰的属性要么是 public (且不被 final 修饰),要么包含 public getter/setter.

2.2.2 示例

一个示例:将关于人的记录流作为输入,过滤后只包含成年人。

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();
        }
    }
}

2.2.3 代码分析

环境
每个 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 一般是流式文件,数据库等。

2.2.4 练习:Filtering a Stream (Ride Cleansing)

git clone https://github.com/apache/flink-training.git
cd flink-training/cd ride-cleansing

时间原因略

2.3 数据管道 & ETL

ETL = Extract-Transform-Load,指将数据从来源端抽取、转换、加载至目的端的过程。

(教程中这部分使用了上个练习的代码,我就直接用上面那个人类了)

2.3.1 无状态转换

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 的明确类型。

2.3.2 按 key 分区

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() 函数可以实现自定义聚合。

2.3.3 有状态的转换

Flink 状态特性:

  1. 本地性:存储在机器本地
  2. 持久性:容错
  3. 纵向可扩展性:可以存储在集成的 RocksDB 实例中,增加本地磁盘来扩展空间
  4. 横向可扩展性:可以随着集群扩缩容重新分布
  5. 可查询性:可以通过状态查询 API 从外部查询,牛!

Rich Functions
Flink 的几种函数接口:FliterFunction, MapFunction, FlatMapFunction 只有单一的抽象方法。

为了增强功能性,提供了 Rich 变体,如 RichMapFunction,提供了以下方法

  • open,算子初始化时调用一次,可以加载静态数据,或者建立外部服务链接等。
  • close
  • getRuntimeContext,是创建和访问 flink 状态的途径

这里举了个例子,使用 ValueState 实现了一个接口,由于在 Section 1 中做过,就略了,qwq.

2.3.4 控制流

同样略!


笔记先到这里,剩下的晚上或者周末做,抓紧时间尝试构建了。

你可能感兴趣的:(Flink,探索,学习笔记)