3、Flink基础:架构、环境搭建与API入门

这一章,我们正式进入Flink的世界。说实话,Flink这几年在实时计算领域几乎是统治级的存在。我最早接触Flink还是1.4版本,那时候社区还没这么火,但它的流处理理念已经让我眼前一亮。今天咱们把Flink的底裤扒一扒——架构、环境、DataStream API、Table API,一条龙讲清楚。

3.1 Flink架构:核心组件与运行原理

先看架构。Flink的架构其实不复杂,但很多人一开始会被各种术语搞晕。我习惯把它拆成两层:运行时层和API层。

核心思想:Flink是一个有状态的流处理引擎。它把一切数据都当作流来处理——批处理只是流处理的一个特例(有限流)。这个理念贯穿了整个架构设计。

咱们先画一张架构图,把各个组件的关系理清楚。

Flink 运行时架构 API 层 DataStream API Table & SQL API ProcessFunction 运行时层 (Runtime) JobManager 调度 / 协调 TaskManager 执行任务 Slot 资源单元 State 状态存储 部署层 Local | Standalone | YARN | Kubernetes | Mesos 说明: JobManager 负责调度和 checkpoint,TaskManager 负责执行算子,Slot 是资源隔离单位。 State 默认存储在 Heap 或 RocksDB,通过 StateBackend 配置。

这张图我建议你多看两眼。API层是你写代码的地方,运行时层是Flink帮你干活的地方。我见过不少新手,代码写得飞起,但JobManager挂了就手足无措——说白了,就是没搞懂架构。

3.2 Flink运行环境搭建

环境搭建这块,我推荐两种方式:本地模式用于开发调试,Docker Compose用于模拟集群。咱们一个一个来。

3.2.1 本地模式(Standalone)

本地模式最简单,适合你刚上手时跑Demo。下载解压就行。

# 1. 下载 Flink(以 1.17 为例)
wget https://dlcdn.apache.org/flink/flink-1.17.0/flink-1.17.0-bin-scala_2.12.tgz

# 2. 解压
tar -xzf flink-1.17.0-bin-scala_2.12.tgz
cd flink-1.17.0

# 3. 启动集群(本地模式)
./bin/start-cluster.sh

# 4. 访问 Web UI
# 浏览器打开 http://localhost:8081

我的习惯:本地开发时,我会把 taskmanager.numberOfTaskSlots 设成 4,这样能同时跑多个任务,调试效率高。但生产环境一般设成 1,避免资源争抢。

3.2.2 Docker Compose 模式

如果你不想污染本地环境,用Docker最省心。我曾经在Mac上折腾了半小时环境变量,后来直接上Docker,5分钟搞定。

# docker-compose.yml
version: "3.8"
services:
  jobmanager:
    image: flink:1.17.0-scala_2.12
    ports:
      - "8081:8081"
    command: jobmanager
    environment:
      - JOB_MANAGER_RPC_ADDRESS=jobmanager

  taskmanager:
    image: flink:1.17.0-scala_2.12
    depends_on:
      - jobmanager
    command: taskmanager
    environment:
      - JOB_MANAGER_RPC_ADDRESS=jobmanager
    scale: 2

启动命令:docker-compose up -d。然后访问 localhost:8081,你会看到两个TaskManager在线。

3.3 Flink DataStream API入门

DataStream API是Flink的核心API,也是我工作中用得最多的。说白了,它就是一套处理无界流的算子集合。

3.3.1 第一个流处理程序:WordCount

咱们写一个经典的WordCount,但这次是流式的,不是批处理。

import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;

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

        // 2. 数据源:从 socket 读取(模拟无界流)
        DataStream<String> text = env.socketTextStream("localhost", 9999);

        // 3. 转换逻辑
        DataStream<Tuple2<String, Integer>> counts = text
            .flatMap(new Tokenizer())
            .keyBy(value -> value.f0)
            .sum(1);

        // 4. 输出
        counts.print();

        // 5. 启动任务
        env.execute("Streaming WordCount");
    }

    public static class Tokenizer implements FlatMapFunction<String, Tuple2<String, Integer>> {
        @Override
        public void flatMap(String value, Collector<Tuple2<String, Integer>> out) {
            String[] words = value.toLowerCase().split("\\W+");
            for (String word : words) {
                if (word.length() > 0) {
                    out.collect(new Tuple2<>(word, 1));
                }
            }
        }
    }
}

注意:这里用的是 socketTextStream,生产环境千万别这么干。socket源没有容错机制,一旦断连,数据就丢了。我一般用Kafka作为生产数据源。

3.3.2 常用算子一览

DataStream API的算子很多,但常用的就这几个。我整理了一张表,方便你对照。

算子 用途 示例
map 一对一转换 stream.map(x -> x * 2)
flatMap 一对多转换 stream.flatMap(...)
filter 过滤元素 stream.filter(x -> x > 0)
keyBy 按key分组 stream.keyBy(event -> event.id)
window 开窗聚合 stream.window(TumblingEventTimeWindows.of(Time.minutes(5)))
process 底层处理(可访问状态) stream.process(new MyProcessFunction())

你想想看,这些算子组合起来,能解决90%的实时计算场景。我做过一个实时风控系统,核心逻辑就是 keyBy + window + process 三板斧。

3.4 Flink Table & SQL API入门

Table API和SQL API是Flink的高级API。它们把流数据抽象成动态表,你可以用SQL直接查询。这对数据分析师特别友好——不用写Java代码,一条SQL搞定。

3.4.1 核心概念:动态表

动态表是什么?说白了,就是一张会随时间变化的表。每来一条数据,表就更新一次。Flink把流转换成动态表,然后执行SQL,最后把结果再转回流。

流 → 动态表 → 流 数据流 (无界/有界) event1, event2, ... 动态表 SQL 查询 SELECT ... WHERE ... 结果流 (追加/更新) result1, result2, ... 关键点: 1. 流被转换成动态表,每来一条数据,表就 append 或 update。 2. SQL 查询在动态表上持续执行,结果实时输出。 3. 结果流可以是 Append-only(只追加)或 Retract(撤回+更新)。

3.4.2 实战:用SQL做实时聚合

假设我们有一个订单流,想实时统计每个品类的销售额。用SQL写,代码量少得惊人。

import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;

public class SQLExample {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

        // 1. 从Kafka读取数据,注册为动态表
        tableEnv.executeSql(
            "CREATE TABLE orders (" +
            "  order_id STRING," +
            "  category STRING," +
            "  amount DOUBLE," +
            "  ts TIMESTAMP(3) METADATA FROM 'timestamp'" +
            ") WITH (" +
            "  'connector' = 'kafka'," +
            "  'topic' = 'orders'," +
            "  'properties.bootstrap.servers' = 'localhost:9092'," +
            "  'format' = 'json'" +
            ")"
        );

        // 2. 执行SQL查询:实时聚合
        tableEnv.executeSql(
            "SELECT " +
            "  category," +
            "  SUM(amount) AS total_sales," +
            "  COUNT(*) AS order_count," +
            "  TUMBLE_END(ts, INTERVAL '1' MINUTE) AS window_end " +
            "FROM orders " +
            "GROUP BY category, TUMBLE(ts, INTERVAL '1' MINUTE)"
        ).print();
    }
}

我的经验:Table API适合快速验证和简单聚合。但如果你需要复杂的状态管理(比如自定义超时逻辑),还是得用DataStream API的ProcessFunction。我一般这样分工:SQL做报表,DataStream做风控。

3.4.3 DataStream与Table互转

实际项目中,经常需要在两种API之间切换。Flink提供了toDataStream和fromDataStream方法。

// DataStream → Table
DataStream<Order> orderStream = ...;
Table orderTable = tableEnv.fromDataStream(orderStream);

// Table → DataStream(追加模式)
DataStream<Row> resultStream = tableEnv.toDataStream(resultTable);

// Table → DataStream(更新模式,带撤回)
DataStream<Row> changelogStream = tableEnv.toChangelogStream(resultTable);

避坑指南:我曾经在项目里直接用 toDataStream 转换一个带聚合的Table,结果发现数据对不上。后来才意识到——聚合结果有更新操作,必须用 toChangelogStream 才能正确处理撤回和更新。记住:有GROUP BY就用Changelog模式。

3.5 本章小结

这一章我们覆盖了Flink的三大块:

  • 架构:JobManager + TaskManager + Slot,API层和运行时层分离
  • 环境搭建:本地模式跑Demo,Docker模式模拟集群
  • API入门:DataStream API做流式处理,Table/SQL API做声明式查询

我个人建议你先把DataStream API练熟,再接触SQL。因为SQL虽然方便,但出了问题你很难排查——你不知道Flink在背后生成了什么执行计划。先理解底层,再用高层,这是我一直以来的学习路径。