Flink SQL 基础:架构、环境与 API 关系

好,咱们正式开始接触 Flink SQL。这一章我打算从三个角度切入:Flink 到底长什么样、怎么把环境搭起来、以及 Table API 和 SQL 到底谁是谁的“马甲”。

说白了,Flink SQL 不是凭空冒出来的东西。它背后是一整套流处理引擎在撑着。你如果只把它当 SQL 用,那有点浪费。我习惯先看架构,再动手搭环境,最后搞清楚 API 的定位——这样后面写查询的时候,心里才有底。

Flink 架构简介:它到底怎么跑的?

先看一张图,把 Flink 的核心角色理清楚。

Flink 运行时架构图 Flink 运行时核心架构 Client(客户端) JobManager(作业管理器) 调度 · 检查点 · 协调 TaskManager Slot · Slot · Slot 数据流执行 TaskManager Slot · Slot · Slot 数据流执行 Kafka / 文件 MySQL / ES Client 提交作业 → JobManager 调度 → TaskManager 多 Slot 并行执行

这张图我画得比较简洁,但核心角色都在了。

  • Client:你写好的 SQL 或者代码,通过 Client 提交给集群。它负责把作业打包、解析、扔给 JobManager。
  • JobManager:整个作业的“大脑”。它决定谁先跑、谁后跑、失败了怎么恢复。检查点(checkpoint)也是它管的。
  • TaskManager:真正干活的“工人”。每个 TaskManager 里有多个 Slot,Slot 就是并行度的最小单位。

我记得刚接触 Flink 时,总搞不清 Slot 和并行度的关系。后来踩了个坑:我把 Slot 数设成 1,并行度设成 4,结果作业一直排队。你想想看,Slot 是物理资源槽,并行度是逻辑并发数——物理不够,逻辑再高也白搭。

💡 我的习惯: 开发环境 Slot 数 = 并行度,生产环境 Slot 数 = 并行度 × 1.5,留点余量给故障恢复。

Flink SQL 环境搭建:别被版本坑了

环境搭建这件事,说简单也简单,说烦也烦。我见过太多人卡在版本兼容性上。这里我直接给出一套我验证过的方案。

1. 下载与解压

# 我推荐 Flink 1.17 或 1.18,SQL 功能最稳定
wget https://dlcdn.apache.org/flink/flink-1.18.0/flink-1.18.0-bin-scala_2.12.tgz
tar -xzf flink-1.18.0-bin-scala_2.12.tgz
cd flink-1.18.0

2. 启动本地集群

# 启动单机版,默认 JobManager + 1 个 TaskManager
./bin/start-cluster.sh

# 检查是否启动成功
jps
# 应该看到:StandaloneSessionClusterEntrypoint(JobManager)
#           TaskManagerRunner(TaskManager)

启动后,打开浏览器访问 http://localhost:8081,能看到 Flink Web UI。嗯,这一步如果看不到,八成是端口被占了,或者 Java 版本不对。Flink 1.18 要求 Java 11+。

3. 启动 SQL Client

# 进入 SQL 交互式客户端
./bin/sql-client.sh

看到 Flink SQL> 提示符,就说明环境搭好了。你可以试试:

Flink SQL> SELECT 'Hello Flink SQL' AS msg;
⚠️ 我曾经踩过的坑: 如果你用 Flink 1.14 之前的版本,SQL Client 默认不加载 Hive 依赖,需要手动添加 jar 包。1.15 之后好多了,但连接外部系统(比如 Kafka、MySQL)时,仍然要提前把对应的 connector jar 放到 lib/ 目录下。

4. 常用配置速查

配置项 默认值 说明
taskmanager.numberOfTaskSlots 1 每个 TaskManager 的 Slot 数
parallelism.default 1 作业默认并行度
state.backend HashMap 状态后端,生产用 RocksDB
execution.runtime-mode STREAMING 流模式 / 批模式

我个人习惯在 conf/flink-conf.yaml 里先把 parallelism.default 改成 2,这样本地测试时能看到并行效果,又不至于把笔记本跑崩。

Table API 与 SQL 的关系:一枚硬币的两面

很多人问我:“Table API 和 SQL 到底选哪个?”

我的回答是:它们本质上是同一套逻辑表达层的两种写法。SQL 是声明式的字符串,Table API 是程序式的链式调用。底层都翻译成相同的 Table 对象,再转成 DataStream 执行。

看个对比就清楚了:

// SQL 写法
Table result = tableEnv.sqlQuery(
    "SELECT user, COUNT(*) AS cnt " +
    "FROM clicks " +
    "GROUP BY user"
);

// Table API 写法
Table result = tableEnv.from("clicks")
    .groupBy($("user"))
    .select($("user"), $("count").count().as("cnt"));

这两段代码,最终生成的执行计划一模一样。区别在哪?

  • SQL:适合快速验证、临时查询、给数据分析师用。字符串形式,动态拼接方便。
  • Table API:适合复杂逻辑、需要 IDE 自动补全、类型安全的场景。编译期就能发现字段名写错。
核心理解: Table API 和 SQL 共享同一个 Catalog、同一个 Planner、同一套 Connector。你可以在一个作业里混用——先用 SQL 注册表,再用 Table API 做复杂聚合,最后用 SQL 输出结果。我经常这么干。

为什么会这样?因为 Flink 在 1.9 版本之后,把 Table API 和 SQL 的底层统一成了 Blink Planner。之前两个 Planner 并存,搞得大家很混乱。现在好了,一套优化器,两种入口。

我画了一张关系图,帮你理清层次:

Table API 与 SQL 关系图 Table API 与 SQL 统一关系 SQL(字符串) Table API(链式) Table 对象 Blink Planner 优化 · 转执行计划

你看,不管是 SQL 还是 Table API,最终都汇到 Table 对象,然后交给 Planner 去优化。Planner 会做谓词下推、投影裁剪、甚至把流转成批来优化性能。

💡 实战建议: 如果你团队里有数据分析师,让他们写 SQL,你负责把 SQL 嵌入到 Table API 的 pipeline 里。用 tableEnv.sqlQuery() 把 SQL 结果转成 Table,再用 Table API 做后续处理——两边都舒服。