Apache Flink 完整知识总结与使用教程
目录
- [Flink 概述与背景](#Flink 概述与背景)
- 核心架构与组件
- 核心概念详解
- 3.1 流与批
- 3.2 时间语义
- 3.3 水位线(Watermark)
- 3.4 窗口(Window)
- 3.5 状态(State)
- 3.6 容错与 Checkpoint
- [DataStream API 使用教程](#DataStream API 使用教程)
- [Table API & Flink SQL](#Table API & Flink SQL)
- [Connectors 连接器](#Connectors 连接器)
- 部署模式
- 性能调优
- [Flink 2.0 新特性](#Flink 2.0 新特性)
- 生产最佳实践
- 常见问题与排错
- 生态系统与应用场景
1. Flink 概述与背景
1.1 什么是 Apache Flink
Apache Flink 是一个开源的、统一流处理与批处理 的分布式计算框架,由 Apache 软件基金会管理。其核心是用 Java 和 Scala 编写的分布式流数据处理引擎,以有状态计算为核心设计思想,能够高效地处理无界(Unbounded)和有界(Bounded)数据流。
Flink 的关键特性:
- 高吞吐、低延迟:毫秒级的端到端延迟,每秒处理数百万条事件
- 精确一次(Exactly-Once)语义:通过轻量级分布式快照算法保证状态一致性
- 流批一体:同一套 API 同时支持流式与批量数据处理
- 事件驱动:原生支持事件时间(Event Time)处理
- 强大的状态管理:支持超大规模有状态计算(TB 级别)
- 高可用与容错:内置 Checkpoint 机制,支持自动故障恢复
- 灵活部署:支持 Standalone、YARN、Kubernetes、Mesos 等多种环境
1.2 发展历史
| 时间 | 事件 |
|---|---|
| 2009 年 | 起源于德国柏林工业大学研究项目 "Stratosphere" |
| 2014 年 | 核心团队将项目捐赠给 Apache 基金会,更名为 Flink(德语"快"),同年成为 Apache 顶级项目 |
| 2014 年 | 创始团队成立 Data Artisans 公司 |
| 2016 年 | 发布 Flink 1.0,正式进入生产可用阶段 |
| 2018 年 | 阿里巴巴成立 Flink 中文社区,举办首届 Flink Forward Asia |
| 2019 年 | 阿里巴巴收购 Data Artisans(后更名 Ververica),并将内部 Blink 项目(150 万行核心代码)捐赠给社区 |
| 2023 年 | Flink 获得 SIGMOD 系统大奖;阿里捐赠 Flink CDC 及孵化 Paimon |
| 2024 年 | Flink Forward 回归柏林,庆祝开源 10 周年,发布 Flink 2.0 计划 |
| 2025 年 3 月 | Flink 2.0 正式发布,史诗级架构革新 |
1.3 Flink vs 其他框架
| 对比维度 | Flink | Spark Streaming | Storm |
|---|---|---|---|
| 处理模型 | 原生流处理(逐条) | 微批处理 | 原生流处理 |
| 延迟 | 毫秒级 | 秒级(批次间隔) | 毫秒级 |
| 吞吐量 | 非常高 | 高 | 中等 |
| 状态管理 | 非常强大(内置) | 较弱 | 需手动实现 |
| Exactly-Once | 支持 | 支持 | 不保证 |
| 流批一体 | 原生支持 | 需不同 API | 不支持 |
| 事件时间 | 完整支持 | 有限支持 | 不支持 |
| SQL 支持 | 成熟 | 成熟 | 无 |
2. 核心架构与组件
2.1 整体分层架构
Flink 采用分层架构设计,从下到上分为四层:
┌─────────────────────────────────────────────────────────┐
│ 高层 API 层(SQL / Table API / CEP) │
├─────────────────────────────────────────────────────────┤
│ 核心开发层(DataStream API / DataSet API) │
├─────────────────────────────────────────────────────────┤
│ Runtime 层(分布式流数据处理引擎) │
├─────────────────────────────────────────────────────────┤
│ 部署层(Local / Cluster / Cloud / K8s) │
└─────────────────────────────────────────────────────────┘
各层说明:
- 部署层:支持 Local(本地调试)、Standalone 集群、YARN、Kubernetes、Mesos 等
- Runtime 层:核心计算引擎,提供分布式数据流处理能力,包含容错、调度、内存管理等
- 核心开发层:DataStream API(流处理)和 DataSet API(批处理,已逐渐被 Table API 替代)
- 高层 API 层:Table API 和 SQL(声明式),CEP(复杂事件处理),ML、Gelly(图处理)
2.2 运行时组件
┌─────────────┐
│ Client │ 提交作业,监控
└──────┬──────┘
│
┌──────▼──────┐
│ JobManager │ 主控节点
│─────────────│
│ Dispatcher │ REST 接口,接收作业
│ Scheduler │ 资源调度
│ CheckpointCoordinator │
└──────┬──────┘
│
┌────────────────────┼────────────────────┐
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ TaskManager │ │ TaskManager │ │ TaskManager │
│─────────────│ │─────────────│ │─────────────│
│ Task Slot │ │ Task Slot │ │ Task Slot │
│ Task Slot │ │ Task Slot │ │ Task Slot │
└─────────────┘ └─────────────┘ └─────────────┘
JobManager(主节点)
- Dispatcher:提供 REST 接口接收客户端提交的 Job,运行 Web UI
- ResourceManager:负责 TaskManager 的资源注册与管理(Task Slot 的申请与释放)
- JobMaster:负责单个 Job 的执行,包括将 JobGraph 转换为 ExecutionGraph、调度 Task、协调 Checkpoint 等
- CheckpointCoordinator:触发并协调分布式 Checkpoint 的执行
TaskManager(工作节点)
- 执行具体的数据处理任务(Task)
- 每个 TaskManager 包含若干 Task Slot,每个 Slot 是一个独立的资源单元(内存隔离)
- Slot 之间可以共享(Task Slot Sharing),同一 Job 的不同算子可在同一 Slot 内运行,形成流水线
Task Slot 详解
- 每个 TaskManager 中 Slot 数量决定了最大并行任务数
- 推荐:Slot 数量 = CPU 核心数(避免资源竞争)
- Slot Sharing(Slot 共享):同一 Job 中不同算子的 SubTask 可以共享同一个 Slot,减少资源浪费,提高 CPU 利用率
2.3 数据流图(Dataflow Graph)
Flink 程序运行时会被映射为数据流图(Dataflow Graph),是一个有向无环图(DAG):
Source(并行度=2) → Map(并行度=2) → KeyBy → Window → Sink(并行度=1)
核心概念:
- StreamGraph:用户代码直接生成的逻辑流图
- JobGraph:将算子链接(Operator Chaining)后的优化图,提交给 JobManager
- ExecutionGraph:JobManager 基于并行度展开的物理执行图,是真正调度的基础
Operator Chaining(算子链):
将相邻的、满足条件的算子合并到同一个 Task 中执行,减少线程切换和序列化开销。满足链接的条件:
- 上下游算子并行度相同
- 下游算子只有一个输入(无 shuffle)
- 算子间通过本地转发(Forward)传输数据
3. 核心概念详解
3.1 流(Stream)与数据源/汇
无界流(Unbounded Stream):没有定义开始和结束,数据持续不断地产生,需要持续处理。典型例子:Kafka 消息流、日志流、传感器数据。
有界流(Bounded Stream):有定义的开始和结束,即批数据。如 HDFS 文件、数据库表。
java
// 流处理执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 批处理执行环境(Flink 1.12+ 推荐使用 DataStream + RuntimeExecutionMode)
env.setRuntimeMode(RuntimeExecutionMode.BATCH); // 批模式
env.setRuntimeMode(RuntimeExecutionMode.STREAMING); // 流模式(默认)
env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC); // 自动推断
3.2 时间语义(Time Semantics)
Flink 支持三种时间语义,这是理解 Flink 流处理的关键:
事件发生 数据进入Flink 算子处理数据
│ │ │
▼ ▼ ▼
Event Time Ingestion Time Processing Time
(事件时间) (摄入时间) (处理时间)
1. 事件时间(Event Time)
- 数据本身携带的时间戳,即事件真实发生的时间
- 推荐使用,能准确反映事件发生的先后顺序
- 可以有效应对乱序(Out-of-Order)和延迟数据
- 窗口计算结果确定,不依赖于处理速度
- 缺点:需要配置 Watermark,实现稍复杂
2. 处理时间(Processing Time)
- 数据被 Flink 算子处理时的系统时钟时间
- 实现最简单,延迟最低
- 缺点:结果不确定(与处理速度相关),不能处理乱序数据
3. 摄入时间(Ingestion Time)
- 数据进入 Flink Source 算子的时间
- 介于事件时间和处理时间之间,实践中价值有限
生产建议 :绝大多数场景使用事件时间,配合 Watermark 处理乱序数据。
3.3 水位线(Watermark)
3.3.1 Watermark 的本质
Watermark 是 Flink 为处理乱序数据(Out-of-Order Data)而提出的一种机制,本质上是一个时间戳,嵌入在数据流中随数据一起流动。
核心公式:
Watermark(t) = maxEventTime - allowedLateness(t)
当算子接收到时间为 T 的 Watermark 时,表示:不会再有时间戳 ≤ T 的数据到来(尽管这不是严格保证)。这个机制告知窗口算子可以安全地触发计算了。
3.3.2 Watermark 工作原理
原始事件流(乱序):
时间戳: [1, 3, 2, 7, 4, 11, 6, 9, 15, 8, ...]
↓ 允许延迟=2秒
生成 Watermark:
Watermark: [-, 1, 0, 5, 2, 9, 4, 7, 13, 6, ...]
当 Watermark ≥ 窗口结束时间 → 触发窗口计算
Watermark 与窗口触发规则:
- 当
Watermark ≥ 窗口EndTime,触发该窗口计算 - 窗口不会因为 Watermark 而立即删除(配合
allowedLateness可延迟)
3.3.3 Watermark 的传播
在并行流中,每个分区独立维护 Watermark:
- 单输入算子:直接转发收到的 Watermark
- 多输入算子:取所有输入分区 Watermark 的最小值作为算子当前 Watermark
这保证了任何算子在处理 Watermark T 时,所有时间戳 ≤ T 的数据已经就绪。
3.3.4 Watermark 代码实现
方式一:使用内置策略(推荐)
java
DataStream<String> stream = env.addSource(kafkaSource);
DataStream<Event> eventStream = stream
.map(/* 解析为 Event 对象 */)
.assignTimestampsAndWatermarks(
WatermarkStrategy
// 允许最大 5 秒乱序
.<Event>forBoundedOutOfOrderness(Duration.ofSeconds(5))
// 从事件中提取时间戳(毫秒)
.withTimestampAssigner((event, recordTimestamp) -> event.getTimestamp())
// 空闲分区超时(避免 Watermark 停滞)
.withIdleness(Duration.ofMinutes(1))
);
方式二:单调递增水印(有序流)
java
WatermarkStrategy.<Event>forMonotonousTimestamps()
.withTimestampAssigner((event, ts) -> event.getTimestamp());
方式三:自定义 WatermarkGenerator
java
public class CustomWatermarkGenerator implements WatermarkGenerator<Event> {
private final long maxOutOfOrderness;
private long maxTimestamp = Long.MIN_VALUE + maxOutOfOrderness + 1;
public CustomWatermarkGenerator(Duration maxOutOfOrderness) {
this.maxOutOfOrderness = maxOutOfOrderness.toMillis();
}
@Override
public void onEvent(Event event, long eventTimestamp, WatermarkOutput output) {
// 每个事件更新最大时间戳
maxTimestamp = Math.max(maxTimestamp, eventTimestamp);
}
@Override
public void onPeriodicEmit(WatermarkOutput output) {
// 周期性(默认200ms)生成 Watermark
output.emitWatermark(new Watermark(maxTimestamp - maxOutOfOrderness - 1));
}
}
3.3.5 迟到数据处理策略
事件流处理层次(推荐叠加使用):
1. Watermark(主要延迟容忍)
↓
2. allowedLateness(窗口关闭再延迟)
↓
3. sideOutput(最终兜底,收集超晚数据)
java
OutputTag<Event> lateTag = new OutputTag<Event>("late-data"){};
SingleOutputStreamOperator<Result> result = eventStream
.keyBy(Event::getKey)
.window(TumblingEventTimeWindows.of(Time.minutes(5)))
.allowedLateness(Time.minutes(1)) // 窗口关闭后再等1分钟
.sideOutputLateData(lateTag) // 超晚数据发往侧输出
.aggregate(new MyAggregator());
// 处理侧输出流
DataStream<Event> lateStream = result.getSideOutput(lateTag);
lateStream.addSink(/* 写入其他存储 */);
3.4 窗口(Window)
窗口是处理无界流的核心机制,将无限数据流切割为有限大小的"桶"进行计算。
3.4.1 窗口类型
1. 滚动窗口(Tumbling Window)
[0,5) [5,10) [10,15) ← 固定大小,不重叠,无缝覆盖
■■■■■ ■■■■■ ■■■■■
java
stream.keyBy(...)
.window(TumblingEventTimeWindows.of(Time.minutes(5)))
// 或基于处理时间:
.window(TumblingProcessingTimeWindows.of(Time.minutes(5)))
适用:统计每5分钟的 PV/UV、每小时的销售额。
2. 滑动窗口(Sliding Window)
窗口大小=10min,滑动步长=5min
[0,10) [5,15) [10,20) ← 重叠,同一数据会进入多个窗口
■■■■■■■■■■
■■■■■■■■■■
■■■■■■■■■■
java
stream.keyBy(...)
.window(SlidingEventTimeWindows.of(Time.minutes(10), Time.minutes(5)))
适用:过去10分钟内的实时指标监控,每5分钟刷新。
3. 会话窗口(Session Window)
以"空闲间隔"为边界,无固定大小
■■ ■■■■■■ ■■■
gap gap gap ← 超过 gap 就切分窗口
java
stream.keyBy(...)
.window(EventTimeSessionWindows.withGap(Time.minutes(30)))
适用:用户行为会话分析,超过30分钟无操作则会话结束。
4. 全局窗口(Global Window)
将所有数据分配到同一个窗口,必须自定义 Trigger,否则永远不触发。
java
stream.keyBy(...)
.window(GlobalWindows.create())
.trigger(CountTrigger.of(1000)) // 每1000条触发一次
5. 计数窗口(Count Window)
java
// 滚动计数窗口:每100个元素触发一次
stream.keyBy(...).countWindow(100);
// 滑动计数窗口:窗口100个,步长10个
stream.keyBy(...).countWindow(100, 10);
3.4.2 窗口函数
窗口函数定义了对窗口内数据的计算逻辑,分为增量聚合函数 和全量窗口函数:
增量聚合(推荐,内存高效)
java
// ReduceFunction:增量聚合,相同类型输入输出
stream.keyBy(Event::getKey)
.window(TumblingEventTimeWindows.of(Time.minutes(5)))
.reduce((e1, e2) -> new Event(e1.key, e1.value + e2.value));
// AggregateFunction:更灵活,支持不同类型输入/中间/输出
stream.keyBy(Event::getKey)
.window(TumblingEventTimeWindows.of(Time.minutes(5)))
.aggregate(new AggregateFunction<Event, Long, Double>() {
@Override
public Long createAccumulator() { return 0L; }
@Override
public Long add(Event value, Long accumulator) {
return accumulator + value.amount;
}
@Override
public Double getResult(Long accumulator) {
return accumulator / 100.0;
}
@Override
public Long merge(Long a, Long b) { return a + b; }
});
全量窗口函数(可获取窗口元数据)
java
// ProcessWindowFunction:可以访问窗口上下文(时间范围、状态等)
stream.keyBy(Event::getKey)
.window(TumblingEventTimeWindows.of(Time.minutes(5)))
.process(new ProcessWindowFunction<Event, Result, String, TimeWindow>() {
@Override
public void process(String key, Context context,
Iterable<Event> elements, Collector<Result> out) {
TimeWindow window = context.window();
long count = StreamSupport.stream(elements.spliterator(), false).count();
out.collect(new Result(key, count, window.getStart(), window.getEnd()));
}
});
// 推荐:AggregateFunction + ProcessWindowFunction 组合(增量聚合 + 窗口元数据)
stream.keyBy(Event::getKey)
.window(TumblingEventTimeWindows.of(Time.minutes(5)))
.aggregate(new MyAggregateFunction(), new MyProcessWindowFunction());
3.4.3 触发器(Trigger)
Trigger 决定窗口何时触发计算。内置触发器:
EventTimeTrigger:事件时间水印到达窗口结束时触发(事件时间窗口默认)ProcessingTimeTrigger:处理时间到达窗口结束时触发CountTrigger:元素数量达到阈值触发PurgingTrigger:包装其他触发器,触发后清空窗口数据
3.4.4 驱逐器(Evictor)
在触发器触发后、窗口函数执行前/后,从窗口中移除特定元素:
java
stream.keyBy(...)
.window(GlobalWindows.create())
.trigger(CountTrigger.of(100))
.evictor(CountEvictor.of(10)) // 保留最新10个元素
.process(new MyProcessWindowFunction());
3.5 状态(State)
3.5.1 状态的意义
在流计算中,状态(State)是中间计算结果的存储。例如:
- WordCount 中每个单词的累计计数
- 用户会话的事件集合
- 告警系统中超阈值的累计次数
相对于无状态处理(每条数据独立处理),有状态计算需要依赖之前的数据或中间结果。
3.5.2 状态分类
按管理方式分:
State
├── Managed State(托管状态,推荐)
│ ├── Keyed State(键控状态) ← 仅在 KeyedStream 中使用
│ │ ├── ValueState<T>
│ │ ├── ListState<T>
│ │ ├── MapState<UK, UV>
│ │ ├── ReducingState<T>
│ │ └── AggregatingState<IN, OUT>
│ └── Operator State(算子状态) ← 与并发算子实例绑定
│ ├── ListState<T>
│ ├── UnionListState<T>
│ └── BroadcastState<K, V>
└── Raw State(原生状态,不推荐) ← 自行管理序列化
Keyed State(键控状态)详解:
键控状态只能在 KeyedStream 中使用,每个 key 维护独立的状态实例:
java
public class CountAlertFunction extends RichFlatMapFunction<SensorReading, Alert> {
// 声明状态描述符
private transient ValueState<Integer> countState;
private static final int THRESHOLD = 3;
@Override
public void open(Configuration parameters) throws Exception {
// 在 open 方法中初始化状态
ValueStateDescriptor<Integer> descriptor =
new ValueStateDescriptor<>("count", Integer.class, 0);
// 可选:设置状态 TTL(自动过期清理)
StateTtlConfig ttlConfig = StateTtlConfig
.newBuilder(Time.hours(1))
.setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
.setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
.build();
descriptor.enableTimeToLive(ttlConfig);
countState = getRuntimeContext().getState(descriptor);
}
@Override
public void flatMap(SensorReading value, Collector<Alert> out) throws Exception {
Integer count = countState.value();
if (value.temperature > 100.0) {
count++;
if (count >= THRESHOLD) {
out.collect(new Alert(value.sensorId, "HIGH_TEMP"));
count = 0; // 重置
}
} else {
count = 0;
}
countState.update(count);
}
}
ListState 使用示例:
java
private transient ListState<Event> bufferState;
@Override
public void open(Configuration parameters) {
ListStateDescriptor<Event> descriptor =
new ListStateDescriptor<>("buffer", Event.class);
bufferState = getRuntimeContext().getListState(descriptor);
}
@Override
public void flatMap(Event event, Collector<Result> out) throws Exception {
bufferState.add(event);
List<Event> buffered = new ArrayList<>();
bufferState.get().forEach(buffered::add);
if (buffered.size() >= 10) {
// 处理 10 条数据
out.collect(process(buffered));
bufferState.clear(); // 清空状态
}
}
MapState 使用示例(适合大 Key 场景):
java
private transient MapState<String, Long> mapState;
@Override
public void open(Configuration parameters) {
MapStateDescriptor<String, Long> descriptor =
new MapStateDescriptor<>("wordCount", String.class, Long.class);
mapState = getRuntimeContext().getMapState(descriptor);
}
@Override
public void flatMap(String word, Collector<Tuple2<String, Long>> out) throws Exception {
Long count = mapState.get(word);
if (count == null) count = 0L;
mapState.put(word, count + 1);
out.collect(Tuple2.of(word, mapState.get(word)));
}
Operator State(算子状态):
算子状态与并行算子实例绑定,常用于 Source/Sink(如保存 Kafka Offset):
java
public class BufferingSink implements SinkFunction<Tuple2<String, Integer>>,
CheckpointedFunction {
private transient ListState<Tuple2<String, Integer>> checkpointedState;
private List<Tuple2<String, Integer>> bufferedElements;
private final int threshold;
@Override
public void initializeState(FunctionInitializationContext context) throws Exception {
ListStateDescriptor<Tuple2<String, Integer>> descriptor =
new ListStateDescriptor<>("buffered-elements", ...);
checkpointedState = context.getOperatorStateStore().getListState(descriptor);
// 从 Checkpoint 恢复时,恢复状态
if (context.isRestored()) {
for (Tuple2<String, Integer> element : checkpointedState.get()) {
bufferedElements.add(element);
}
}
}
@Override
public void snapshotState(FunctionSnapshotContext context) throws Exception {
// Checkpoint 触发时,将当前状态写入 checkpointedState
checkpointedState.clear();
for (Tuple2<String, Integer> element : bufferedElements) {
checkpointedState.add(element);
}
}
@Override
public void invoke(Tuple2<String, Integer> value, Context context) throws Exception {
bufferedElements.add(value);
if (bufferedElements.size() >= threshold) {
flush(); // 批量写入
}
}
}
广播状态(Broadcast State):
适用于将一个小的数据流广播到所有并行实例,实现动态规则更新:
java
// 规则流(低吞吐)
DataStream<Rule> ruleStream = ...;
// 数据流(高吞吐)
DataStream<Event> eventStream = ...;
// 定义广播状态描述符
MapStateDescriptor<String, Rule> ruleDescriptor =
new MapStateDescriptor<>("rules", String.class, Rule.class);
// 广播规则流
BroadcastStream<Rule> broadcastRules = ruleStream.broadcast(ruleDescriptor);
// 连接并处理
eventStream
.connect(broadcastRules)
.process(new BroadcastProcessFunction<Event, Rule, Result>() {
@Override
public void processElement(Event event, ReadOnlyContext ctx, Collector<Result> out) {
ReadOnlyBroadcastState<String, Rule> ruleState =
ctx.getBroadcastState(ruleDescriptor);
Rule rule = ruleState.get(event.getType());
if (rule != null && rule.matches(event)) {
out.collect(new Result(event, rule));
}
}
@Override
public void processBroadcastElement(Rule rule, Context ctx, Collector<Result> out) {
ctx.getBroadcastState(ruleDescriptor).put(rule.getName(), rule);
}
});
3.5.3 状态后端(State Backend)
状态后端决定状态存储的位置和方式:
| 状态后端 | 存储位置 | 适用场景 | 优缺点 |
|---|---|---|---|
| HashMapStateBackend | TaskManager JVM 堆内存 | 状态小、延迟敏感 | 速度快;受 JVM 堆限制,OOM 风险 |
| EmbeddedRocksDBStateBackend | TaskManager 本地磁盘(RocksDB) | 超大状态(TB 级) | 支持大状态;序列化开销,读写较慢 |
| ForStStateBackend(Flink 2.0+) | 分离存储(HDFS/S3) | 超大状态,解耦存储 | 新一代,支持远程状态 |
配置方式:
java
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 方式1:HashMapStateBackend(默认)
env.setStateBackend(new HashMapStateBackend());
env.getCheckpointConfig().setCheckpointStorage("hdfs:///flink/checkpoints");
// 方式2:RocksDB(推荐生产大状态场景)
env.setStateBackend(new EmbeddedRocksDBStateBackend());
env.getCheckpointConfig().setCheckpointStorage("hdfs:///flink/checkpoints");
// 也可以在 flink-conf.yaml 中全局配置
// state.backend: rocksdb
// state.checkpoints.dir: hdfs:///flink/checkpoints
RocksDB 调优参数(重要):
yaml
# flink-conf.yaml
state.backend.rocksdb.block.blocksize: 32KB
state.backend.rocksdb.thread.num: 4
state.backend.rocksdb.writebuffer.size: 64MB
state.backend.rocksdb.writebuffer.count: 3
state.backend.rocksdb.compaction.style: LEVEL
3.6 容错与 Checkpoint
3.6.1 Checkpoint 原理(Chandy-Lamport 算法)
Flink 采用轻量级异步分布式快照算法 (基于 Chandy-Lamport 算法),核心是 Barrier(屏障) 机制:
数据流:... [data] [data] [BARRIER n] [data] [data] [BARRIER n+1] ...
↑
CheckpointCoordinator 定期注入
触发流程:
1. CheckpointCoordinator 发送触发信号给所有 Source
2. Source 将 Barrier n 注入数据流(Barrier 与数据一起流动)
3. 算子收到 Barrier 后:
- 单输入:触发快照,向下游转发 Barrier
- 多输入(对齐模式):
a. 先到 Barrier 的输入分区阻塞(数据入缓冲区)
b. 等所有输入分区的 Barrier n 都到达
c. 触发本算子快照,释放阻塞分区,向下游发送 Barrier
4. 所有 Sink 都收到 Barrier 后,向 JobManager 确认
5. JobManager 确认 Checkpoint n 完成
Barrier 对齐 vs 非对齐(Unaligned Checkpoint):
| 模式 | 语义保证 | 延迟影响 | 适用场景 |
|---|---|---|---|
| 对齐(默认) | Exactly-Once | 背压时延迟大 | 常规场景 |
| 非对齐(Flink 1.11+) | Exactly-Once | 背压时延迟小 | 高背压场景 |
| At-Least-Once | At-Least-Once | 最低延迟 | 允许重复、极低延迟场景 |
3.6.2 Checkpoint 配置
java
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 开启 Checkpoint,每 5 秒触发一次
env.enableCheckpointing(5000);
CheckpointConfig config = env.getCheckpointConfig();
// 一致性语义:EXACTLY_ONCE(默认)或 AT_LEAST_ONCE
config.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
// 两次 Checkpoint 之间的最小间隔(防止 Checkpoint 堆积)
config.setMinPauseBetweenCheckpoints(1000);
// Checkpoint 超时时间(默认 10 分钟)
config.setCheckpointTimeout(60_000);
// 允许同时进行的最大 Checkpoint 数量(建议为 1)
config.setMaxConcurrentCheckpoints(1);
// 作业取消时保留 Checkpoint(用于手动恢复)
config.setExternalizedCheckpointCleanup(
ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
// 使用非对齐 Checkpoint(适合背压场景)
config.enableUnalignedCheckpoints();
// 配置存储位置(生产必须配置持久化路径)
config.setCheckpointStorage("hdfs:///flink/checkpoints");
3.6.3 Savepoint
Savepoint 是用户手动触发的特殊 Checkpoint,用于计划性维护(如版本升级、并行度调整、代码更新):
bash
# 触发 Savepoint
bin/flink savepoint <jobId> [targetDirectory] [-yid <yarnAppId>]
# 从 Savepoint 恢复启动
bin/flink run -s hdfs:///flink/savepoints/savepoint-xxx /path/to/job.jar
# 删除 Savepoint
bin/flink savepoint -d hdfs:///flink/savepoints/savepoint-xxx
Checkpoint vs Savepoint 对比:
| 对比项 | Checkpoint | Savepoint |
|---|---|---|
| 触发方式 | 自动周期触发 | 用户手动触发 |
| 目的 | 故障自动恢复 | 计划性备份/迁移 |
| 生命周期 | Flink 自动管理 | 用户自行管理 |
| 格式 | 后端特定格式(可增量) | 可移植格式 |
| 扩缩容支持 | 有限 | 完整支持 |
| 代码升级 | 通常不支持 | 支持(兼容变更) |
3.6.4 重启策略
java
// 1. 固定延迟重启:最多重试 3 次,每次间隔 10 秒
env.setRestartStrategy(RestartStrategies.fixedDelayRestart(3, Duration.ofSeconds(10)));
// 2. 失败率重启:5 分钟内失败超过 3 次才停止
env.setRestartStrategy(RestartStrategies.failureRateRestart(
3, Duration.ofMinutes(5), Duration.ofSeconds(10)));
// 3. 不重启(直接失败)
env.setRestartStrategy(RestartStrategies.noRestart());
// 4. 无限重启(配合退避策略)
env.setRestartStrategy(RestartStrategies.exponentialDelayRestart(
Duration.ofSeconds(1), Duration.ofMinutes(5), 1.5, Duration.ofMinutes(10), 0));
3.6.5 端到端精确一次(End-to-End Exactly-Once)
单独 Flink 的 Exactly-Once 只保证内部状态,要实现端到端还需要 Sink 支持两阶段提交(2PC):
Flink Exactly-Once 端到端流程:
Source(可重放,如 Kafka)
↓ 读取偏移量作为状态
Flink 内部(Checkpoint 保证状态一致性)
↓ 预提交(Pre-Commit)
Sink(支持事务,如 Kafka、MySQL)
↓ 当 Checkpoint 完成 → 正式提交(Commit)
↓ 当 Checkpoint 失败 → 回滚(Abort)
Flink 提供 TwoPhaseCommitSinkFunction 抽象基类供自定义实现。
4. DataStream API 使用教程
4.1 程序结构
每个 Flink 程序都遵循相同的结构:
java
// 1. 获取执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 2. 设置并行度(可选)
env.setParallelism(4);
// 3. 读取数据源(Source)
DataStream<String> source = env.addSource(...);
// 4. 数据转换(Transformations)
DataStream<Tuple2<String, Integer>> result = source
.flatMap(...)
.filter(...)
.keyBy(...)
.sum(...);
// 5. 写入数据汇(Sink)
result.addSink(...);
// 6. 触发执行
env.execute("My Flink Job");
4.2 数据源(Source)
内置 Source:
java
// 从集合创建(测试用)
DataStream<Integer> stream = env.fromCollection(Arrays.asList(1, 2, 3, 4, 5));
// 从元素创建
DataStream<String> stream = env.fromElements("hello", "world");
// 从文件读取
DataStream<String> stream = env.readTextFile("hdfs:///path/to/file");
// Socket 数据源(测试用)
DataStream<String> stream = env.socketTextStream("localhost", 9999);
// 自定义 Source(实现 SourceFunction)
DataStream<SensorReading> stream = env.addSource(new SensorSource());
Kafka Source(推荐方式 - 新版 Source API):
java
KafkaSource<String> kafkaSource = KafkaSource.<String>builder()
.setBootstrapServers("kafka:9092")
.setTopics("my-topic")
.setGroupId("flink-consumer-group")
.setStartingOffsets(OffsetsInitializer.earliest())
.setValueOnlyDeserializer(new SimpleStringSchema())
.build();
DataStream<String> stream = env.fromSource(
kafkaSource,
WatermarkStrategy
.<String>forBoundedOutOfOrderness(Duration.ofSeconds(5))
.withIdleness(Duration.ofMinutes(1)),
"Kafka Source"
);
4.3 核心转换算子(Transformation)
java
// map:一对一转换
DataStream<Integer> lengths = words.map(String::length);
// flatMap:一对多转换
DataStream<String> splitWords = sentences.flatMap(
(String line, Collector<String> out) ->
Arrays.stream(line.split(" ")).forEach(out::collect)
);
// filter:过滤
DataStream<Integer> positives = numbers.filter(n -> n > 0);
// keyBy:按 Key 分组(产生 KeyedStream)
KeyedStream<Event, String> keyed = events.keyBy(Event::getUserId);
// reduce:对 KeyedStream 进行增量聚合
DataStream<SensorReading> maxTemp = readings
.keyBy(SensorReading::getSensorId)
.reduce((r1, r2) -> r1.temperature > r2.temperature ? r1 : r2);
// aggregations:快捷聚合(min/max/sum)
DataStream<Tuple2<String, Integer>> summed = stream
.keyBy(t -> t.f0)
.sum(1); // 对 Tuple 的第二个字段求和
// union:合并多个相同类型的流
DataStream<Event> merged = stream1.union(stream2, stream3);
// connect:连接两个不同类型的流
ConnectedStreams<Event, Control> connected = eventStream.connect(controlStream);
connected.map(new CoMapFunction<Event, Control, Result>() {
@Override public Result map1(Event event) { return process(event); }
@Override public Result map2(Control control) { return process(control); }
});
// split / sideOutput:侧输出(分流)
OutputTag<Event> highTag = new OutputTag<Event>("high"){};
SingleOutputStreamOperator<Event> mainStream = events
.process(new ProcessFunction<Event, Event>() {
@Override
public void processElement(Event event, Context ctx, Collector<Event> out) {
if (event.value > 100) {
ctx.output(highTag, event); // 发往侧输出
} else {
out.collect(event); // 主流输出
}
}
});
DataStream<Event> highStream = mainStream.getSideOutput(highTag);
// iterate:迭代(处理有环数据流)
IterativeStream<Long> iteration = stream.iterate();
DataStream<Long> iterationBody = iteration.map(x -> x > 0 ? x - 1 : x);
DataStream<Long> feedback = iterationBody.filter(x -> x > 0);
iteration.closeWith(feedback);
DataStream<Long> output = iterationBody.filter(x -> x <= 0);
4.4 ProcessFunction(底层 API)
ProcessFunction 是最底层、最灵活的 API,可以访问时间、状态和定时器:
java
public class AlertProcessFunction extends KeyedProcessFunction<String, SensorReading, Alert> {
private ValueState<Long> timerState; // 存储定时器时间戳
@Override
public void open(Configuration parameters) {
timerState = getRuntimeContext().getState(
new ValueStateDescriptor<>("timer", Long.class));
}
@Override
public void processElement(SensorReading reading, Context ctx, Collector<Alert> out)
throws Exception {
// 判断温度是否超阈值
if (reading.temperature > 100.0) {
// 如果没有注册过定时器,注册一个 10 秒后触发的定时器
if (timerState.value() == null) {
long timer = ctx.timerService().currentProcessingTime() + 10_000;
ctx.timerService().registerProcessingTimeTimer(timer);
timerState.update(timer);
}
} else {
// 温度恢复正常,取消定时器
Long timer = timerState.value();
if (timer != null) {
ctx.timerService().deleteProcessingTimeTimer(timer);
timerState.clear();
}
}
}
@Override
public void onTimer(long timestamp, OnTimerContext ctx, Collector<Alert> out) {
// 定时器触发:持续超温 10 秒,发出告警
out.collect(new Alert(ctx.getCurrentKey(), "SUSTAINED_HIGH_TEMP", timestamp));
timerState.clear();
}
}
ProcessFunction 家族:
| 函数类型 | 适用场景 |
|---|---|
ProcessFunction |
非 Keyed 流,访问当前元素和处理时间定时器 |
KeyedProcessFunction |
Keyed 流,访问状态、定时器(事件/处理时间) |
CoProcessFunction |
两个流 connect 后使用 |
ProcessWindowFunction |
窗口计算,访问窗口元数据 |
ProcessAllWindowFunction |
非 Keyed 窗口计算 |
4.5 双流 Join
基于窗口的 Join:
java
// Window Join(两个流在同一窗口内 Join)
stream1
.join(stream2)
.where(e1 -> e1.key)
.equalTo(e2 -> e2.key)
.window(TumblingEventTimeWindows.of(Time.hours(1)))
.apply((e1, e2) -> new Result(e1, e2));
Interval Join(区间 Join):
java
// e1 的 eventTime 在 [e2.eventTime - 5min, e2.eventTime + 10min] 范围内
stream1
.keyBy(e1 -> e1.orderId)
.intervalJoin(stream2.keyBy(e2 -> e2.orderId))
.between(Time.minutes(-5), Time.minutes(10))
.process((e1, e2, ctx, out) -> out.collect(new Result(e1, e2)));
4.6 数据汇(Sink)
java
// 输出到控制台
stream.print();
stream.printToErr();
// 写入文件
stream.addSink(StreamingFileSink
.forRowFormat(new Path("hdfs:///output"), new SimpleStringEncoder<>())
.withRollingPolicy(DefaultRollingPolicy.builder()
.withRolloverInterval(Duration.ofMinutes(15))
.withInactivityInterval(Duration.ofMinutes(5))
.withMaxPartSize(MemorySize.ofMebiBytes(1024))
.build())
.build());
// Kafka Sink(新版)
KafkaSink<String> kafkaSink = KafkaSink.<String>builder()
.setBootstrapServers("kafka:9092")
.setRecordSerializer(KafkaRecordSerializationSchema.builder()
.setTopic("output-topic")
.setValueSerializationSchema(new SimpleStringSchema())
.build())
.setDeliveryGuarantee(DeliveryGuarantee.EXACTLY_ONCE)
.build();
stream.sinkTo(kafkaSink);
5. Table API & Flink SQL
5.1 概述
Table API 和 Flink SQL 是 Flink 的声明式 API,位于 DataStream API 之上,提供更高层次的抽象:
- Table API:基于 Java/Python/Scala 的链式调用 DSL,类似于 SQL 的表达式语言
- Flink SQL:标准 SQL 语法,适合快速开发、降低技术门槛
- 两者可混用,也可与 DataStream API 互转
java
// 创建 TableEnvironment
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
5.2 表的创建与注册
从 DDL 创建(推荐):
sql
-- 创建 Kafka Source 表
CREATE TABLE orders (
order_id BIGINT,
user_id STRING,
product STRING,
amount DECIMAL(10, 2),
order_time TIMESTAMP(3),
WATERMARK FOR order_time AS order_time - INTERVAL '5' SECOND
) WITH (
'connector' = 'kafka',
'topic' = 'orders',
'properties.bootstrap.servers' = 'kafka:9092',
'properties.group.id' = 'flink-group',
'format' = 'json',
'scan.startup.mode' = 'earliest-offset'
);
-- 创建 MySQL Sink 表
CREATE TABLE order_summary (
product STRING,
total_amount DECIMAL(10, 2),
window_start TIMESTAMP(3),
window_end TIMESTAMP(3),
PRIMARY KEY (product, window_start) NOT ENFORCED
) WITH (
'connector' = 'jdbc',
'url' = 'jdbc:mysql://mysql:3306/flink_db',
'table-name' = 'order_summary',
'username' = 'root',
'password' = '123456'
);
从 DataStream 转换:
java
DataStream<Row> dataStream = ...;
Table table = tableEnv.fromDataStream(dataStream,
Schema.newBuilder()
.column("user_id", DataTypes.STRING())
.column("amount", DataTypes.DECIMAL(10, 2))
.columnByExpression("proc_time", "PROCTIME()")
.build());
// 注册为临时视图
tableEnv.createTemporaryView("my_table", table);
// 将 Table 转回 DataStream
DataStream<Row> resultStream = tableEnv.toDataStream(resultTable);
5.3 Flink SQL 核心语法
聚合查询:
sql
-- 分组聚合
SELECT user_id, COUNT(*) AS order_count, SUM(amount) AS total_amount
FROM orders
GROUP BY user_id;
-- 滚动窗口聚合(TUMBLE)
SELECT
product,
TUMBLE_START(order_time, INTERVAL '1' HOUR) AS window_start,
TUMBLE_END(order_time, INTERVAL '1' HOUR) AS window_end,
SUM(amount) AS total_amount
FROM orders
GROUP BY product, TUMBLE(order_time, INTERVAL '1' HOUR);
-- 滑动窗口聚合(HOP)
SELECT
product,
HOP_START(order_time, INTERVAL '5' MINUTE, INTERVAL '1' HOUR) AS window_start,
SUM(amount) AS total_amount
FROM orders
GROUP BY product, HOP(order_time, INTERVAL '5' MINUTE, INTERVAL '1' HOUR);
-- 会话窗口聚合(SESSION)
SELECT
user_id,
SESSION_START(order_time, INTERVAL '30' MINUTE) AS session_start,
COUNT(*) AS event_count
FROM orders
GROUP BY user_id, SESSION(order_time, INTERVAL '30' MINUTE);
窗口 TVF(Flink 1.13+ 推荐方式):
sql
-- 使用 TUMBLE TVF(Table-Valued Function)
SELECT
product,
window_start,
window_end,
SUM(amount) AS total_amount
FROM TABLE(
TUMBLE(TABLE orders, DESCRIPTOR(order_time), INTERVAL '1' HOUR)
)
GROUP BY product, window_start, window_end;
流表 Join(Temporal Join / Lookup Join):
sql
-- Lookup Join(维表关联,适合实时查询维表)
SELECT o.order_id, o.user_id, u.username, o.amount
FROM orders AS o
LEFT JOIN users FOR SYSTEM_TIME AS OF o.proc_time AS u
ON o.user_id = u.user_id;
-- Interval Join(双流时间区间 Join)
SELECT o.order_id, p.payment_id
FROM orders o, payments p
WHERE o.order_id = p.order_id
AND p.pay_time BETWEEN o.order_time AND o.order_time + INTERVAL '1' HOUR;
TopN 查询:
sql
-- 每个品类销售额前 3 的商品(OVER + ROW_NUMBER)
SELECT * FROM (
SELECT
product,
category,
total_amount,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY total_amount DESC) AS rn
FROM order_summary
)
WHERE rn <= 3;
OVER 窗口(滚动聚合):
sql
SELECT
user_id,
order_time,
amount,
SUM(amount) OVER (
PARTITION BY user_id
ORDER BY order_time
RANGE BETWEEN INTERVAL '1' HOUR PRECEDING AND CURRENT ROW
) AS running_total
FROM orders;
5.4 UDF(用户自定义函数)
ScalarFunction(标量函数,一进一出):
java
public class HashFunction extends ScalarFunction {
public String eval(String input) {
return DigestUtils.md5Hex(input);
}
}
// 注册
tableEnv.createTemporaryFunction("HASH", HashFunction.class);
// 使用
tableEnv.executeSql("SELECT HASH(user_id) FROM orders");
TableFunction(表值函数,一进多出):
java
@FunctionHint(output = @DataTypeHint("ROW<word STRING, length INT>"))
public class SplitFunction extends TableFunction<Row> {
public void eval(String str) {
for (String word : str.split(" ")) {
collect(Row.of(word, word.length()));
}
}
}
// 使用
SELECT s.word, s.length
FROM sentences, LATERAL TABLE(SplitFunction(sentence)) AS s(word, length);
AggregateFunction(自定义聚合函数):
java
public class WeightedAvgFunction extends AggregateFunction<Double, WeightedAvg.WeightedAvgAccum> {
public static class WeightedAvgAccum {
public long sum = 0;
public int count = 0;
}
@Override
public WeightedAvgAccum createAccumulator() {
return new WeightedAvgAccum();
}
public void accumulate(WeightedAvgAccum acc, long value, int weight) {
acc.sum += value * weight;
acc.count += weight;
}
@Override
public Double getValue(WeightedAvgAccum acc) {
return acc.count == 0 ? null : (double) acc.sum / acc.count;
}
}
5.5 Catalog 与 元数据管理
java
// 使用 HiveCatalog 接入 Hive 元数据
HiveCatalog hiveCatalog = new HiveCatalog(
"myhive", // catalog 名称
"default", // 默认数据库
"/path/to/hive/conf"
);
tableEnv.registerCatalog("myhive", hiveCatalog);
tableEnv.useCatalog("myhive");
// 查询 Hive 表
tableEnv.executeSql("SELECT * FROM hive_table WHERE dt = '2024-01-01'");
6. Connectors 连接器
6.1 Kafka Connector
Kafka 是 Flink 最常用的 Source 和 Sink。
依赖(Maven):
xml
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka</artifactId>
<version>3.x.x-1.18</version>
</dependency>
Kafka Source(DataStream API):
java
KafkaSource<String> source = KafkaSource.<String>builder()
.setBootstrapServers("kafka1:9092,kafka2:9092")
.setTopics("orders")
.setGroupId("flink-consumer-group")
.setStartingOffsets(OffsetsInitializer.committedOffsets(OffsetResetStrategy.EARLIEST))
.setValueOnlyDeserializer(new SimpleStringSchema())
// 自定义反序列化
.setDeserializer(KafkaRecordDeserializationSchema.valueOnly(new JsonDeserializationSchema<>(Order.class)))
.build();
Kafka Sink(Exactly-Once):
java
KafkaSink<Order> sink = KafkaSink.<Order>builder()
.setBootstrapServers("kafka:9092")
.setRecordSerializer(KafkaRecordSerializationSchema.builder()
.setTopic("output-orders")
.setKeySerializationSchema(new OrderKeySerializer())
.setValueSerializationSchema(new JsonSerializationSchema<>())
.build())
.setDeliveryGuarantee(DeliveryGuarantee.EXACTLY_ONCE)
.setTransactionalIdPrefix("flink-txn")
.build();
Flink SQL Kafka 表:
sql
CREATE TABLE kafka_orders (
order_id BIGINT,
user_id STRING,
amount DECIMAL(10, 2),
ts TIMESTAMP(3) METADATA FROM 'timestamp', -- Kafka 消息时间戳
partition BIGINT METADATA VIRTUAL, -- Kafka 分区
WATERMARK FOR ts AS ts - INTERVAL '5' SECOND
) WITH (
'connector' = 'kafka',
'topic' = 'orders',
'properties.bootstrap.servers' = 'kafka:9092',
'properties.group.id' = 'flink-sql-group',
'scan.startup.mode' = 'group-offsets',
'format' = 'json',
'json.ignore-parse-errors' = 'true'
);
6.2 JDBC Connector
sql
-- Flink SQL JDBC Sink
CREATE TABLE mysql_sink (
user_id STRING,
total DECIMAL(10, 2),
PRIMARY KEY (user_id) NOT ENFORCED
) WITH (
'connector' = 'jdbc',
'url' = 'jdbc:mysql://localhost:3306/flink_db?serverTimezone=UTC',
'table-name' = 'user_stats',
'username' = 'root',
'password' = '123456',
'sink.buffer-flush.max-rows' = '1000',
'sink.buffer-flush.interval' = '1s'
);
6.3 Flink CDC(Change Data Capture)
Flink CDC 允许直接从数据库捕获变更数据(Insert/Update/Delete):
sql
-- MySQL CDC Source(无需 Kafka)
CREATE TABLE mysql_orders (
order_id BIGINT PRIMARY KEY NOT ENFORCED,
user_id STRING,
amount DECIMAL(10, 2),
status STRING
) WITH (
'connector' = 'mysql-cdc',
'hostname' = 'mysql',
'port' = '3306',
'username' = 'flink',
'password' = '123456',
'database-name' = 'shop',
'table-name' = 'orders'
);
支持 MySQL、PostgreSQL、Oracle、MongoDB、SQL Server 等数据源。
6.4 Elasticsearch Connector
sql
CREATE TABLE es_sink (
user_id STRING,
order_cnt BIGINT,
PRIMARY KEY (user_id) NOT ENFORCED
) WITH (
'connector' = 'elasticsearch-7',
'hosts' = 'http://elasticsearch:9200',
'index' = 'user_order_stats',
'sink.flush-on-checkpoint' = 'true',
'sink.bulk-flush.max-actions' = '1000',
'sink.bulk-flush.interval' = '1s'
);
6.5 HBase Connector
sql
CREATE TABLE hbase_dim (
rowkey STRING,
cf ROW<name STRING, age INT>
) WITH (
'connector' = 'hbase-2.2',
'table-name' = 'user_info',
'zookeeper.quorum' = 'zk1,zk2,zk3:2181'
);
6.6 FileSystem Connector
sql
-- 分区文件 Sink(支持 Hive 分区格式)
CREATE TABLE fs_sink (
user_id STRING,
amount DECIMAL(10, 2),
dt STRING -- 分区字段
) PARTITIONED BY (dt)
WITH (
'connector' = 'filesystem',
'path' = 'hdfs:///data/output',
'format' = 'parquet',
'sink.partition-commit.trigger' = 'process-time',
'sink.partition-commit.delay' = '1 h',
'sink.partition-commit.policy.kind' = 'success-file'
);
7. 部署模式
7.1 Session 模式
预先启动一个长期运行的 Flink 集群,多个 Job 共享资源:
bash
# 启动 Session 集群(Standalone)
bin/start-cluster.sh
# 提交 Job 到 Session
bin/flink run -c com.example.MyJob my-job.jar --param1 value1
# 查看运行中的 Job
bin/flink list
# 取消 Job
bin/flink cancel <jobId>
优点 :Job 启动快,资源共享
缺点:Job 间资源隔离差,一个 Job 异常可能影响其他 Job
7.2 Per-Job 模式(已废弃,Flink 1.15+)
每个 Job 独享一个集群,Job 完成后集群释放。已被 Application 模式替代。
7.3 Application 模式(推荐生产使用)
bash
# YARN Application 模式
bin/flink run-application \
-t yarn-application \
-Djobmanager.memory.process.size=2048m \
-Dtaskmanager.memory.process.size=4096m \
-Dtaskmanager.numberOfTaskSlots=4 \
-c com.example.MyJob \
hdfs:///jars/my-job.jar
# Kubernetes Application 模式
bin/flink run-application \
-t kubernetes-application \
-Dkubernetes.cluster-id=my-flink-cluster \
-Dkubernetes.container.image=my-flink-image:latest \
-Dkubernetes.jobmanager.cpu=1 \
-Dkubernetes.taskmanager.cpu=2 \
-c com.example.MyJob \
local:///opt/flink/usrlib/my-job.jar
优点:资源完全隔离,main() 方法在 JobManager 上执行,不需要下载 JAR
7.4 YARN 部署
bash
# YARN Session 模式:提前启动集群
bin/yarn-session.sh \
-nm flink-session \
-jm 2048 \
-tm 4096 \
-s 4 \
-d # 后台运行
# 提交 Job 到 YARN Session
bin/flink run -c com.example.MyJob my-job.jar
# YARN Per-Job/Application 模式
bin/flink run-application -t yarn-application ...
7.5 Kubernetes 部署
Native Kubernetes(推荐):
yaml
# flink-configuration-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: flink-config
data:
flink-conf.yaml: |
jobmanager.rpc.address: flink-jobmanager
taskmanager.numberOfTaskSlots: 4
blob.server.port: 6124
jobmanager.rpc.port: 6123
taskmanager.rpc.port: 6122
state.backend: rocksdb
state.checkpoints.dir: s3://my-bucket/flink/checkpoints
high-availability: org.apache.flink.kubernetes.highavailability.KubernetesHaServicesFactory
high-availability.storageDir: s3://my-bucket/flink/ha
Flink Kubernetes Operator(推荐方式):
yaml
# flink-deployment.yaml
apiVersion: flink.apache.org/v1beta1
kind: FlinkDeployment
metadata:
name: my-flink-job
spec:
image: flink:1.18
flinkVersion: v1_18
flinkConfiguration:
taskmanager.numberOfTaskSlots: "4"
state.backend: rocksdb
state.checkpoints.dir: s3://bucket/checkpoints
execution.checkpointing.interval: "5000"
serviceAccount: flink
jobManager:
resource:
memory: "2048m"
cpu: 1
taskManager:
resource:
memory: "4096m"
cpu: 2
job:
jarURI: s3://bucket/jars/my-job.jar
entryClass: com.example.MyJob
parallelism: 8
upgradeMode: stateless
7.6 高可用(HA)配置
ZooKeeper HA:
yaml
# flink-conf.yaml
high-availability: zookeeper
high-availability.zookeeper.quorum: zk1:2181,zk2:2181,zk3:2181
high-availability.zookeeper.path.root: /flink
high-availability.cluster-id: /my-cluster
high-availability.storageDir: hdfs:///flink/ha
Kubernetes HA(推荐):
yaml
high-availability: org.apache.flink.kubernetes.highavailability.KubernetesHaServicesFactory
high-availability.storageDir: s3://bucket/flink/ha
kubernetes.cluster-id: my-cluster
8. 性能调优
8.1 并行度设置
java
// 全局并行度
env.setParallelism(8);
// 算子级别并行度(覆盖全局)
stream.map(...).setParallelism(4);
stream.keyBy(...).sum(1).setParallelism(16);
// SQL 级别
tableEnv.getConfig().getConfiguration()
.setInteger("parallelism.default", 8);
并行度设置原则:
- Source 并行度建议与 Kafka 分区数一致
- CPU 密集型算子并行度 ≈ CPU 核心数
- 网络 I/O 密集型可适当增大
- 总 Slot 数 = 最大算子并行度(考虑 Slot 共享)
8.2 内存管理
TaskManager 内存模型:
TaskManager 进程内存
├── Flink 总内存(taskmanager.memory.flink.size)
│ ├── 框架堆内存(128MB,固定)
│ ├── 框架堆外内存(128MB,固定)
│ ├── Task 堆内存(taskmanager.memory.task.heap.size)
│ ├── Task 堆外内存(taskmanager.memory.task.off-heap.size)
│ ├── 网络内存(taskmanager.memory.network.fraction,默认10%)
│ └── 托管内存(taskmanager.memory.managed.fraction,默认40%)
│ └── RocksDB、批排序等使用
└── JVM 额外内存(Metaspace + Overhead)
推荐配置(4GB TaskManager):
yaml
taskmanager.memory.process.size: 4096m
taskmanager.memory.flink.size: 3072m
taskmanager.memory.managed.fraction: 0.4
taskmanager.memory.network.fraction: 0.1
8.3 网络与背压
背压(Backpressure) 是下游算子处理速度跟不上上游速度时,反向施加的压力,可通过 Flink Web UI 中的 Backpressure 面板查看。
背压常见原因与处理:
| 原因 | 解决方案 |
|---|---|
| 下游 Sink 写入慢 | 增加 Sink 并行度,启用批量写入 |
| CPU 密集型算子 | 增加并行度,优化算法 |
| 网络 I/O 成为瓶颈 | 调整网络缓冲区大小 |
| Checkpoint 时间过长 | 启用非对齐 Checkpoint,增大 Checkpoint 间隔 |
| GC 频繁 | 调整堆内存,使用 RocksDB |
网络缓冲区调优:
yaml
taskmanager.network.memory.fraction: 0.1
taskmanager.network.memory.min: 64mb
taskmanager.network.memory.max: 1gb
taskmanager.network.numberOfBuffers: 2048 # 旧版
8.4 Checkpoint 调优
java
// 开启非对齐 Checkpoint(高背压场景,减少延迟)
env.getCheckpointConfig().enableUnalignedCheckpoints();
// RocksDB 增量 Checkpoint(大状态场景,减少 Checkpoint 大小)
EmbeddedRocksDBStateBackend backend = new EmbeddedRocksDBStateBackend(true); // true=增量
env.setStateBackend(backend);
// 避免 Checkpoint 与 Checkpoint 之间重叠(并发数=1)
env.getCheckpointConfig().setMaxConcurrentCheckpoints(1);
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(30_000); // 30秒
8.5 数据倾斜处理
Key 倾斜的解决方案:
java
// 方案1:两阶段聚合(局部聚合 + 全局聚合)
stream
.map(event -> {
// 添加随机后缀,打散热点 Key
int suffix = new Random().nextInt(100);
return new Tuple2<>(event.key + "_" + suffix, event.value);
})
.keyBy(t -> t.f0)
.sum(1)
// 去掉后缀,进行全局聚合
.map(t -> new Tuple2<>(t.f0.split("_")[0], t.f1))
.keyBy(t -> t.f0)
.sum(1);
// 方案2:使用 Slot 共享组隔离热点算子
stream.keyBy(...)
.process(new HotKeyProcessFunction())
.slotSharingGroup("hot-key-group"); // 独立资源组
8.6 GC 调优建议
yaml
# JVM 参数(taskmanager.jvm-opts 或 env.java.opts.taskmanager)
env.java.opts.taskmanager: >-
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=16m
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/opt/flink/log/gc.log
8.7 算子链(Chain)控制
java
// 禁用算子链(用于调试或特殊场景)
stream.map(...).disableChaining();
// 从当前算子开始一个新链
stream.map(...).startNewChain();
// 全局禁用算子链
env.disableOperatorChaining();
9. Flink 2.0 新特性
Flink 2.0 于 2025 年 3 月正式发布,是 2016 年以来最大的一次架构升级。
9.1 主要变化
1. 最低 Java 版本升级:Java 8 支持被移除,最低要求 Java 11,推荐 Java 17/21。
2. 物化表(Materialized Table)
物化表是 Flink 2.0 最重要的特性之一,允许用单个声明同时管理实时数据 和历史数据:
sql
-- 创建物化表(同时支持流/批查询)
CREATE MATERIALIZED TABLE order_stats
FRESHNESS = INTERVAL '1' MINUTE -- 数据刷新频率
AS
SELECT
product,
window_start,
window_end,
SUM(amount) AS total_amount
FROM TABLE(TUMBLE(TABLE orders, DESCRIPTOR(order_time), INTERVAL '1' HOUR))
GROUP BY product, window_start, window_end;
3. ForStStateBackend(新一代状态后端)
基于 RocksDB 但支持分离存储(状态存在远端如 S3/HDFS),解耦状态与计算资源,支持超大规模状态:
yaml
state.backend: forst
state.backend.forst.remote.dir: s3://bucket/flink/state
4. 批处理性能大幅提升
- 引入自适应 Broadcast Join
- 自动优化数据倾斜的 Join
- 在 10TB TPC-DS 基准测试中,性能较上个版本显著提升
5. 原生 AI 算子
支持流式数据与 TensorFlow/PyTorch 模型无缝对接:
java
// AI 推理算子(示意)
stream.transform("ai-inference",
TypeInformation.of(PredictionResult.class),
new AIInferenceOperator(modelPath));
6. 深度集成 Apache Paimon
Flink + Paimon 形成**流式湖仓(Streaming Lakehouse)**架构:
- 支持嵌套 projection 下推
- Lookup Join 性能大幅提升
- 通过 Flink SQL 直接执行 Paimon 维护操作
7. API 清理与废弃
- 正式移除 DataSet API(已废弃多个版本)
- 统一使用 DataStream API 处理批流
- 移除
MemoryStateBackend和FsStateBackend(已被HashMapStateBackend替代)
9.2 升级建议
Flink 1.x → 2.0 迁移步骤:
1. 升级 Java 至 11+
2. 更新 DataSet API → DataStream API(BATCH 模式)
3. 更新 StateBackend 配置
MemoryStateBackend → HashMapStateBackend
FsStateBackend → HashMapStateBackend + Checkpoint 存储
4. 更新 Watermark API(旧版 AssignerWithPeriodicWatermarks → WatermarkStrategy)
5. 检查并更新连接器版本(connector API 有变化)
6. 通过 Savepoint 从旧版本迁移状态
10. 生产最佳实践
10.1 作业健壮性
java
// 1. 必须开启 Checkpoint
env.enableCheckpointing(60_000); // 1分钟
env.getCheckpointConfig().setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
env.getCheckpointConfig().setCheckpointStorage("hdfs:///flink/checkpoints");
env.getCheckpointConfig().setExternalizedCheckpointCleanup(
ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);
// 2. 配置重启策略
env.setRestartStrategy(RestartStrategies.failureRateRestart(
3, Duration.ofMinutes(5), Duration.ofSeconds(10)));
// 3. 为算子指定 UID(保证 Savepoint 兼容性)
stream.map(new MyMapFunction()).uid("my-map").name("My Map");
stream.keyBy(...).process(new MyProcessor()).uid("my-processor").name("My Processor");
// 4. 避免使用 Lambda(影响 UID 稳定性)
// 不推荐:stream.map(x -> x * 2)
// 推荐:stream.map(new MyMapFunction())
10.2 算子 UID 的重要性
java
// 没有 UID 的算子,Flink 会自动生成 UID(基于拓扑位置)
// 一旦拓扑变化,UID 会改变,导致 Savepoint 无法恢复!
// 强烈推荐:为每个有状态算子手动设置 UID
stream
.map(new ParseFunction()).uid("parse-001").name("Parse Events")
.keyBy(Event::getUserId)
.process(new SessionFunction()).uid("session-002").name("Session Window")
.addSink(new KafkaSink()).uid("sink-003").name("Kafka Sink");
10.3 状态管理最佳实践
java
// 1. 为状态设置 TTL,避免状态无限增长
StateTtlConfig ttlConfig = StateTtlConfig
.newBuilder(Time.hours(24))
.setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite) // 写时刷新 TTL
.setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
.cleanupFullSnapshot() // Checkpoint 时清理过期状态
.cleanupIncrementally(1000, true) // 增量清理
.build();
descriptor.enableTimeToLive(ttlConfig);
// 2. 大状态使用 RocksDB
env.setStateBackend(new EmbeddedRocksDBStateBackend(true)); // 增量 Checkpoint
// 3. 定期清理不再使用的状态
// 在 onTimer 或 processElement 中手动调用 state.clear()
// 4. 避免在状态中存储大对象(如 List<String> 包含大量数据)
// 应尽量精简存储的状态内容
10.4 Kafka 消费最佳实践
java
// 1. 设置消费者隔离级别(EXACTLY_ONCE 场景)
Properties props = new Properties();
props.setProperty("isolation.level", "read_committed"); // 仅读已提交事务的消息
// 2. 设置合理的 idle 超时(多分区场景避免 Watermark 停滞)
WatermarkStrategy.forBoundedOutOfOrderness(Duration.ofSeconds(5))
.withIdleness(Duration.ofMinutes(1)); // 1分钟无数据的分区视为空闲
// 3. 动态分区发现(Topic 扩分区后自动感知)
KafkaSource.<String>builder()
.setProperty("partition.discovery.interval.ms", "30000") // 30秒检测一次
...
// 4. Kafka Sink 事务超时配置(需与 Flink Checkpoint 超时协调)
// Kafka 事务超时 > Flink Checkpoint 超时
props.setProperty("transaction.timeout.ms", "600000"); // 10分钟
10.5 监控与告警
关键监控指标:
JVM 类指标:
- flink_taskmanager_job_task_operator_numRecordsIn:输入记录数
- flink_taskmanager_job_task_operator_numRecordsOut:输出记录数
- flink_taskmanager_job_task_backPressuredTimeMsPerSecond:背压时间
- flink_jobmanager_job_lastCheckpointSize:上次 Checkpoint 大小
- flink_jobmanager_job_lastCheckpointDuration:上次 Checkpoint 耗时
- flink_jobmanager_job_numberOfFailedCheckpoints:失败 Checkpoint 数
- flink_taskmanager_Status_JVM_Memory_Heap_Used:JVM 堆内存使用量
Prometheus + Grafana 集成:
yaml
# flink-conf.yaml
metrics.reporter.prometheus.class: org.apache.flink.metrics.prometheus.PrometheusReporter
metrics.reporter.prometheus.port: 9249
metrics.reporter.prometheus.interval: 10 SECONDS
告警规则示例(Prometheus AlertManager):
yaml
groups:
- name: flink-alerts
rules:
- alert: FlinkCheckpointFailed
expr: flink_jobmanager_job_numberOfFailedCheckpoints > 3
for: 5m
labels:
severity: critical
annotations:
summary: "Flink Checkpoint 连续失败"
- alert: FlinkBackpressureHigh
expr: flink_taskmanager_job_task_backPressuredTimeMsPerSecond > 500
for: 10m
labels:
severity: warning
annotations:
summary: "Flink 算子存在持续背压"
10.6 生产上线清单
在将 Flink 作业投入生产前,务必检查以下项目:
- 所有有状态算子均设置了 UID
- 开启了 Checkpoint,存储路径为高可用文件系统(HDFS/S3)
- 配置了合理的重启策略
- Source 配置了 Watermark 策略(事件时间场景)
- Sink 配置了合适的交付语义
- 配置了 State TTL,避免状态无限增长
- 大状态使用了 RocksDB 状态后端
- 设置了合理的并行度
- 配置了监控告警(Checkpoint 失败、背压、资源使用)
- 验证了 Savepoint/Checkpoint 恢复流程
- 代码中无 Kryo 序列化(建议使用 Avro/Protobuf)
- 日志级别配置合理(生产用 WARN/INFO)
11. 常见问题与排错
11.1 Checkpoint 超时/失败
症状:Checkpoint 耗时过长或经常失败。
排查步骤:
- 查看 Web UI → Jobs → Checkpoints → History,查看失败原因和各算子耗时
- 检查是否存在严重背压(背压导致 Barrier 传播缓慢)
- 检查状态大小是否合理(过大导致快照耗时长)
常见解决方案:
yaml
# 增大超时时间
execution.checkpointing.timeout: 600000 # 10分钟
# 启用非对齐 Checkpoint(解决背压导致的超时)
execution.checkpointing.unaligned: true
# RocksDB 增量 Checkpoint
state.backend.incremental: true
# 调整 RocksDB 写入速度
state.backend.rocksdb.writebuffer.size: 128MB
state.backend.rocksdb.writebuffer.count: 4
11.2 OOM(内存溢出)
排查步骤:
- 确认 OOM 类型:
java.lang.OutOfMemoryError: Java heap space→ 堆内存不足java.lang.OutOfMemoryError: Direct buffer memory→ 堆外内存不足java.lang.OutOfMemoryError: GC overhead limit exceeded→ GC 压力过大
- 调整内存配置
yaml
# 调整 Task 堆内存
taskmanager.memory.task.heap.size: 2gb
# 调整托管内存(RocksDB 使用)
taskmanager.memory.managed.fraction: 0.5
# 调整网络内存
taskmanager.memory.network.fraction: 0.15
# 调整 JVM Metaspace
taskmanager.memory.jvm-metaspace.size: 512mb
11.3 数据倾斜
症状:部分 TaskManager 的 CPU/内存使用率远高于其他,背压产生于某几个算子。
排查:Web UI → Jobs → SubTasks,查看各 SubTask 的处理记录数。
解决方案:
java
// 方案1:Key 加盐(两阶段聚合)
// 方案2:自定义分区器
stream.partitionCustom(new RandomPartitioner(), x -> x.key);
// 方案3:预聚合
// 方案4:过滤异常 Key(如果允许丢弃)
stream.filter(event -> !event.key.equals("HOT_KEY"));
11.4 Watermark 不推进(窗口不触发)
症状:窗口数据积累但从不触发计算。
常见原因:
- 某个并行分区没有数据,导致全局 Watermark 停滞(取最小值)
- Source 数据稀少,Watermark 生成间隔太长
解决方案:
java
// 1. 设置空闲超时
WatermarkStrategy.forBoundedOutOfOrderness(Duration.ofSeconds(5))
.withIdleness(Duration.ofMinutes(1)); // 超时后该分区不参与 Watermark 计算
// 2. 调整 Watermark 生成间隔
env.getConfig().setAutoWatermarkInterval(200); // 200ms(默认值)
// 3. 确认 Source 并行度与 Kafka 分区数匹配
11.5 任务重启循环
症状:作业不断重启,日志中显示反复失败。
排查:
- 查看具体异常:
bin/flink list,通过 Web UI 或 YARN 日志查看详细错误 - 常见原因:
- Checkpoint 路径无法访问(HDFS 故障)
- 序列化错误(Kryo 序列化失败)
- 依赖冲突(ClassNotFoundException)
- 外部系统连接失败(Kafka/MySQL 不可达)
bash
# 查看 Flink 日志
tail -f log/flink-*-taskmanager-*.log
tail -f log/flink-*-jobmanager-*.log
# YARN 模式下查看日志
yarn logs -applicationId <appId>
11.6 Flink SQL 常见问题
sql
-- 问题1:GroupBy 聚合结果不断更新(Retract 流)
-- 解决:下游 Sink 需支持 ChangeLog,或使用 TUMBLE/HOP 窗口
-- 问题2:Temporal Join 数据不匹配
-- 原因:维表快照时间与事件时间不匹配
-- 解决:检查 FOR SYSTEM_TIME AS OF 的时间列是否正确
-- 问题3:JOIN 导致状态无限增长
-- 解决:使用 Interval Join 限制时间范围,或设置 join.idle-state.retention.time
SET table.exec.state.ttl = '24h';
12. 生态系统与应用场景
12.1 Flink 生态系统
Apache Flink 生态系统
核心项目:
├── Apache Flink # 核心计算引擎
├── Flink CDC # 变更数据捕获(阿里捐赠)
├── Apache Paimon # 实时数据湖存储(从 Flink 孵化)
└── Flink Kubernetes Operator # K8s 原生部署
周边生态:
├── 数据源
│ ├── Apache Kafka # 消息队列
│ ├── Apache Pulsar # 消息队列
│ ├── Apache RocketMQ # 消息队列
│ └── Kinesis, PubSub # 云消息服务
├── 存储/计算
│ ├── Apache Hadoop HDFS # 分布式文件系统
│ ├── Amazon S3/OSS/GCS # 对象存储
│ ├── Apache HBase # NoSQL 数据库
│ ├── Apache Iceberg # 数据湖格式
│ ├── Apache Hudi # 数据湖格式
│ └── Apache Paimon # 数据湖格式
├── 数据库 Sink
│ ├── MySQL/PostgreSQL # JDBC
│ ├── ClickHouse/Doris # OLAP
│ └── Elasticsearch # 搜索引擎
└── 监控运维
├── Prometheus + Grafana # 指标监控
└── Apache Zeppelin # 交互式查询
12.2 典型应用场景
1. 实时数据管道(ETL)
Kafka → Flink(清洗/转换/enrichment) → HDFS/S3/ClickHouse
特点:低延迟数据集成,替代传统 T+1 数据链路
2. 实时数据仓库
业务数据库(MySQL)
↓ Flink CDC
ODS 层(原始数据)
↓ Flink SQL(流式 ETL)
DWD 层(明细数据)
↓ Flink SQL(窗口聚合)
DWS 层(汇总数据)
↓
ADS 层(应用数据)→ ClickHouse/Doris(供查询)
3. 实时监控与告警
传感器数据/日志
↓ Flink CEP(复杂事件处理)
检测异常模式(如:5分钟内连续3次超温)
↓
告警通知(钉钉/邮件/短信)
Flink CEP 示例:
java
Pattern<SensorReading, ?> pattern = Pattern.<SensorReading>begin("start")
.where(reading -> reading.temperature > 90)
.times(3)
.within(Time.minutes(5));
PatternStream<SensorReading> patternStream = CEP.pattern(sensorStream, pattern);
patternStream.select(map -> {
SensorReading last = map.get("start").get(2);
return new Alert(last.sensorId, "TRIPLE_HIGH_TEMP");
}).addSink(alertSink);
4. 机器学习特征工程
原始行为数据流
↓ Flink(实时特征计算)
用户实时特征(过去1小时点击量、停留时长等)
↓
特征服务(Redis)→ 模型推理(TensorFlow Serving)
5. 流式湖仓(Streaming Lakehouse)
业务数据(各类数据库)
↓ Flink CDC
Paimon ODS(实时写入)
↓ Flink SQL(流批一体 ETL)
Paimon DWD/DWS
├─→ Flink SQL(实时查询,秒级延迟)
└─→ Spark/Flink 批读(历史分析)
6. 金融风控
交易流
↓ Flink(实时规则引擎 + CEP)
风险评分(实时)
↓
决策系统(放行/拦截/人工审核)
12.3 行业最佳实践案例
阿里巴巴:
- 双十一实时大屏:使用 Flink + Paimon 流式湖仓,支撑天猫营销分析,实现统一实时+离线处理
- 风控系统:每天处理数百亿规则计算,延迟 < 100ms
字节跳动:
- 实时推荐特征:Flink 每天处理数百 TB 数据,实时更新用户特征
- 日志分析:全链路实时日志告警
腾讯:
- 游戏实时数据分析:玩家行为实时统计
- 广告实时竞价:毫秒级决策
美团:
- 实时数仓:Flink + Kafka 构建实时数仓,支撑外卖、酒旅等业务
- 骑手实时调度:基于位置流数据的实时路径规划
附录
A. 常用配置参考
yaml
# ========= 核心配置 =========
jobmanager.rpc.address: localhost
jobmanager.memory.process.size: 4096m
taskmanager.memory.process.size: 8192m
taskmanager.numberOfTaskSlots: 4
parallelism.default: 4
# ========= Checkpoint 配置 =========
execution.checkpointing.interval: 60000
execution.checkpointing.mode: EXACTLY_ONCE
execution.checkpointing.timeout: 600000
execution.checkpointing.min-pause: 30000
execution.checkpointing.max-concurrent-checkpoints: 1
state.checkpoints.dir: hdfs:///flink/checkpoints
state.savepoints.dir: hdfs:///flink/savepoints
# ========= 状态后端 =========
state.backend: rocksdb
state.backend.incremental: true
# ========= 网络 =========
taskmanager.network.memory.fraction: 0.1
taskmanager.network.memory.min: 64mb
taskmanager.network.memory.max: 1gb
# ========= 高可用 =========
high-availability: zookeeper
high-availability.zookeeper.quorum: zk1:2181,zk2:2181,zk3:2181
high-availability.storageDir: hdfs:///flink/ha
# ========= 监控 =========
metrics.reporters: prometheus
metrics.reporter.prometheus.class: org.apache.flink.metrics.prometheus.PrometheusReporter
metrics.reporter.prometheus.port: 9249
# ========= 日志 =========
env.log.dir: /opt/flink/log
env.log.max: 100mb
B. 常用命令参考
bash
# ===== 集群管理 =====
bin/start-cluster.sh # 启动 Standalone 集群
bin/stop-cluster.sh # 停止 Standalone 集群
bin/jobmanager.sh start|stop # 启停 JobManager
bin/taskmanager.sh start|stop # 启停 TaskManager
# ===== 作业管理 =====
bin/flink run -c <MainClass> <jar> # 提交作业
bin/flink run -s <savepointPath> <jar> # 从 Savepoint 恢复
bin/flink list # 查看运行中作业
bin/flink list --all # 查看所有作业(含已完成)
bin/flink cancel <jobId> # 取消作业
bin/flink stop <jobId> # 优雅停止(触发 Savepoint)
bin/flink info <jar> # 查看作业信息(不执行)
# ===== Savepoint =====
bin/flink savepoint <jobId> # 触发 Savepoint
bin/flink savepoint <jobId> <dir> # 触发并指定路径
bin/flink savepoint -d <savepointPath> # 删除 Savepoint
# ===== SQL Client =====
bin/sql-client.sh # 启动 SQL Client(交互模式)
bin/sql-client.sh -f my_sql.sql # 执行 SQL 文件
# ===== YARN 相关 =====
bin/yarn-session.sh -d # 启动 YARN Session(后台)
bin/flink run -t yarn-per-job ... # YARN Per-Job 模式
bin/flink run-application -t yarn-application ... # YARN Application 模式
C. Maven 依赖参考
xml
<properties>
<flink.version>1.18.1</flink.version>
<scala.binary.version>2.12</scala.binary.version>
</properties>
<dependencies>
<!-- Flink 核心 -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java</artifactId>
<version>${flink.version}</version>
</dependency>
<!-- Table API & SQL -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-api-java-bridge</artifactId>
<version>${flink.version}</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-planner-loader</artifactId>
<version>${flink.version}</version>
</dependency>
<!-- Kafka Connector -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka</artifactId>
<version>3.1.0-1.18</version>
</dependency>
<!-- JDBC Connector -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-jdbc</artifactId>
<version>3.1.2-1.18</version>
</dependency>
<!-- CEP -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-cep</artifactId>
<version>${flink.version}</version>
</dependency>
<!-- RocksDB State Backend -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-statebackend-rocksdb</artifactId>
<version>${flink.version}</version>
</dependency>
<!-- 本地运行(provided 在提交时排除)-->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-clients</artifactId>
<version>${flink.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
D. 学习资源推荐
| 资源 | 地址 | 说明 |
|---|---|---|
| 官方文档 | https://flink.apache.org/docs/stable/ | 最权威,英文 |
| 中文文档 | https://nightlies.apache.org/flink/flink-docs-stable/zh/ | 官方中文翻译 |
| Ververica 教程 | https://training.ververica.com/ | 系统化在线课程 |
| Flink 中文社区 | https://flink-learning.org.cn/ | 中文技术文章 |
| Flink Forward 视频 | https://www.youtube.com/@FlinkForward | 大会演讲视频 |
| 阿里云开发者社区 | https://developer.aliyun.com/ | 中文实践案例 |
| GitHub 官方仓库 | https://github.com/apache/flink | 源码与 Issue |