Flink 期末重点知识点手册
依据:老师划重点 md + 第 1~8 章 PPT 整理。
本手册按"会选择、会辨析、会填空、会看代码、会补全代码"的标准编写,不按 PPT 页码机械复述。重点放在老师明确划出的概念、区别、代码骨架和易错点。
一、总体框架:先建立全课程地图
1. Flink 是什么
Flink 的核心目标是:数据流上的有状态计算(Stateful Computations over Data Streams)。
可以这样理解:
- Flink 是一个用于处理有界流 和无界流的分布式处理框架与引擎。
- 它既能做实时流处理,也能做批处理。
- 流处理时,程序往往不仅看"当前这一条数据",还要结合此前保存的统计结果、窗口数据、偏移量等信息;这些额外保存的信息就是状态(State)。
2. 有界流与无界流
| 类型 | 开始与结束 | 处理特点 | 常见对应 |
|---|---|---|---|
| 有界流 Bounded Stream | 有开始,也有结束 | 可以等所有数据到齐后统一计算、排序 | 批处理 |
| 无界流 Unbounded Stream | 有开始,没有结束 | 数据持续到来,不能等数据"全部到齐" | 实时流处理 |
易错点:
- 无界流不是"数据很多",而是理论上没有结束。
- 批处理不代表不能用 DataStream API。Flink 1.12 之后,推荐统一使用 DataStream API ,通过设置运行模式为
BATCH来完成批处理。
3. Flink 的主要特点
重点记忆:
- 高吞吐、低延迟;
- 支持事件时间与处理时间;
- 支持 Exactly-Once 状态一致性;
- 可连接 Kafka、Hive、JDBC、HDFS、Redis 等外部系统;
- 支持故障恢复、动态扩缩容和高可用部署。
4. Flink 与 Spark Streaming 的主要区别
| 项目 | Spark Streaming | Flink |
|---|---|---|
| 根本处理模式 | 批处理 | 流处理 |
| 数据处理方式 | 微批次 | 逐事件流式处理 |
| 运行结构 | DAG 划分为多个 Stage,前一阶段完成后再进行下一阶段 | 事件在一个节点处理后可直接流向下游节点 |
| 数据模型 | RDD / DStream | 数据流与事件序列 |
一句话记忆:
Spark Streaming 本质上偏"微批",Flink 本质上是标准的"逐事件流处理"。
5. Flink 四层 API
从高到低:
text
SQL
Table API
DataStream / DataSet API
有状态流处理 / Process Function
| 层级 | 特点 |
|---|---|
| SQL | 最高层,使用 SQL 语句表达计算逻辑 |
| Table API | 面向表的声明式 API |
| DataStream API | Flink 最核心的流处理 API |
| 有状态流处理 / Process Function | 最底层、最灵活,可实现复杂业务逻辑 |
考试易答:
- 最高层:SQL
- DataStream API:核心 API
- 最底层:有状态流处理 / 处理函数
- DataSet API:Flink 1.12 后已不推荐使用,DataStream API 可以统一处理流和批。
第 2 章 Flink 快速上手:重点是 WordCount 代码
老师重点文档说明:第二章概念不多,但 WordCountStreamDemo 必须会写 ,尤其是 keyBy() 中接口对象、匿名内部类和 Lambda 的写法。
1. 流式 WordCount 的处理流程
text
读取数据
→ flatMap 按空格拆单词
→ 每个单词变为 (word, 1)
→ keyBy 按单词分组
→ sum 对次数累加
→ print 输出
→ execute 触发执行
典型逻辑:
text
hello flink
hello world
会变成:
text
(hello, 1)
(flink, 1)
(hello, 1)
(world, 1)
再按单词分组并求和:
text
(hello, 2)
(flink, 1)
(world, 1)
2. 必须记住的 Stream WordCount 骨架
java
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
DataStreamSource<String> lines =
env.socketTextStream("hadoop102", 7777);
SingleOutputStreamOperator<Tuple2<String, Integer>> wordAndOne = lines
.flatMap((String line, Collector<Tuple2<String, Integer>> out) -> {
String[] words = line.split(" ");
for (String word : words) {
out.collect(Tuple2.of(word, 1));
}
})
.returns(Types.TUPLE(Types.STRING, Types.INT));
SingleOutputStreamOperator<Tuple2<String, Integer>> result = wordAndOne
.keyBy(value -> value.f0)
.sum(1);
result.print();
env.execute();
3. keyBy() 的三种常见写法
假设流中元素类型为:
java
Tuple2<String, Integer>
写法一:Lambda 表达式
最常用:
java
.keyBy(value -> value.f0)
含义:取元组的第一个字段 f0 作为分组键。
写法二:匿名内部类
java
.keyBy(new KeySelector<Tuple2<String, Integer>, String>() {
@Override
public String getKey(Tuple2<String, Integer> value) {
return value.f0;
}
})
含义与 Lambda 完全一致,只是写法更完整。
写法三:单独定义 KeySelector 函数类
java
public class MyKeySelector
implements KeySelector<Tuple2<String, Integer>, String> {
@Override
public String getKey(Tuple2<String, Integer> value) {
return value.f0;
}
}
使用:
java
.keyBy(new MyKeySelector())
4. 为什么 flatMap 后有时需要 .returns()?
Java 有泛型擦除 问题。对于某些 Lambda 表达式,Flink 只能推断出结果是 Tuple2,但无法准确识别其完整泛型类型,例如:
java
Tuple2<String, Integer>
因此需要手动补充类型信息:
java
.returns(Types.TUPLE(Types.STRING, Types.INT))
考试中如果问原因,可以答:
因为 Java 泛型擦除会导致 Flink 对 Lambda 返回类型推断不完整,需要显式指定类型信息,以便正确完成序列化和反序列化。
第 3 章 Flink 部署:三种部署模式必须会比较
老师特别强调:三种部署模式必须知道,且要能说出区别。
1. Standalone 模式
含义
Standalone 是 Flink 独立运行模式,不依赖 YARN、Kubernetes 等外部资源管理平台。
特点
- Flink 自己运行 JobManager 和 TaskManager;
- 不依赖外部资源调度平台;
- 资源不足、节点故障时,不具备自动扩缩容和自动资源重分配保证;
- 多数情况下需要人工处理。
适用场景
- 本地开发;
- 功能测试;
- 作业较少、资源需求较简单的环境。
易错判断
Standalone 并不是不能运行生产作业,而是资源调度和故障处理能力相对弱,因此通常更适合开发测试或简单场景。
2. YARN 模式的基本运行过程
YARN 模式下:
text
Client 提交 Flink 应用
→ YARN ResourceManager 申请资源
→ NodeManager 分配 Container
→ Container 中启动 JobManager 和 TaskManager
→ Flink 根据作业所需 Slot 动态申请 TaskManager 资源
注意区分:
- YARN ResourceManager:YARN 平台的资源管理器;
- Flink ResourceManager:Flink 内部组件,负责管理 Flink 的 Slot。
二者名字相同,但不是同一个组件。
3. 三种部署模式对比
| 对比点 | 会话模式 Session Mode | 单作业模式 Per-Job Mode | 应用模式 Application Mode |
|---|---|---|---|
| 集群创建时机 | 先启动集群,再提交作业 | 提交作业后,为该作业创建集群 | 提交应用后,为该应用创建集群 |
| 多个作业是否共用集群 | 是 | 否 | 否 |
| 资源是否共享 | 是,作业竞争资源 | 否,资源隔离 | 否,资源隔离 |
| 作业完成后集群是否关闭 | 不一定,Session 仍可继续接收作业 | 是 | 是 |
main() 执行位置 |
Client | Client | JobManager |
| 适用场景 | 小规模、执行时间短、作业多 | 稳定生产作业 | 减少客户端压力、应用级隔离 |
4. 会话模式 Session Mode
核心思想
先申请一个 Flink 集群,之后不断向这个集群提交作业。
text
先启动 Session Cluster
→ Client 提交 Job1
→ Client 提交 Job2
→ Client 提交 Job3
特点
- 集群资源在启动时就确定;
- 多个作业共享资源;
- 作业之间可能互相竞争内存、Slot、CPU;
- 适合许多小作业、短作业。
一句话记忆:
会话模式:一个集群,多个作业,共享资源。
5. 单作业模式 Per-Job Mode
核心思想
每提交一个作业,就为该作业单独启动一个 Flink 集群。
text
提交 Job1 → 创建 Cluster1 → Job1 完成 → Cluster1 关闭
提交 Job2 → 创建 Cluster2 → Job2 完成 → Cluster2 关闭
特点
- 每个作业独享集群;
- 作业之间资源隔离;
- 作业结束后,集群和资源一起释放;
- 生产环境更稳定;
- 一般需要依赖 YARN、Kubernetes 等资源管理平台。
一句话记忆:
单作业模式:一个作业,一个集群,作业结束,集群释放。
6. 应用模式 Application Mode
核心思想
应用程序不再主要由客户端解析和执行,而是直接交给 JobManager 执行。
与单作业模式的区别
两者都具有"一个应用或作业对应一个集群"的特点,但最关键区别是:
| 项目 | 单作业模式 | 应用模式 |
|---|---|---|
应用 main() 的执行位置 |
Client | JobManager |
| Client 工作负担 | 较重,需要解析应用、传递依赖 | 较轻 |
| 网络传输压力 | Client 需要传输依赖、二进制数据 | 压力较小 |
一句话记忆:
应用模式:让 JobManager 直接执行应用程序,减少 Client 的资源和网络压力。
第 4 章 Flink 运行时架构:组件、并行度、Slot 是高频选择题
本章重点是:组件职责、并行度设置、算子链、任务槽与并行度的关系。
1. JobManager、TaskManager 总体关系
text
Client
↓ 提交作业
JobManager
↓ 调度、申请 Slot、分配任务
TaskManager
↓
真正执行具体计算
2. JobManager 的三个核心组件
2.1 JobMaster
JobMaster 是 JobManager 中最核心的组件。
关键点:
- 一个 Job 对应一个 JobMaster;
- 多个 Job 可以同时存在于一个 Flink 集群;
- 每个 Job 都有自己的 JobMaster;
- 将
JobGraph转换为ExecutionGraph; - 向 ResourceManager 申请 Slot;
- 获取资源后把任务分配给 TaskManager;
- 协调检查点 Checkpoint。
高频描述题
"负责单个作业、生成执行图、申请 Slot、协调检查点"的组件是什么?
答案:JobMaster
2.2 ResourceManager
Flink 内部的 ResourceManager 负责资源分配与管理。
它管理的核心资源是:
text
TaskManager 的 Task Slot
注意:
- Flink 集群中只有一个 Flink ResourceManager;
- 它主要负责管理 Slot;
- 每个 Task 要被分配到 Slot 中运行;
- 不要与 YARN 的 ResourceManager 混淆。
高频描述题
"管理 TaskManager 的 Slot、负责资源分配"的组件是什么?
答案:ResourceManager
2.3 Dispatcher
Dispatcher 的职责:
- 提供 REST 接口;
- 接收作业提交;
- 为新作业启动 JobMaster;
- 启动 Web UI;
- 展示和监控作业信息。
高频描述题
"负责 REST 作业提交、启动 JobMaster、提供 Web UI"的组件是什么?
答案:Dispatcher
3. TaskManager
TaskManager 是 Flink 的工作进程。
主要职责:
- 真正执行数据流计算;
- 内部包含多个 Slot;
- 启动后向 ResourceManager 注册 Slot;
- 将 Slot 提供给 JobMaster;
- 接收 JobMaster 分配的任务并执行。
一句话:
JobManager 负责"管、调、分",TaskManager 负责"真正干活"。
4. 并行度 Parallelism
4.1 什么是并行度
一个算子可以被拆分成多个并行子任务执行。
text
map 算子
→ map[1]
→ map[2]
→ map[3]
某个算子的子任务数量,就是该算子的并行度。
例如:
text
Source 并行度 = 2
Map 并行度 = 2
Window 并行度 = 2
Sink 并行度 = 1
则整个作业的并行度通常看作:
text
最大并行度 = 2
4.2 并行度的三种设置方式
方式一:单独设置某个算子
java
stream.map(word -> Tuple2.of(word, 1L))
.setParallelism(2);
特点:
- 只影响当前算子;
- 是最细粒度的设置方式。
方式二:代码中设置全局并行度
java
env.setParallelism(2);
特点:
- 设置当前程序的默认并行度;
- 没有单独设置并行度的算子,默认使用该值;
- 实际生产中通常不建议硬编码全局并行度,否则扩缩容不够灵活。
方式三:提交作业时设置
bash
bin/flink run -p 2 -c 全类名 jar包
特点:
- 作用类似全局并行度;
- 比直接写死在代码中更灵活;
- Web UI 提交作业时也可以填写并行度。
方式四:配置文件设置
在 flink-conf.yaml 中:
yaml
parallelism.default: 2
特点:
- 作为集群默认并行度;
- 当代码和提交命令均未设置时使用;
- 开发环境中默认并行度通常为本机 CPU 核数。
易错点
keyBy() 不能设置并行度:
java
.keyBy(...)
原因:
keyBy()不是普通算子,它是逻辑上的按键分区操作。
5. 算子链 Operator Chain
5.1 算子之间的两种数据传输方式
一对一 One-to-One
特点:
- 不重新分区;
- 数据顺序和分区关系保持;
- 上游一个子任务对应下游一个子任务;
- 常见于
map、filter、flatMap。
text
Source[1] → Map[1]
Source[2] → Map[2]
重分区 Redistributing
特点:
- 数据会重新分配给下游任务;
- 下游可能接收多个上游任务的数据;
- 类似 Spark 的 Shuffle。
常见场景:
text
map → keyBy / window
keyBy / window → Sink
5.2 什么是算子链
满足以下条件的算子可以合并为同一个 Task:
text
并行度相同
+
一对一的数据传输关系
例如:
text
Source → map
通常可以合并。
text
Source + map
→ 一个 Task
好处:
- 减少线程切换;
- 减少缓冲区数据交换;
- 降低延迟;
- 提升吞吐量。
Flink 默认会自动进行算子链合并。
禁止合并算子链
java
.map(word -> Tuple2.of(word, 1L))
.disableChaining();
从当前位置开始新的算子链
java
.map(word -> Tuple2.of(word, 1L))
.startNewChain();
6. 任务槽 Task Slot
6.1 什么是 Slot
TaskManager 是一个 JVM 进程,可以运行多个线程执行多个子任务。
为了划分资源,Flink 将 TaskManager 的资源划分为多个 Slot。
text
TaskManager
├─ Slot 1
├─ Slot 2
└─ Slot 3
每个 Slot 表示一部分相对独立的计算资源,主要用于隔离内存资源。
6.2 Slot 数量设置
在 flink-conf.yaml 中:
yaml
taskmanager.numberOfTaskSlots: 8
通常可设置为机器 CPU 核数附近。
注意:
Slot 主要隔离内存,不是严格的 CPU 隔离。
6.3 Slot 共享
默认情况下,Flink 允许同一作业中不同任务节点的子任务共享 Slot。
例如:
text
Source → flatMap → reduce → sink
在默认 Slot 共享机制下,属于同一条流水线、对应并行子任务的任务可以放进同一个 Slot。
这样做的好处:
- 资源密集型与非资源密集型任务可共享资源;
- 更容易保证整条作业管道在 TaskManager 故障时仍具备恢复能力;
- 可以减少需要的 Slot 数量。
指定 Slot 共享组:
java
.map(word -> Tuple2.of(word, 1L))
.slotSharingGroup("group1");
只有同一个 Slot Sharing Group 中的子任务才能共享 Slot。
6.4 Task Slot 与 Parallelism 的区别
| 概念 | Task Slot | Parallelism |
|---|---|---|
| 本质 | 集群资源单位 | 程序实际并发能力 |
| 属性 | 静态资源概念 | 动态执行概念 |
| 配置 | taskmanager.numberOfTaskSlots |
parallelism.default、-p、setParallelism() |
| 作用 | 决定集群可提供多少并发资源 | 决定作业实际启动多少并行子任务 |
一句话:
Slot 是"有多少工位",并行度是"实际安排多少人同时干活"。
6.5 YARN 下 TaskManager 数量计算
text
TaskManager 数量
=
向上取整(Job 并行度 / 每个 TaskManager 的 Slot 数)
例如:
text
Job 并行度 = 10
每个 TM Slot 数 = 3
TM 数量 = ceil(10 / 3) = 4
第 5 章 DataStream API:本课程代码重点最多的一章
本章重点包括执行环境、Source、Transformation、物理分区、分流合流、Kafka 与 JDBC Sink。
1. DataStream 程序基本结构
text
Execution Environment
→ Source
→ Transformation
→ Sink
→ execute()
对应代码结构:
java
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
// Source
DataStreamSource<String> source = ...;
// Transformation
SingleOutputStreamOperator<String> result = source.map(...);
// Sink
result.print();
// Execute
env.execute();
2. 创建执行环境
执行环境类:
java
StreamExecutionEnvironment
2.1 getExecutionEnvironment():最重点
java
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
特点:
- 本地直接运行时,自动返回本地执行环境;
- 打成 Jar 后提交到集群时,自动返回集群执行环境;
- 最常用、最推荐。
2.2 createLocalEnvironment():了解
java
StreamExecutionEnvironment localEnv =
StreamExecutionEnvironment.createLocalEnvironment();
特点:
- 强制创建本地执行环境;
- 不传并行度时,默认并行度通常为本机 CPU 核数。
2.3 createRemoteEnvironment():了解
java
StreamExecutionEnvironment remoteEnv =
StreamExecutionEnvironment.createRemoteEnvironment(
"host",
1234,
"path/to/jarFile.jar"
);
参数含义:
| 参数 | 含义 |
|---|---|
"host" |
JobManager 主机名 |
1234 |
JobManager 端口 |
"path/to/jarFile.jar" |
要提交到集群执行的 Jar 包 |
3. 执行模式 Execution Mode
| 模式 | 含义 |
|---|---|
| Streaming | 默认模式,用于无界流实时处理 |
| Batch | 批处理模式 |
| Automatic | 根据数据源是否有界自动选择模式 |
批处理命令行方式:
bash
bin/flink run -Dexecution.runtime-mode=BATCH ...
代码方式:
java
env.setRuntimeMode(RuntimeExecutionMode.BATCH);
考试中建议记:
批处理优先通过提交命令设置
execution.runtime-mode=BATCH,比代码硬编码更灵活。
4. execute():为什么必须调用
java
env.execute();
Flink 是延迟执行、事件驱动的。
写完:
java
result.print();
只是定义了数据流图,不代表任务已真正执行。
只有调用:
java
env.execute();
才真正触发作业执行。
5. 源算子 Source
Source 是整个程序的输入端。
常见数据源:
| 数据源 | 常见方法或连接器 | 适用情况 |
|---|---|---|
| 集合 | fromCollection() |
测试 |
| 文件 | 文件连接器 | 批处理、日志文件 |
| Socket | socketTextStream() |
测试实时流 |
| Kafka | Kafka Connector | 常见生产数据源 |
| 数据生成器 | DataGen Connector | 测试、性能测试 |
| 自定义 Source | SourceFunction 等 |
特殊数据源 |
5.1 旧版与新版 Source 写法
旧架构:
java
env.addSource(...)
新版 Source 架构:
java
env.fromSource(...)
Flink 1.12 之后更推荐新 Source 架构。
5.2 Socket Source
java
env.socketTextStream("hadoop102", 7777);
参数:
| 参数 | 含义 |
|---|---|
"hadoop102" |
Socket 服务所在主机 |
7777 |
Socket 端口 |
启动测试 Socket 服务:
bash
nc -lk 7777
注意:
socketTextStream()是非并行 Source,吞吐较低,通常只用于测试。
5.3 自定义 Source 的并行度
| 类型 | 是否并行 |
|---|---|
SourceFunction |
并行度通常为 1 |
RichSourceFunction |
并行度通常为 1 |
ParallelSourceFunction |
可以并行 |
RichParallelSourceFunction |
可以并行,且具有丰富功能 |
6. 基本转换算子
6.1 map()
特点:
text
输入一个元素
→ 输出一个元素
即"一一映射"。
java
stream.map(value -> value * 2);
适用于:
- 字段转换;
- 数据类型转换;
- 给每条数据补充信息;
- 简单加工。
6.2 filter()
filter() 根据布尔值决定是否保留元素。
java
stream.filter(value -> value > 10);
规则:
filter() 返回值 |
结果 |
|---|---|
true |
保留该数据并输出 |
false |
过滤掉该数据 |
高频易错点:
true不是"过滤掉",而是"通过过滤条件,保留"。
6.3 flatMap()
特点:
text
输入一个元素
→ 输出 0 个、1 个或多个元素
适合:
- 拆分字符串;
- 拆分集合;
- 一条记录生成多条记录;
- WordCount 的按空格拆词。
例如:
text
输入:"hello flink"
输出:"hello"、"flink"
与 map() 的区别:
| 算子 | 一个输入元素可产生多少输出元素 |
|---|---|
map |
恰好 1 个 |
flatMap |
0 到多个 |
7. 聚合相关:keyBy 不是聚合算子
这是老师特别强调的易错点。
7.1 keyBy() 的本质
keyBy() 是:
text
按键分区
不是聚合,也不是普通转换算子。
它把一个 DataStream 按照 key 逻辑划分为多个分区,得到:
text
KeyedStream
例如:
java
stream.keyBy(value -> value.f0);
表示按 f0 字段分组。
7.2 keyBy() 的作用
text
相同 key 的数据
→ 发送到同一个逻辑分区
→ 后续可以在该 key 范围内做聚合、窗口、状态计算
底层大致思路:
text
hash(key) % 分区数量
因此如果 key 是自定义 POJO,需要正确实现:
java
hashCode()
7.3 简单聚合算子
必须先 keyBy(),再聚合。
java
stream
.keyBy(value -> value.f0)
.sum(1);
常见方法:
| 方法 | 含义 |
|---|---|
sum() |
求和 |
min() |
指定字段最小值 |
max() |
指定字段最大值 |
minBy() |
返回最小字段所在的整条记录 |
maxBy() |
返回最大字段所在的整条记录 |
min() 与 minBy() 的区别
假设有对象:
text
(id, score, name)
min("score"):只保证score是最小值,其他字段可能保留旧记录值;minBy("score"):返回真正拥有最小score的那条完整数据。
max() 与 maxBy() 同理。
7.4 reduce()
reduce() 用于自定义归约逻辑。
java
stream
.keyBy(value -> value.f0)
.reduce((value1, value2) -> {
// 返回同类型结果
return ...;
});
特点:
- 每来一条新数据,就和当前聚合状态继续计算;
- 输入类型与输出类型相同;
- 每个 key 都维护自己的聚合状态;
- 无界流中状态不会自动无限清空,因此适合 key 数量有限的场景。
8. UDF:用户自定义函数
三种主要形式:
| 形式 | 说明 |
|---|---|
| 函数类 | 自己实现 MapFunction、FilterFunction、ReduceFunction 等接口 |
| 匿名内部类 | 在调用算子时直接写匿名实现 |
| Lambda 表达式 | 写法最简洁 |
| 富函数类 Rich Function | 可获取运行时上下文,具有生命周期方法 |
Rich Function 生命周期
常见富函数:
java
RichMapFunction
RichFilterFunction
RichReduceFunction
关键方法:
java
open()
- 初始化方法;
- 一个并行子任务生命周期内通常调用一次;
- 在
map()、filter()等真正工作方法之前调用。
java
close()
- 清理资源;
- 生命周期结束时调用一次。
注意:
text
open()/close():每个并行子任务通常一次
map()/filter():每条数据都会调用
9. 物理分区算子
老师特别强调:
物理分区算子负责决定上游数据如何发送到下游并行任务。
keyBy()是逻辑按键分区,不属于物理分区算子。
常见物理分区:
| 算子 | 分配方式 | 特点 |
|---|---|---|
shuffle() |
随机 | 随机均匀发送到下游 |
rebalance() |
轮询 | 面向全部下游并行任务轮流分发 |
rescale() |
局部轮询 | 只在下游部分任务间轮询 |
broadcast() |
广播 | 每条数据复制给所有下游任务 |
global() |
全局 | 所有数据发送到下游第一个子任务 |
partitionCustom() |
自定义 | 自己定义分区规则 |
9.1 shuffle()
java
stream.shuffle();
特点:
- 随机分配;
- 大致均匀;
- 每次运行时数据具体去向可能不同。
9.2 rebalance()
java
stream.rebalance();
特点:
- Round-Robin 轮询;
- 将数据平均分配给所有下游并行任务;
- 类似发牌。
9.3 rescale()
java
stream.rescale();
特点:
- 也是轮询;
- 但不是发给所有下游任务;
- 只会发送给某一部分下游并行任务;
- 可以理解为"分小组发牌"。
9.4 broadcast()
java
stream.broadcast();
特点:
- 每条数据复制到所有下游并行任务;
- 下游会得到重复副本;
- 常用于配置流、规则流等需要所有下游任务都知道的数据。
9.5 global()
java
stream.global();
特点:
- 所有数据都发往下游第一个并行子任务;
- 相当于强行把下游并行处理能力压到 1;
- 容易形成性能瓶颈,使用要谨慎。
10. 分流与合流
10.1 union()
java
stream1.union(stream2, stream3);
特点:
- 可以合并多个流;
- 所有流的数据类型必须一致;
- 只是把数据合到一个流;
- 不去重。
一句话:
union:多条同类型流合并为一条流,不去重。
10.2 connect()
java
stream1.connect(stream2);
特点:
- 只能连接两条流;
- 两条流可以是不同类型;
- 两条流仍然可以分别处理;
- 后续一般通过
CoMapFunction、CoFlatMapFunction等分别处理两条流。
一句话:
connect:两条流可以类型不同,连接后仍能分别处理。
10.3 union 与 connect 对比
| 对比项 | union() |
connect() |
|---|---|---|
| 可连接流数量 | 两条或多条 | 只能两条 |
| 数据类型 | 必须相同 | 可以不同 |
| 是否保留两条流的处理差异 | 否,合并为同类型流 | 是,可以分别处理 |
| 是否去重 | 不去重 | 不涉及去重 |
11. 输出算子 Sink
Flink 计算结果最终通常要写入外部系统。
常见 Sink:
text
控制台 print
文件 FileSink
Kafka
MySQL / JDBC
自定义 Sink
旧版写法:
java
stream.addSink(...);
新版写法:
java
stream.sinkTo(...);
print() 本身也是一种 Sink。
12. Kafka 连接器重点
Kafka 连接器依赖:
xml
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka</artifactId>
<version>${flink.version}</version>
</dependency>
Kafka Source 的关键参数一定要认识:
| 参数 | 含义 |
|---|---|
| Bootstrap Servers | Kafka Broker 地址 |
| Topic | 消费的主题 |
| Group ID | 消费者组 |
| Starting Offsets | 从哪里开始消费,例如 earliest、latest |
| Deserializer | 如何把 Kafka 中的字节数据反序列化为 Java 对象 |
典型新 Source 架构:
java
KafkaSource<String> kafkaSource = KafkaSource.<String>builder()
.setBootstrapServers("hadoop102:9092")
.setTopics("topic1")
.setGroupId("g1")
.setStartingOffsets(OffsetsInitializer.latest())
.setValueOnlyDeserializer(new SimpleStringSchema())
.build();
DataStreamSource<String> stream = env.fromSource(
kafkaSource,
WatermarkStrategy.noWatermarks(),
"kafka-source"
);
逐项理解:
java
.setBootstrapServers("hadoop102:9092")
指定 Kafka 地址。
java
.setTopics("topic1")
指定消费主题。
java
.setGroupId("g1")
指定消费者组。
java
.setStartingOffsets(OffsetsInitializer.latest())
指定从最新 Offset 开始消费。
java
.setValueOnlyDeserializer(new SimpleStringSchema())
指定消息 value 按字符串反序列化。
13. JDBC / MySQL Sink 重点
JDBC 连接器依赖:
xml
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-jdbc</artifactId>
<version>1.17-SNAPSHOT</version>
</dependency>
MySQL 驱动:
xml
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
典型代码结构:
java
stream.addSink(
JdbcSink.sink(
"insert into ws(id, ts, vc) values (?, ?, ?)",
new JdbcStatementBuilder<WaterSensor>() {
@Override
public void accept(
PreparedStatement ps,
WaterSensor value
) throws SQLException {
ps.setString(1, value.getId());
ps.setLong(2, value.getTs());
ps.setInt(3, value.getVc());
}
},
JdbcExecutionOptions.builder()
.withBatchSize(1000)
.build(),
new JdbcConnectionOptions.JdbcConnectionOptionsBuilder()
.withUrl("jdbc:mysql://localhost:3306/test")
.withDriverName("com.mysql.cj.jdbc.Driver")
.withUsername("root")
.withPassword("root")
.build()
)
);
accept() 方法的重点
accept() 会在需要写入一条数据时执行。
java
ps.setString(1, value.getId());
ps.setLong(2, value.getTs());
ps.setInt(3, value.getVc());
对应 SQL 中的三个占位符:
sql
insert into ws(id, ts, vc) values (?, ?, ?)
位置必须一一对应:
| SQL 占位符 | Java 设置方法 | 对应字段 |
|---|---|---|
第 1 个 ? |
setString(1, ...) |
id |
第 2 个 ? |
setLong(2, ...) |
ts |
第 3 个 ? |
setInt(3, ...) |
vc |
第 6 章 时间与窗口:四大基石之一
老师强调:Flink 四大重要基础中,本章是"时间和窗口"。必须掌握窗口分类、时间语义、水印、迟到数据和侧输出。
1. 什么是窗口 Window
Flink 主要处理无界流,而无界流不会自然结束。
窗口的作用:
text
把无限数据流
切分成有限的数据块
再对每一块数据进行计算
正确理解:
窗口不是一个静态"框",更像是一个动态创建的数据桶。
数据落到某个时间范围内,就进入对应窗口;窗口达到触发条件后再进行计算。
2. 窗口分类
2.1 按驱动类型分类
| 类型 | 划分依据 | 记忆方式 |
|---|---|---|
| 时间窗口 Time Window | 时间范围 | 定点发车 |
| 计数窗口 Count Window | 元素数量 | 人齐发车 |
时间窗口
例如:
text
每 30 秒统计一次
窗口根据时间开始和结束。
计数窗口
例如:
text
每收集 10 条数据统计一次
达到指定数量后触发。
2.2 按分配规则分类
| 类型 | 窗口关系 | 数据是否可能进入多个窗口 |
|---|---|---|
| 滚动窗口 Tumbling Window | 首尾相接,无重叠、无间隔 | 不会 |
| 滑动窗口 Sliding Window | 有固定大小和滑动步长 | 可能 |
| 会话窗口 Session Window | 按会话间隔划分 | 不会重叠 |
| 全局窗口 Global Window | 同一 key 全部放入同一窗口 | 默认不自动触发 |
3. 滚动窗口 Tumbling Window
特点:
- 固定窗口大小;
- 窗口之间首尾相接;
- 不重叠;
- 无间隔;
- 每条数据只属于一个窗口;
- 只有一个参数:窗口大小。
例如:
text
每 30 秒统计一次最近 30 秒数据
代码思路:
java
.window(TumblingEventTimeWindows.of(Time.seconds(30)))
参数数量:1 个。
4. 滑动窗口 Sliding Window
特点:
- 固定窗口大小;
- 有固定滑动步长;
- 参数有两个:窗口大小和滑动步长;
- 当滑动步长小于窗口大小时,窗口会重叠;
- 一条数据可能被分配到多个窗口。
例如:
text
每 30 秒统计一次最近 1 分钟数据
代码:
java
.window(
SlidingEventTimeWindows.of(
Time.minutes(1),
Time.seconds(30)
)
)
参数顺序:
text
窗口大小 size
→ 滑动步长 slide
也就是:
java
SlidingEventTimeWindows.of(size, slide)
滚动窗口与滑动窗口的关系
text
size = slide
时:
text
滑动窗口 = 特殊的滚动窗口
例如:
java
SlidingEventTimeWindows.of(
Time.seconds(30),
Time.seconds(30)
)
效果上等价于 30 秒滚动窗口。
5. 会话窗口 Session Window
会话窗口只能基于时间定义。
核心参数:
text
session gap
即会话超时时间、两个会话之间允许的最大间隔。
判断逻辑:
text
相邻数据间隔 < gap
→ 属于同一个会话窗口
相邻数据间隔 > gap
→ 进入新会话窗口
特点:
- 窗口长度不固定;
- 起始结束时间不固定;
- 窗口之间不会重叠;
- 适用于用户会话、登录行为、连续点击等场景。
6. 全局窗口 Global Window
特点:
- 相同 key 的所有数据放到同一个窗口;
- 没有结束时间;
- 默认不会自动触发计算;
- 需要自定义 Trigger 才能触发;
- Count Window 底层就是依赖 Global Window 实现。
一句话:
全局窗口不会自己结束,也不会自己自动计算。
7. Keyed Window 与 Non-Keyed Window
7.1 Keyed Window
先分组:
java
stream
.keyBy(...)
.window(...)
特点:
- 每个 key 有自己独立的一组窗口;
- 相同 key 的数据进入同一逻辑分区;
- 可以并行执行;
- 实际业务最常用。
7.2 Non-Keyed Window
不做 keyBy():
java
stream
.windowAll(...)
特点:
- 所有数据进入同一个窗口;
- 非并行操作;
- 相当于窗口计算并行度为 1;
- 即使手动调高并行度也无效。
易错点:
windowAll()本身就是非并行操作。
8. 窗口函数
窗口函数负责定义窗口中数据如何计算。
分为两类:
| 类型 | 代表方法 | 特点 |
|---|---|---|
| 增量聚合函数 | reduce()、sum()、min()、max()、aggregate() |
每来一条数据就更新中间结果 |
| 全窗口函数 | apply()、process() |
先收集完整窗口数据,触发时再统一处理 |
增量聚合函数
优点:
- 节省内存;
- 不需要缓存所有窗口数据;
- 适合求和、最大值、最小值等。
例如:
java
.sum("vc")
全窗口函数
特点:
- 需要获取窗口完整数据;
- 可以获取窗口起始时间、结束时间等上下文;
- 适合复杂计算。
常见:
java
.apply(...)
.process(...)
9. 时间语义
| 时间语义 | 含义 |
|---|---|
| Event Time | 数据真实发生、产生时的时间 |
| Processing Time | Flink 实际处理该数据时的系统时间 |
例如一条支付记录:
text
用户支付发生时间:10:00:00
Flink 收到并处理时间:10:00:05
则:
text
Event Time = 10:00:00
Processing Time = 10:00:05
为什么实际业务常用 Event Time
因为网络、Kafka、分布式传输可能造成延迟或乱序。
如果统计:
text
8:00 ~ 9:00 的活跃用户
通常应该按用户行为真正发生的时间统计,而不是按数据什么时候到达服务器统计。
从 Flink 1.12 起,事件时间成为默认时间语义。
10. Watermark 水印:本章最重要概念之一
10.1 为什么需要 Watermark
事件时间数据经常乱序。
例如真实事件时间顺序:
text
10:08
10:09
10:11
10:15
但 Flink 接收到的顺序可能是:
text
10:08
10:15
10:09
10:11
如果没有水印,窗口很难判断:
text
"这个窗口的数据是不是已经基本到齐,可以计算了?"
10.2 Watermark 的本质
Watermark 可以理解为:
事件时间进度标记,也是允许一定乱序后的延迟触发机制。
常见表达:
text
Watermark
=
当前已观察到的最大事件时间
-
最大允许乱序时间
例如:
text
当前最大事件时间 = 10:30
允许乱序时间 = 2 秒
Watermark = 10:28
注意:
水印不会修改原始事件时间。
窗口边界仍按 Event Time 划分,水印只影响"什么时候触发窗口计算"。
10.3 窗口触发条件
对于事件时间窗口,通常需要同时满足:
text
1. 窗口中有数据
2. Watermark >= 窗口结束时间
即:
text
Watermark >= Window End Time
例如窗口:
text
[10:00:00, 10:10:00)
当:
text
Watermark >= 10:10:00
时,该窗口才可以触发计算。
10.4 水印代码骨架
java
WatermarkStrategy<WaterSensor> watermarkStrategy =
WatermarkStrategy
.<WaterSensor>forBoundedOutOfOrderness(
Duration.ofSeconds(3)
)
.withTimestampAssigner(
(event, timestamp) -> event.getTs() * 1000L
);
SingleOutputStreamOperator<WaterSensor> stream = source
.assignTimestampsAndWatermarks(watermarkStrategy);
关键理解:
java
forBoundedOutOfOrderness(Duration.ofSeconds(3))
表示:
最多允许约 3 秒的乱序。
java
withTimestampAssigner(...)
表示:
从哪一个字段中提取事件时间戳。
11. 迟到数据、Allowed Lateness 与侧输出
11.1 水印可以解决什么
水印可以解决一定范围内的短期乱序和延迟。
但它不能无限等待,否则窗口永远无法关闭。
11.2 allowedLateness()
java
.allowedLateness(Time.seconds(5))
含义:
Watermark 到达窗口结束时间后,窗口先触发计算,但暂时不关闭,继续等待一段允许迟到时间。
未设置 allowedLateness 时:
text
Watermark 到达窗口结束时间
→ 窗口计算
→ 窗口关闭
设置 allowedLateness 后:
text
Watermark 到达窗口结束时间
→ 窗口先计算
→ 暂不关闭
→ 在允许迟到时间内继续接收迟到数据
→ 有迟到数据到达时可能再次触发计算
11.3 Side Output 侧输出流
如果数据已经:
text
晚于 Watermark
+
又晚于 allowedLateness
则它已经无法再放回原窗口。
这时可以通过侧输出流收集:
java
OutputTag<WaterSensor> lateTag =
new OutputTag<WaterSensor>("late-data") {};
SingleOutputStreamOperator<String> result = stream
.keyBy(WaterSensor::getId)
.window(TumblingEventTimeWindows.of(Time.seconds(10)))
.allowedLateness(Time.seconds(5))
.sideOutputLateData(lateTag)
.process(...);
DataStream<WaterSensor> lateData =
result.getSideOutput(lateTag);
侧输出的用途:
- 收集超时迟到数据;
- 后续单独补偿;
- 写入 Kafka;
- 写入数据库;
- 人工分析或修复。
一句话:
正常窗口处理不了的超级迟到数据,放进 Side Output 交给开发者自行处理。
第 7 章 状态与检查点:四大基石之二
本章重点是状态的分类、Keyed State 各类型、Checkpoint 与 Savepoint 的区别及恢复命令。
1. 什么是状态 State
无状态算子:
text
当前输出只依赖当前输入
例如:
java
map()
filter()
flatMap()
有状态算子:
text
当前输出依赖当前输入
+
此前保存的数据
例如:
text
聚合算子
窗口算子
有状态算子的典型过程:
text
接收数据
→ 读取当前状态
→ 更新状态
→ 输出结果
2. 托管状态与原始状态
| 类型 | 含义 |
|---|---|
| Managed State 托管状态 | Flink 统一管理状态存储、恢复、重分配 |
| Raw State 原始状态 | 用户自己管理序列化、存储和恢复 |
实际开发中通常使用:
text
Managed State
因为故障恢复、状态重组等工作由 Flink 处理。
3. Operator State 与 Keyed State
3.1 共同点
无论 Operator State 还是 Keyed State:
- 都由本地并行子任务维护;
- 不同并行子任务之间状态不共享;
- 每个并行子任务只能访问自己维护的状态。
3.2 Operator State 算子状态
作用范围:
text
当前算子子任务实例
也就是说:
text
Task 1 有自己的状态
Task 2 有自己的状态
Task 3 有自己的状态
同一个 Task 中处理的所有数据访问同一份算子状态。
常见应用:
- Source 的 Offset;
- 当前任务处理到第几条数据;
- 当前子任务的局部缓存数据。
使用时通常需要实现:
java
CheckpointedFunction
3.3 Keyed State 按键分区状态
Keyed State 必须建立在:
java
keyBy(...)
之后的 KeyedStream 上。
作用范围:
text
当前并行子任务
+
当前 key
例如:
text
key = user1 → 一份状态
key = user2 → 一份状态
key = user3 → 一份状态
重点理解:
Keyed State 不仅区分 Task,也区分 key。
不同 key 的状态彼此隔离。
没有 keyBy() 的普通 DataStream,即使使用富函数,也不能直接通过运行时上下文访问 Keyed State。
4. Keyed State 的类型
| 状态类型 | 存储内容 | 常用方法 |
|---|---|---|
ValueState<T> |
单个值 | value()、update() |
ListState<T> |
列表 | add()、addAll()、get() |
ReducingState<T> |
按 ReduceFunction 聚合后的结果 | add()、get() |
AggregatingState<IN, OUT> |
按 AggregateFunction 聚合后的结果 | add()、get() |
MapState<K, V> |
Map 键值对 | put()、get()、entries() |
FoldingState |
旧的折叠状态 | 已废弃,推荐用 AggregatingState 替代 |
4.1 ValueState
适合保存单个值。
例如记录某个用户最近一次访问时间:
java
ValueState<Long> lastVisitTime;
更新:
java
lastVisitTime.update(value);
获取:
java
Long time = lastVisitTime.value();
4.2 ListState
适合保存多个元素。
例如保存某用户最近若干条行为记录:
java
ListState<String> behaviorState;
添加:
java
behaviorState.add("click");
获取:
java
Iterable<String> values = behaviorState.get();
4.3 ReducingState
适合每次输入新数据时,都按 ReduceFunction 做归约。
例如不断累加某个 key 的统计值。
4.4 AggregatingState
适合输入类型和输出类型不同的复杂聚合。
例如:
text
输入:订单对象
中间状态:金额与数量
输出:平均订单金额
4.5 MapState
适合维护键值对结构。
例如:
text
商品 ID → 商品购买数量
5. Checkpoint 检查点
5.1 Checkpoint 的作用
Checkpoint 可以理解为自动"存档"。
Flink 定期保存:
- 算子状态;
- Keyed State;
- Source Offset;
- 窗口状态;
- 聚合结果等。
发生故障后:
text
从最近检查点恢复
→ 恢复状态
→ 从对应 Offset 继续处理
目标是使故障恢复后的结果与故障前保持一致。
因此 Checkpoint 也称:
text
一致性检查点
5.2 开启 Checkpoint
java
env.enableCheckpointing(1000);
含义:
text
每 1000 毫秒,即每 1 秒进行一次检查点快照。
保存到 HDFS:
java
env.setStateBackend(
new FsStateBackend(
"hdfs://bigdata01:9820/flink/checkpoint"
)
);
取消作业后保留检查点:
java
env.getCheckpointConfig()
.enableExternalizedCheckpoints(
CheckpointConfig.ExternalizedCheckpointCleanup
.RETAIN_ON_CANCELLATION
);
含义:
作业被取消时,不自动删除外部存储中的 Checkpoint。
6. Savepoint 保存点
Savepoint 与 Checkpoint 的核心原理、状态快照算法基本相同。
最大区别在于:
| 对比项 | Checkpoint | Savepoint |
|---|---|---|
| 触发方式 | Flink 自动、周期性触发 | 用户手动触发 |
| 主要作用 | 故障恢复 | 运维、升级、迁移、暂停恢复 |
| 创建频率 | 经常 | 按需 |
| 是否带额外元数据 | 相对少 | 有额外元数据 |
Savepoint 常用于:
- 版本升级;
- 修改程序;
- 调整并行度;
- 暂停应用;
- 恢复应用;
- 作业归档。
6.1 创建 Savepoint
bash
bin/flink savepoint <jobId> [targetDirectory]
例如:
bash
bin/flink savepoint \
79f53c5c0bb3563b6b6ed3011176c411 \
hdfs://hadoop102:8020/flink/savepoint
6.2 从 Checkpoint 或 Savepoint 恢复
关键参数:
bash
-s
命令骨架:
bash
bin/flink run -c 全类名 -s 保存点或检查点路径 jar包
例如:
bash
bin/flink run \
-c com.bigdata.day06._01CheckPointDemo \
-s hdfs://bigdata01:9820/flink/checkpoint/xxx \
xxx.jar
必背:
-s用于指定 Savepoint 或 Checkpoint 路径,从对应状态快照恢复作业。
第 8 章 Table API 与 SQL:连接器、窗口、Watermark 是重点
本章重点包括表环境、表流互转、Kafka/JDBC Connector、TUMBLE()、HOP()、WATERMARK FOR。
1. Table API 是什么
Table API 是 Flink 面向表的高层 API。
特点:
- 支持流处理与批处理统一;
- 以关系模型为基础;
- 可以使用
select、join、group by、aggregate等操作; - SQL 支持基于 Apache Calcite;
- 可与 DataStream API 相互转换。
2. Table API 开发五步
text
1. 创建表环境
2. 创建表
3. 查询表
4. 输出表
5. 表流转换
3. 创建表环境
依赖:
xml
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-planner_2.12</artifactId>
<version>${flink.version}</version>
</dependency>
代码:
java
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
StreamTableEnvironment tableEnv =
StreamTableEnvironment.create(env);
必须记住:
| 内容 | 答案 |
|---|---|
| 表环境类 | StreamTableEnvironment |
| 创建方法 | StreamTableEnvironment.create(env) |
4. 创建临时表
将 DataStream 注册为临时视图:
java
tableEnv.createTemporaryView("wc", dataStreamSource);
作用:
text
把 DataStream 注册成 SQL 可以直接查询的虚拟表。
后续可以写:
java
Table table = tableEnv.sqlQuery("select * from wc");
5. DataStream 转 Table
5.1 fromDataStream()
java
Table sensorTable =
tableEnv.fromDataStream(sensorDS);
作用:
text
DataStream → Table
指定字段:
java
Table sensorTable =
tableEnv.fromDataStream(
sensorDS,
$("id"),
$("vc")
);
字段重命名:
java
Table sensorTable =
tableEnv.fromDataStream(
sensorDS,
$("id").as("sid"),
$("vc")
);
5.2 createTemporaryView()
java
tableEnv.createTemporaryView(
"sensorTable",
sensorDS,
$("id"),
$("ts"),
$("vc")
);
作用:
text
DataStream → 临时表 / 临时视图
→ 可以直接被 SQL 使用
6. Table 转 DataStream
6.1 toDataStream()
java
tableEnv.toDataStream(table).print();
适合:
- 普通追加型结果;
- 表中每一行不会反复更新的情况。
6.2 toChangelogStream()
java
tableEnv.toChangelogStream(table).print();
适合:
- 聚合结果;
- 分组统计结果;
- 表中某一行可能被更新的情况。
例如:
sql
SELECT id, SUM(vc)
FROM source
GROUP BY id
这里某个 id 的 SUM(vc) 会不断变化。
因此应使用:
java
tableEnv.toChangelogStream(table)
来得到更新日志流。
高频易错点
| 方法 | 适用场景 |
|---|---|
toDataStream() |
追加型、不会更新的表 |
toChangelogStream() |
有更新、撤回、聚合变化的表 |
7. Table 字段类型转换
7.1 原子类型
例如:
java
DataStream<Long> stream = ...;
转成 Table 后只有一列。
重命名:
java
Table table =
tableEnv.fromDataStream(stream, $("myLong"));
7.2 Tuple 类型
Tuple 默认字段名:
text
f0、f1、f2...
例如:
java
DataStream<Tuple2<Long, Integer>> stream = ...;
重排字段:
java
Table table =
tableEnv.fromDataStream(stream, $("f1"), $("f0"));
重命名:
java
Table table =
tableEnv.fromDataStream(
stream,
$("f1").as("myInt"),
$("f0").as("myLong")
);
7.3 POJO 类型
POJO 转 Table 时,默认字段名使用 POJO 的属性名。
例如:
java
Table table =
tableEnv.fromDataStream(stream);
若 POJO 中有:
text
user
url
timestamp
则 Table 默认列名也是这些属性名。
8. Table API 的 Kafka Connector
Kafka 依赖:
xml
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka</artifactId>
<version>${flink.version}</version>
</dependency>
JSON 格式依赖:
xml
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-json</artifactId>
<version>1.17.2</version>
</dependency>
Kafka 表的典型 DDL
sql
CREATE TABLE table1 (
`user_id` INT,
`page_id` INT,
`status` STRING
) WITH (
'connector' = 'kafka',
'topic' = 'topic1',
'properties.bootstrap.servers' = 'hadoop102:9092',
'properties.group.id' = 'g1',
'scan.startup.mode' = 'latest-offset',
'format' = 'json'
);
参数意义:
| 参数 | 含义 |
|---|---|
'connector' = 'kafka' |
使用 Kafka 连接器 |
'topic' |
Kafka Topic |
'properties.bootstrap.servers' |
Kafka Broker 地址 |
'properties.group.id' |
消费者组 |
'scan.startup.mode' |
Offset 起始策略 |
'format' = 'json' |
数据格式是 JSON |
从 Kafka 读,过滤后再写 Kafka
sql
INSERT INTO table2
SELECT *
FROM table1
WHERE status = 'success';
含义:
text
从 table1 读取 Kafka 数据
→ 过滤 status='success'
→ 写入 table2 对应的 Kafka Topic
9. JDBC Connector
JDBC 表的典型 DDL:
sql
CREATE TABLE table2 (
`user_id` INT,
`page_id` INT,
`status` STRING
) WITH (
'connector' = 'jdbc',
'url' = 'jdbc:mysql://localhost:3306/flink',
'table-name' = 't_success',
'username' = 'root',
'password' = 'root'
);
关键参数:
| 参数 | 含义 |
|---|---|
'connector' = 'jdbc' |
使用 JDBC Connector |
'url' |
数据库连接地址 |
'table-name' |
数据库表名 |
'username' |
用户名 |
'password' |
密码 |
Kafka 数据写入 MySQL:
sql
INSERT INTO table2
SELECT *
FROM table1
WHERE status = 'success';
10. Table API 的事件时间与 Watermark
创建事件时间字段:
sql
`event_time` TIMESTAMP(3)
创建 Watermark:
sql
WATERMARK FOR event_time
AS event_time - INTERVAL '3' SECOND
完整结构:
sql
CREATE TABLE table1 (
`username` STRING,
`price` INT,
`event_time` TIMESTAMP(3),
WATERMARK FOR event_time
AS event_time - INTERVAL '3' SECOND
) WITH (
...
);
老师多次强调填空时重点记住:
sql
WATERMARK FOR
如果题目只问"创建水印的关键子句",写:
sql
WATERMARK FOR
即可。
11. Table API 滚动窗口:TUMBLE()
滚动窗口函数名:
sql
TUMBLE()
典型写法:
sql
SELECT
window_start,
window_end,
username,
COUNT(1) AS zongNum,
SUM(price) AS totalMoney
FROM TABLE(
TUMBLE(
TABLE table1,
DESCRIPTOR(event_time),
INTERVAL '60' SECOND
)
)
GROUP BY
window_start,
window_end,
username;
含义:
text
每 60 秒统计一次
每个用户名下的消费次数和消费金额
TUMBLE() 核心参数:
text
TABLE 表名
DESCRIPTOR(时间字段)
窗口大小
12. Table API 滑动窗口:HOP()
滑动窗口函数名:
sql
HOP()
需求:
text
每隔 10 秒
统计最近 1 分钟
每个用户的消费金额和消费次数
典型写法:
sql
SELECT
window_start,
window_end,
username,
COUNT(1) AS zongNum,
SUM(price) AS totalMoney
FROM TABLE(
HOP(
TABLE table1,
DESCRIPTOR(event_time),
INTERVAL '10' SECOND,
INTERVAL '60' SECOND
)
)
GROUP BY
window_start,
window_end,
username;
参数顺序:
text
HOP(
TABLE 表名,
DESCRIPTOR(时间字段),
滑动步长 slide,
窗口大小 size
)
与 DataStream API 中滑动窗口不同的是:
| API | 参数顺序 |
|---|---|
DataStream SlidingEventTimeWindows.of() |
size, slide |
Table API HOP() |
slide, size |
这是非常容易写反的一点。
13. 处理时间表
处理时间字段可写为:
sql
`event_time` AS PROCTIME()
例如:
sql
CREATE TABLE table1 (
`username` STRING,
`price` INT,
`event_time` AS PROCTIME()
) WITH (
...
);
含义:
不使用数据本身携带的时间戳,而使用 Flink 实际处理数据时的系统时间。
最后冲刺:高频易错点清单
-
keyBy()不是聚合算子。它是逻辑上的按键分区,结果是
KeyedStream。 -
keyBy()也不是物理分区算子。物理分区算子包括
shuffle、rebalance、rescale、broadcast、global等。 -
filter()返回true表示保留数据。 -
map()是一进一出;flatMap()是一进零到多出。 -
滚动窗口一个参数,滑动窗口两个参数。
-
DataStream 滑动窗口参数顺序:
size, slide。 -
Table API 的
HOP()参数顺序:slide, size。 -
windowAll()是非并行操作。 -
Watermark 不改变 Event Time。
它只决定窗口什么时候触发。
-
事件时间窗口触发条件:
text
窗口有数据
+
Watermark >= 窗口结束时间
-
allowedLateness()允许窗口在首次触发后继续接收一段时间的迟到数据。 -
Side Output 处理超出 Watermark 和允许迟到时间的超级迟到数据。
-
Operator State 按并行子任务隔离;Keyed State 按"并行子任务 + key"隔离。
-
Checkpoint 自动触发;Savepoint 手动触发。
-
从状态快照恢复作业要记住
-s参数。 -
toDataStream()适合追加型结果;有聚合更新时使用toChangelogStream()。 -
Kafka Connector 关键参数:地址、Topic、消费者组、Offset 策略、反序列化或格式。
-
JDBC 中 SQL 里的
?与PreparedStatement.setXxx(位置, 值)必须按字段顺序对应。 -
三种部署模式最重要的区别:集群生命周期、资源是否共享、
main()在 Client 还是 JobManager 执行。 -
Table API 创建表环境:
java
StreamTableEnvironment.create(env)
- 注册临时视图:
java
createTemporaryView()
- Table API 创建水印的关键词:
sql
WATERMARK FOR
- Table API 滚动窗口函数:
sql
TUMBLE()
- Table API 滑动窗口函数:
sql
HOP()