Flink从入门到精通:全面实战指南


目录

  1. [Flink 概述与背景](#Flink 概述与背景)
  2. 核心架构与组件
  3. 核心概念详解
    • 3.1 流与批
    • 3.2 时间语义
    • 3.3 水位线(Watermark)
    • 3.4 窗口(Window)
    • 3.5 状态(State)
    • 3.6 容错与 Checkpoint
  4. [DataStream API 使用教程](#DataStream API 使用教程)
  5. [Table API & Flink SQL](#Table API & Flink SQL)
  6. [Connectors 连接器](#Connectors 连接器)
  7. 部署模式
  8. 性能调优
  9. [Flink 2.0 新特性](#Flink 2.0 新特性)
  10. 生产最佳实践
  11. 常见问题与排错
  12. 生态系统与应用场景

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 正式发布,史诗级架构革新
对比维度 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 中执行,减少线程切换和序列化开销。满足链接的条件:

  1. 上下游算子并行度相同
  2. 下游算子只有一个输入(无 shuffle)
  3. 算子间通过本地转发(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.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);

聚合查询:

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'
);

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

每个 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();

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 处理批流
  • 移除 MemoryStateBackendFsStateBackend(已被 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 耗时过长或经常失败。

排查步骤

  1. 查看 Web UI → Jobs → Checkpoints → History,查看失败原因和各算子耗时
  2. 检查是否存在严重背压(背压导致 Barrier 传播缓慢)
  3. 检查状态大小是否合理(过大导致快照耗时长)

常见解决方案

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(内存溢出)

排查步骤

  1. 确认 OOM 类型:
    • java.lang.OutOfMemoryError: Java heap space → 堆内存不足
    • java.lang.OutOfMemoryError: Direct buffer memory → 堆外内存不足
    • java.lang.OutOfMemoryError: GC overhead limit exceeded → GC 压力过大
  2. 调整内存配置
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 不推进(窗口不触发)

症状:窗口数据积累但从不触发计算。

常见原因

  1. 某个并行分区没有数据,导致全局 Watermark 停滞(取最小值)
  2. 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 任务重启循环

症状:作业不断重启,日志中显示反复失败。

排查

  1. 查看具体异常:bin/flink list,通过 Web UI 或 YARN 日志查看详细错误
  2. 常见原因:
    • 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>
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. 生态系统与应用场景

复制代码
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

相关推荐
SeaTunnel2 小时前
深度解析 Apache SeaTunnel 核心引擎三大技术创新:高可靠异步持久化与 CDC 架构优化实战
大数据·数据库·架构·apache·seatunnel
DolphinScheduler社区2 小时前
第 8 篇|Apache DolphinScheduler 与 Flink Spark 数据引擎的边界、协同与最佳实践
大数据·flink·spark·开源·apache·海豚调度·大数据工作流调度
黄焖鸡能干四碗2 小时前
企业元数据梳理和元数据管理方案(PPT方案)
大数据·运维·网络·分布式·spark
木心术12 小时前
大数据处理技术:Hadoop与Spark核心原理解析
大数据·hadoop·分布式·spark
BizViewStudio9 小时前
甄选 2026:AI 重构新媒体代运营行业的三大核心变革与落地路径
大数据·人工智能·新媒体运营·媒体
Cx330❀11 小时前
Linux命名管道(FIFO)通信:从原理到实操,一文搞懂跨进程通信
大数据·linux·运维·服务器·elasticsearch·搜索引擎
汽车仪器仪表相关领域11 小时前
NHVOC-70系列固定污染源挥发性有机物监测系统:精准破局工业VOCs监测痛点,赋能环保合规升级
大数据·人工智能·安全性测试
实证小助手13 小时前
世界各国经济政策不确定指数(1997-2024年)月度数据
大数据·人工智能
csgo打的菜又爱玩13 小时前
1.JobManager启动流程解析.md
大数据·flink·源代码管理