一文入门Flink

本文介绍了Apache Flink的基础知识,包括流处理概念、Flink作业部署选项、提交模式、架构、核心组件如source、sink、window、watermark以及Exactly-once语义。Flink作为一个分布式流计算引擎,支持批处理和流处理,提供了丰富的窗口机制和高可用的容错策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

图片

数据流分为有界数据流和无界数据流,它们的区别是有没有明确的结束点。

有界数据流有结束点,对有界数据流的处理称为批处理,特点是在处理之前就可以拿到所有的数据。

无界数据流没有结束点,数据是源源不断的,对数据的处理将会是持续性的。

flink是一个分布式流计算引擎,需要结合计算资源才能执行计算作业。

flink作业部署

如果是上手体验,可以在本地运行测试。如果是生产使用,有三种选择:

(1)在虚拟机上运行,部署多个 JobManager 备实例做 HA,通过 zookeeper 做故障感知和切换。

(2)与 Yarn、Mesos 等资源管理器集成部署,或者使用 Docker、Kubernetes 部署在容器环境。

(3)直接使用云产商提供的云计算服务,例如 AliCloud Realtime Compute、Amazon Kinesis Data Analytics For Java、Huawei Cloud Stream Service 等,可以开箱即用,聚焦业务开发,无需关注运维。

flink作业提交

图片

根据集群的生命周期及资源隔离程度、应用程序的 main() 方法是执行在 JobManager 还是 client,作业提交模式有三种:

会话模式: 一个 JobManager 实例管理多个作业,多个作业共享同一套 TaskManager 集群。这个模式考虑到出现故障时可能互相影响,所以并不常用,但也有它的优势,因为是共享 JobManager,所以不需要为每个提交的作业初始化一个新的 JobManager,比较适合对启动时间比较敏感的短作业。

作业模式: 每个作业独享 JobManager,作业的 main() 方法运行在 client(集群创建之前)。优点是资源隔离、故障隔离,但由于 main() 方法的执行是在 client 上,而 main() 方法主要就是做一些作业提交前的准备工作,包括下载依赖 jar 这种比较消耗带宽的操作、生成 dataflow graft 这种比较消耗cpu 的操作,如果在一个中心化的部署环境,也就是一台机器上同时运行多个 client,就会导致带宽和 cpu 热点问题。

应用模式:每个作业独享 JobManager,作业的 main() 方法在 JobManager 上执行。这种模式就解决了作业模式可能导致带宽和cpu热点的问题,同时资源和故障也都是隔离的。

当部署一个 flink 作业时,flink 会根据作业设置的并行度,向资源管理器申请资源。

一个被提交的任务会被拆分成很多个子任务,并行地在集群里执行。

当某个节点发生故障时,flink会申请新的资源替换发生故障的节点,保证任务能够继续执行。

flink架构

Flink 集群是一种主从架构,由一个 JobManager 和多个 TaskManager 组成。

Client 并不属于集群运行时的一部分,它只负责准备和发送 dataflow graph 给 JobManager。

JobManager 是 flink 集群的大脑,它主要负责协调多个 TaskManager 作业执行,同时也会负责协调 checkpoint 和故障恢复等等。

TaskManager 才是真正的 Worker,它负责执行 flink 作业。

flink 能同时支持流处理和批处理,它认为批是流的特例,这个认知模型是它能做到“实时”计算的重要基础。

相对批处理的应用场景,flink 被使用最多的还是流处理,所以接下来就从流处理这条主线来认识 flink。

先看一段 flink 官方提供的示例程序,这段程序用来统计一段英文句子里每个单词出现的次数。

public class WindowWordCount {

    public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        DataStream<Tuple2<String, Integer>> dataStream = env
                .socketTextStream("localhost", 9999)
                .flatMap(new Splitter())
                .keyBy(value -> value.f0)
                .window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
                .sum(1);

        dataStream.print();

        env.execute("Window WordCount");
    }
    
    public static class Splitter implements FlatMapFunction<String, Tuple2<String, Integer>> {
        @Override
        public void flatMap(String sentence, Collector<Tuple2<String, Integer>> out) throws Exception {
            for (String word : sentence.split(" ")) {
        out.collect(new Tuple2<String, Integer>(word, 1));
            }
        }
    }
}

主要逻辑是从 socket(source) 里读取数据流事件,先对事件使用 flatmap 算子(operators)将句子切割成单词,然后以单词为 key 进行分区,再对每个分区应用5s的滚动窗口(windows)进行 sum 算子(operators)累加,最后将结果输出(sink)打印出来。

其中涉及到 source、sink、windows、operators 等概念,逐一说明。

source和sink

source 指数据的输入,sink 指数据的输出

flink 原生提供了一些基本的数据源接口,包括从 file、socket 读取或者写入数据,也支持自定义 source/sink,一般不会自己定制,flink 官方和 Apache Bahir 已经提供了一些常用的数据源连接器(connectors)可以拿过来白嫖。

windows 

在无界数据流的场景,流是没有结束点的,所以就需要窗口将流切分成有限大小的块方便计算。

图片

flink 里的窗口类型有4种:时间窗口、计数窗口、会话窗口、全局窗口,其中

时间窗口和计数窗口又分为滑动窗口和滚动窗口两种,滑动窗口和滚动窗口的主要区别在于相邻的两个窗口是否首尾相接。

对于滑动来说,如果窗口大小大于滑动步长,那么相邻的两个窗口就会有重合,如果窗口大小小于滑动步长相邻的两个窗口就会有间隙,如果窗口大小等于滑动步长,则效果与滚动窗口一致。

那么,每种窗口有什么特点?

时间窗口的窗口大小是以时间为单位。

Time

flink 里的时间有3种。

图片

接入时间:事件被 flink 接收的时间

处理时间:具体到某一个算子处理事件的时间

事件时间:事件发生的时间

其中,事件时间更加符合一般业务要求,所以比较常用。

计数窗口的窗口大小是以数量为单位。

会话窗口稍微特殊一点,可以指定两个会话的时间间隔,如果达到了这个时间间隔还没有新事件到达,就会关闭窗口,之后如果再有一个事件过来,就会分配一个新的窗口。

全局窗口默认没有触发时机,是一个比较底层的窗口,前面说的各种窗口都是在它的基础上添加不同特征的触发器实现的。

说白了,窗口就是无限流的切割器。  

当然,窗口切割本身没有意义,真正产生价值的是窗口计算,每个窗口有对应的触发计算时机。

简单来说,计数窗口达到指定数量、时间窗口达到指定时间、会话窗口达到指定会话间隔后会触发计算。

Watermark

涉及到基于事件时间处理的时候,可能因为各种原因,事件到达的顺序是不确定的,那什么时候触发窗口计算,对于晚到的事件是直接丢弃还是再等等就是一个要面对的问题。

flink 引入了 watermark 机制来尽可能保证在这种无序事件情况,尽可能多的事件能够参与到计算中

watermark 实际上就是一种特殊的事件,带着一个时间戳,用来暗示在某个时间点之前的所有事件都已经到达。

图片

就像图里这样,W(11) 表示11这个时间点之前的所有事件都已经到达了,也就是大概率不会再有11这个时间点之前的事件会过来。

实际情况比上面要复杂,因为会有很多个算子,所以每个算子都需要维护自己的 watermark

图片

DataStream 与 Operators

DataStream 有点类似 java 里的数据集合,与 java collection 不同的是,DataStream 一旦被创建,不能再增加和删除里面的元素,只能使用 DataStream 暴露的 api 进行操作,这些操作在 flink 里叫做转换(Transformations),如下图所示:

图片

每种转换操作都需要由对应的算子(Operators)来完成。

算子有很多类型,大体可分为:

单数据处理算子:如 map()、fliter() 等操作,

window 操作算子:如 window()、windowAll() 等操作,

多流合并算子:如 join(DataStream)、coGroup(DataStream) 等操作,

单流切分算子:如 split()、sideOutput() 操作;

State

这些算子有些是有状态的,比如 window 算子,在窗口触发之前需要缓存一段时间的数据,那这些数据就需要作为状态保存下来。

再如聚合算子,聚合结果需要依赖中间状态,所以聚合操作就需要维护中间状态。

也有一些算子是无状态的,比如 map 算子,map 操作只对当前的数据进行处理,这个操作是无状态的计算。

在 flink 中,状态始终与特定算子相关联。在 flink 里,托管类型的状态有2种:

  • 算子状态(operator state),算子状态的作用范围限定为算子任务,一个任务一个状态;

  • 分区状态(keyed state),根据输入数据流中定义的 key 来维护和访问状态(对于 KeyedStream 上的任务状态,不同 key 的状态是独立维护的,每个 key 只能访问它自己的状态,不同 key 之间不能互相访问);

flink 会将状态保存到状态后端,状态后端有很多种,flink 内置支持将状态保存在内存或 RocksDB 中,RocksDB 是一种高性能的磁盘数据存储。也可以将状态保存在分布式文件系统如 HDFS 中,以保证状态后端的高可用。

Checkpoint

flink 在做故障恢复时依靠的就是这些状态,各个算子组成了一个分布式的运行环境,如果记录的只是各个算子独自的状态,那在状态恢复时这个状态将是不一致的,所以如何在一个分布式的环境下保证状态的一致性就是一个flink要面对的问题。

flink 参考 Chandy-Lamport 分布式快照算法的思想设计了自己的解决方案,大概思想是,首先 JobManager 协调 TaskMananger 发起 checkpoint,从 source 端发射一个 checkpoint barrier(和watermark类似,也是一种特殊事件)给下游算子,下游算子接收到这个 barrier 就会对自身状态进行记录,随着这个 barrier 的流动,正常情况下,所有算子都会接收到这个 barrier 并记录自身状态,于是一个全局的快照就生成了,在故障恢复时只需要找到最近一次成功记录的快照将数据恢复即可。

根据算子在接收到 barrier 时处理策略上的不同,有两种具体实现。

第一种是对齐式的 checkpoint

图片

很多算子的输入流会有多条,在其中一条输入流接收到这个 barrier 时,当前这个输入流会暂停处理,直到其它输入流的 barrier 全都到达了,这时候对状态进行记录,再继续往下执行。

这很明显是一种阻塞式的等待,在一些极端场景下对计算性能的影响是巨大的。

于是 flink 就设计了第二种非对齐式的 checkpoint

图片

这种就是说当第一个输入流里的 barrier 到达的时候,立马把算子的状态和正在流动(属于当前快照版本)的数据一起记录了,恢复的时候再把这些数据一起恢复,这样就不用阻塞着等待其它输入流的barrier了。

本质就是用 io 换时间,如何说计算任务本身就是 io 密集型的,使用这种非对齐的 checkpoint 只会加重 io 压力,甚至带来问题。

Exactly-once

checkpoint 就是 flink 的容错机制。也是 flink 能做到 Exactly-once 的基础。

Exactly-once 是个很容易让人误解的概念,有必要强调下,Exactly-once 的语义并不是保证每条数据只被处理了一次,而是确保一条数据对最终结果只会影响一次。

那要做到 Exactly-once,实际上是需要数据源具有回放的能力,比如 kafka 的 offset 就是一种能够做到回放数据的机制,offeset 会在 checkpoint 时一起记录到快照数据里,以便故障恢复时对数据进行回放。

再严格点,要做到端到端的 Exactly-once 还需要数据的 sink 也能参与到 checkpoint 流程中,还是以 kafka 为例,kafka 的生产消息的事务机制就可以提供这个保证。

checkpoint是一种 flink 内部的自动容错机制,那想要手工触发生成快照也是可以的,这个是savepoint。

对比 checkpoint 一个明显的区别是,savepoint 只能手动触发和清理,而 checkpoint 则是 flink 自动管理的。

END

核心基本概念就这些了,还有一些东西限于篇幅,比如数据倾斜、背压等问题,以后有机会再说,不影响了解 flink。更多细节可参考官网文档。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值