深入理解Flink

367 阅读7分钟

Flink 是一个框架和分布式处理引擎,用于在无边界和有边界数据流上进行有状态的计算。Flink 自底向上在不同的抽象级别提供了多种 API,并且针对常见的使用场景开发了专用的扩展库。

1. Flink 特性

1.1 无界和有界数据 

任何类型的数据都可以形成一种事件流。信用卡交易、传感器测量、机器日志、网站或移动应用程序上的用户交互记录,所有这些数据都形成一种流。

数据可以被作为 无界 或者 有界 流来处理。

  1. 无界流 有定义流的开始,但没有定义流的结束。它们会无休止地产生数据。无界流的数据必须持续处理,即数据被摄取后需要立刻处理。我们不能等到所有数据都到达再处理,因为输入是无限的,在任何时候输入都不会完成。处理无界数据通常要求以特定顺序摄取事件,例如事件发生的顺序,以便能够推断结果的完整性。
  2. 有界流 有定义流的开始,也有定义流的结束,通常被称为批处理。

image.png

1.2 充分利用内存性能

有状态的 Flink 程序针对本地状态访问进行了优化。任务的状态始终保留在内存中,如果状态大小超过可用内存,则会保存在能高效访问的磁盘数据结构中。任务通过访问本地(通常在内存中)状态来进行所有的计算,从而产生非常低的处理延迟。Flink 通过定期和异步地对本地状态进行持久化存储来保证故障场景下精确一次的状态一致性。

1.3 应用状态

每一个具有一定复杂度的流处理应用都是有状态的。任何运行基本业务逻辑的流处理应用都需要在一定时间内存储所接收的事件或中间结果,以供后续的某个时间点(例如收到下一个事件或者经过一段特定时间)进行访问并进行后续处理。

应用状态是 Flink 中的一等公民,Flink 提供了许多状态管理相关的特性支持,其中包括:

  • 多种状态基础类型:Flink 为多种不同的数据结构提供了相对应的状态基础类型,例如原子值(value),列表(list)以及映射(map)。开发者可以基于处理函数对状态的访问方式,选择最高效、最适合的状态基础类型。
  • 插件化的State Backend:State Backend 负责管理应用程序状态,并在需要的时候进行 checkpoint。Flink 支持多种 state backend,可以将状态存在内存或者内存数据库。RocksDB 是一种高效的嵌入式、持久化键值存储引擎。Flink 也支持插件式的自定义 state backend 进行状态存储。
  • 精确一次语义:Flink 的 checkpoint 和故障恢复算法保证了故障发生后应用状态的一致性。因此,Flink 能够在应用程序发生故障时,对应用程序透明,不造成正确性的影响。
  • 超大数据量状态:Flink 能够利用其异步以及增量式的 checkpoint 算法,存储数 TB 级别的应用状态。
  • 可弹性伸缩的应用:Flink 能够通过在更多或更少的工作节点上对状态进行重新分布,支持有状态应用的分布式的横向伸缩。

1.4 时间语义支持 

时间是流处理应用另一个重要的组成部分。因为事件总是在特定时间点发生,所以大多数的事件流都拥有事件本身所固有的时间语义。进一步而言,许多常见的流计算都基于时间语义,例如窗口聚合、会话计算、模式检测和基于时间的 join。流处理的一个重要方面是应用程序如何衡量时间,即区分事件时间(event-time)和处理时间(processing-time)。

Flink 提供了丰富的时间语义支持。

  • 事件时间模式:使用事件时间语义的流处理应用根据事件本身自带的时间戳进行结果的计算。因此,无论处理的是历史记录的事件还是实时的事件,事件时间模式的处理总能保证结果的准确性和一致性。
  • Watermark 支持:Flink 引入了 watermark 的概念,用以衡量事件时间进展。Watermark 也是一种平衡处理延时和完整性的灵活机制。
  • 迟到数据处理:当以带有 watermark 的事件时间模式处理数据流时,在计算完成之后仍会有相关数据到达。这样的事件被称为迟到事件。Flink 提供了多种处理迟到数据的选项,例如将这些数据重定向到旁路输出(side output)或者更新之前完成计算的结果。
  • 处理时间模式:除了事件时间模式,Flink 还支持处理时间语义。处理时间模式根据处理引擎的机器时钟触发计算,一般适用于有着严格的低延迟需求,并且能够容忍近似结果的流处理应用。

1.5 分层 API 

Flink 根据抽象程度分层,提供了三种不同的 API。每一种 API 在简洁性和表达力上有着不同的侧重,并且针对不同的应用场景。

3. Flink 实时数据处理

接下来,我们将通过一个简单的示例,展示如何使用 Flink 构建一个实时数据处理应用程序。在这个示例中,我们将分析网络上的用户点击事件,统计每个用户的点击量,并输出结果。

3.1 定义数据结构

首先,我们需要定义用户点击事件的数据结构。这里我们使用 Java 作为编程语言。

public class ClickEvent {
    private String userId;
    private String itemId;
    private long timestamp;

    // 构造函数、getters 和 setters 省略
}

3.2 创建 Flink 应用程序

接下来,我们需要创建一个 Flink 应用程序,并定义数据源、操作符和数据接收器。

import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;

public class ClickEventCount {
    public static void main(String[] args) throws Exception {
        // 1. 创建一个 Flink 执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // 2. 定义数据源,这里我们假设从一个 Kafka topic 中读取数据
        // 生产环境中,您需要使用 Flink Kafka Connector 连接到 Kafka
        DataStream<String> inputStream = env.fromElements(
                "user1,item1,1619023921000",
                "user2,item2,1619023924000",
                "user1,item3,1619023926000",
                "user2,item4,1619023928000",
                "user1,item5,1619023930000"
        );

        // 3. 定义操作符
        DataStream<ClickEvent> clickStream = inputStream.map(new MapFunction<String, ClickEvent>() {
            @Override
            public ClickEvent map(String value) throws Exception {
                String[] parts = value.split(",");
                return new ClickEvent(parts[0], parts[1], Long.parseLong(parts[2]));
            }
        });

        DataStream<Tuple2<String, Integer>> result = clickStream
                .keyBy(ClickEvent::getUserId)
                .window(TumblingProcessingTimeWindows.of(Time.seconds(10)))
                .sum("clickCount");

        // 4. 定义数据接收器,这里我们将结果打印到控制台
        result.print();

        // 5. 启动 Flink 应用程序
        env.execute("ClickEventCount");
    }
}

这个示例中,我们首先创建一个 Flink 执行环境,然后定义数据源(从 Kafka 读取数据)。接着,我们使用 map 操作符将输入的字符串转换为 ClickEvent 对象。

在转换后的数据流上,我们使用 keyBy 函数按用户分组,然后使用窗口操作符定义一个10秒的滚动窗口。在每个窗口中,我们计算每个用户的点击量。最后,我们将结果打印到控制台。

4. 部署和运行 Flink 应用程序

要部署和运行 Flink 应用程序,您需要将其打包成一个 JAR 文件,并将其提交到 Flink 集群。您可以使用以下命令将应用程序打包成 JAR 文件:

mvn clean package

接下来,您可以使用 Flink 提供的脚本 flink 提交应用程序到集群:

./bin/flink run -c com.example.ClickEventCount target/clickeventcount-1.0-SNAPSHOT.jar

这将启动应用程序并将其提交到 Flink 集群。应用程序运行期间,您可以通过 Flink Web UI 监控应用程序的状态和性能。

4. 总结

本文介绍了 Flink 的核心概念,并通过一个简单的示例展示了如何使用 Flink 构建实时数据处理应用程序。