3. SQL 客户端与编程模型:SQL Client 模式、Table API 与 SQL 的关系、编程模型(Source-Transform-Sink)

这一章,我们来聊聊 Flink SQL 开发的几个核心概念。说白了,就是搞清楚「怎么跑 SQL」、「Table API 和 SQL 到底啥关系」以及「Flink 程序的基本骨架长什么样」。

我刚开始接触 Flink 时,也在这几个概念上绕了不少弯路。尤其是 SQL Client 和 Table API,总觉得它们功能重叠,不知道什么时候该用哪个。后来踩过坑才明白——它们其实是同一枚硬币的两面。

3.1 SQL Client 模式:交互式开发的利器

先说说 SQL Client。这是个什么东西呢?

Flink 官方提供了一个命令行工具,叫 sql-client。你可以在终端里直接输入 SQL 语句,实时看到结果。有点像 MySQL 的客户端,但背后跑的是 Flink 引擎。

核心价值:SQL Client 让你不用写一行 Java/Scala 代码,就能完成 Flink SQL 的开发、调试和运维。

我个人习惯把 SQL Client 用在两个场景:

  • 快速验证:写了一段 SQL,不确定语法对不对、逻辑通不通,直接丢进 SQL Client 跑一下。
  • 临时查询:线上有个问题,需要查一下 Kafka 里的数据长什么样,或者看看 Hive 表里有没有脏数据。

启动 SQL Client 很简单:

# 进入 Flink 安装目录
./bin/sql-client.sh embedded

# 或者指定配置文件
./bin/sql-client.sh embedded -d conf/sql-client.yaml

进去之后,你会看到一个 Flink SQL> 的提示符。这时候就可以开始写 SQL 了。

小技巧:我建议你在 sql-client.yaml 里提前配好常用的 Catalog 和 Connector。比如 Kafka 的连接信息、Hive 的元数据地址。这样每次启动后直接就能用,不用反复写 CREATE TABLE 语句。

SQL Client 支持两种运行模式:

模式 说明 适用场景
embedded 客户端进程内嵌 Flink 运行时,不依赖集群 本地开发、小数据量验证
gateway 连接远程 Flink Session 集群 生产环境、多人协作

我记得有一次线上排查数据延迟问题,就是靠 SQL Client 的 gateway 模式连上集群,直接查 Kafka Source 的 watermark 和当前处理时间,几分钟就定位到了问题。要是没有这个工具,估计得写个 Flink 作业再提交,折腾半天。

3.2 Table API 与 SQL 的关系:一体两面

很多人觉得 Table API 和 SQL 是两套东西。其实不然。

Flink 的 Table API 和 SQL 共享同一个底层引擎——Table Planner。它们最终都会被翻译成相同的 DataStream 或 DataSet 程序执行。

你可以这样理解:

  • SQL:声明式,你只管「要什么」,不用管「怎么做」。
  • Table API:嵌入式 DSL,用 Java/Scala 的方法链来表达查询逻辑。

举个例子,同样一个过滤+聚合操作:

-- SQL 写法
SELECT user_id, COUNT(*) AS cnt
FROM orders
WHERE amount > 100
GROUP BY user_id;
// Table API 写法
tableEnv.from("orders")
  .filter($("amount").isGreater(100))
  .groupBy($("user_id"))
  .select($("user_id"), $("count").as("cnt"));

看到没?逻辑完全一样,只是表达方式不同。

关键点:Table API 和 SQL 可以无缝混用。你可以在 Table API 中调用 tableEnv.sqlQuery() 执行 SQL,也可以在 SQL 中引用 Table API 注册的临时表。

我个人更倾向于:

  • 简单的 ETL 逻辑用 SQL,可读性强,维护成本低。
  • 复杂的业务逻辑(比如多步状态计算、自定义 UDF 组合)用 Table API,因为 IDE 有代码提示,编译期就能发现类型错误。

注意:Table API 和 SQL 的优化路径完全一致。不要觉得「用 SQL 性能就差,用 Table API 性能就好」。性能取决于你写的逻辑和 Planner 的优化能力,跟 API 形式无关。

3.3 编程模型:Source → Transform → Sink

Flink 的编程模型,说白了就是三个步骤:

  1. Source:从哪里读数据?
  2. Transform:怎么处理数据?
  3. Sink:结果写到哪里去?

这个模型贯穿 Flink 的所有 API 层面——不管是 DataStream、Table API 还是 SQL,底层都是这个骨架。

下面这张图可以帮你直观理解:

Flink SQL 编程模型:Source → Transform → Sink Source Kafka / MySQL / Hive / File Transform SELECT / JOIN / AGG / UDF Sink Kafka / MySQL / HDFS / ES 说明: • Source 定义数据输入,支持 Kafka、MySQL CDC、Hive、文件等 • Transform 是 SQL 逻辑,包括过滤、关联、聚合、窗口计算等 • Sink 定义结果输出,支持 Kafka、JDBC、HDFS、Elasticsearch 等

在 Flink SQL 中,这三个步骤对应着不同的 DDL 和 DML 语句:

阶段 SQL 语句 作用
Source CREATE TABLE ... WITH ('connector'='kafka', ...) 定义输入表,指定数据源连接信息
Transform INSERT INTO sink_table SELECT ... FROM source_table 编写查询逻辑,处理数据
Sink CREATE TABLE ... WITH ('connector'='jdbc', ...) 定义输出表,指定结果写入目标

一个完整的 Flink SQL 作业,通常长这样:

-- 1. 定义 Source(从 Kafka 读订单数据)
CREATE TABLE orders (
  order_id BIGINT,
  user_id BIGINT,
  amount DOUBLE,
  ts TIMESTAMP(3),
  WATERMARK FOR ts AS ts - INTERVAL '5' SECOND
) WITH (
  'connector' = 'kafka',
  'topic' = 'orders',
  'properties.bootstrap.servers' = 'localhost:9092',
  'format' = 'json'
);

-- 2. 定义 Sink(写入 MySQL)
CREATE TABLE order_stats (
  user_id BIGINT,
  total_amount DOUBLE,
  order_count BIGINT,
  PRIMARY KEY (user_id) NOT ENFORCED
) WITH (
  'connector' = 'jdbc',
  'url' = 'jdbc:mysql://localhost:3306/flink',
  'table-name' = 'order_stats'
);

-- 3. Transform + Sink(执行计算并写入结果)
INSERT INTO order_stats
SELECT
  user_id,
  SUM(amount) AS total_amount,
  COUNT(*) AS order_count
FROM orders
WHERE amount > 0
GROUP BY user_id;

经验之谈:我建议你在写 Flink SQL 作业时,先把 Source 和 Sink 的表定义写好,单独测试连通性。确认数据能读进来、能写出去之后,再写中间的 Transform 逻辑。这样排查问题会快很多。

3.4 三种开发模式的对比

Flink SQL 开发有三种主流模式,我整理了一张对比表:

模式 开发方式 适合场景 优点 缺点
SQL Client 命令行交互 快速验证、临时查询 零编码、即时反馈 不适合复杂逻辑、难以复用
Table API Java/Scala 代码 复杂 ETL、需要 IDE 支持 类型安全、可调试、可复用 代码量较大
SQL + Java 混合 代码中嵌入 SQL 生产作业、需要灵活扩展 兼顾 SQL 简洁性和代码灵活性 需要同时掌握两种语法

我个人在实际项目中,90% 的场景用的是第三种——SQL + Java 混合模式。为什么呢?

  • 核心的 ETL 逻辑用 SQL 写,清晰易懂,业务同学也能 review。
  • 外围的配置管理、错误处理、监控上报用 Java 写,灵活可控。

避坑指南:我曾经在一个项目里,把所有逻辑都写在 SQL Client 的脚本里,结果作业一多,管理起来非常痛苦。后来改成 Java 项目 + SQL 文件的方式,每个作业一个独立的 SQL 文件,通过代码动态加载执行。这样既保留了 SQL 的可读性,又有了版本管理和 CI/CD 的能力。

3.5 小结

这一章我们聊了三个核心概念:

  • SQL Client:交互式开发工具,适合快速验证和临时查询。
  • Table API 与 SQL 的关系:同根同源,可以混用,选择取决于场景偏好。
  • Source → Transform → Sink:Flink 编程模型的基本骨架,所有作业都遵循这个模式。