如何让 Flink SQL 生产提交更优雅?支持 Catalog 快照与算子级资源调优

项目已完全开源,欢迎围观:

原生 Flink SQL 在生产环境有三大痛点:

痛点 原生 Flink SQL Flink SQL Bootstrap
Catalog 不可复用 DDL 与作业强耦合,每次提交都要重建表。 通过 Catalog 快照 JSON 部署 —— 表、视图、UDF 预注册,运行时无需 DDL。
不支持多语句脚本 原生仅支持单条语句提交。 完整的 Multi-Statement SQL Script 支持 —— DDL 立即执行,DML 编译优化后作为单一 Pipeline 提交。
资源调优粒度粗 仅支持 TM/JM 级别的粗粒度配置(内存、slot)。 算子级资源注入 —— 为每个算子独立配置 CPU、堆内存、托管内存、并行度和链策略。

核心能力

  1. 自定义 Catalog 快照 —— 将表、视图和 UDF 序列化为 JSON 文件。作业启动时 Catalog 已就绪,SQL 脚本中无需写 DDL。
  2. Multi-Statement SQL Script —— 在单个 .sql 文件中编写 CREATE TABLESETINSERTCALL 等语句。启动器自动切分、校验并编排执行。
  3. 细粒度资源调优 —— 通过 --init-resource 生成资源模板,按算子调整并行度和资源,并在作业提交前注入 Flink DAG。
  4. 多协议资源加载 —— 支持从 classpath:file://http(s)://hdfs://s3:// 加载 SQL 脚本和配置。
  5. 支持所有 Flink 部署模式 —— 支持 Standalone、Per-Job、Application、Session 模式,以及 Local、YARN、Kubernetes 等资源环境。
  6. SQL 语法校验与行级错误定位 —— 通过 --validate 在本地快速校验 SQL 语法,无需提交到 Flink 集群。错误信息精确到行号和列号,便于快速迭代。
  7. Flink 1.20+ 兼容 —— 适配 Flink 1.20 及以后版本,不修改 Flink 引擎或 Planner。(未来会持续适配更多版本)。

快速开始

环境要求

依赖 版本
Java 11+
Flink 1.20+

编译

mvn package -DskipTests

Step 1 — 运行 SQL 脚本

最简单的方式 —— 直接提交一条 SQL 脚本:

$FLINK_HOME/bin/flink run \
    --target local \
    /path/to/flink-sql-bootstrap.jar \
    --script-file classpath:example-word-count.sql

其中 example-word-count.sql 包含 DDL + DML,无需任何外部依赖:

-- datagen 自动生成句子 → 分词 → 分组计数 → print 输出
CREATE TEMPORARY TABLE source_table (
  sentence STRING
) WITH (
  'connector' = 'datagen',
  'rows-per-second' = '1'
);

CREATE TEMPORARY TABLE sink_table (
  word STRING,
  cnt BIGINT
) WITH (
  'connector' = 'print'
);

INSERT INTO sink_table
SELECT word, COUNT(*) AS cnt
FROM source_table
CROSS JOIN UNNEST(SPLIT(sentence, ' ')) AS t(word)
GROUP BY word;

执行后将在控制台看到类似输出(实际值可能是随机字符串,因为 datagen 生成随机数据):

Flink SQL> INSERT INTO sink_table
           SELECT word, COUNT(*) AS cnt ...
> +I[<random_hex_string>, 1]
> +I[<random_hex_string>, 1]
> +I[<random_hex_string>, 1]

Step 2 — 生成并注入资源配置

从 SQL 脚本生成资源模板:

$FLINK_HOME/bin/flink run \
    --target local \
    /path/to/flink-sql-bootstrap.jar \
    --script-file classpath:example-word-count.sql \
    --init-resource

输出示例:

{
  "version" : 1,
  "operators" : [ {
    "uid" : "1_source",
    "name" : "source_table[1]",
    "parallelism" : 1,
    "chainStrategy" : "HEAD"
  }, {
    "uid" : "2_correlate",
    "name" : "Correlate[2]",
    "parallelism" : 1,
    "chainStrategy" : "ALWAYS"
  }, {
    "uid" : "3_calc",
    "name" : "Calc[3]",
    "parallelism" : 1,
    "chainStrategy" : "ALWAYS"
  }, {
    "uid" : "5_group-aggregate",
    "name" : "GroupAggregate[5]",
    "parallelism" : -1,
    "chainStrategy" : "ALWAYS"
  }, {
    "uid" : "6_sink",
    "name" : "sink_table[6]",
    "parallelism" : -1,
    "chainStrategy" : "ALWAYS"
  } ]
}

生成的模板为每个算子分配了 uidname、默认 parallelismchainStrategy。修改这些字段可调整并行度和链策略,然后添加 resource 字段配置 CPU/内存(支持的字段见资源调优 JSON)。

将输出保存为 resource.json,修改参数后带资源配置提交:

$FLINK_HOME/bin/flink run \
    --target local \
    /path/to/flink-sql-bootstrap.jar \
    --script-file classpath:example-word-count.sql \
    --resource-file classpath:resource.json

Step 3 — 带 Catalog 快照部署

通过 Catalog 快照预注册表和 UDF,使 SQL 脚本中 不写任何 DDL。需要配合专门的 SQL 脚本使用:

$FLINK_HOME/bin/flink run \
    --target local \
    /path/to/flink-sql-bootstrap.jar \
    --script-file classpath:example-word-count-advanced.sql \
    --catalog-file classpath:example-catalog.json \
    --resource-file classpath:example-resource.json \
    --dependency classpath:example-udf-reverse.jar \
    --dependency classpath:example-udf-substring.jar

其中 example-word-count-advanced.sql 只包含 DML(表和 UDF 已由 Catalog 快照预注册):

INSERT INTO dws_word_count
SELECT my_reverse(my_substring(word, 0, 2)) AS word, COUNT(*) AS cnt
FROM ods_words
CROSS JOIN UNNEST(SPLIT(sentence, ' ')) AS t(word)
GROUP BY my_reverse(my_substring(word, 0, 2));

输出(注意 2 字符前缀 —— my_reverse(my_substring(...)) UDF 已生效):

Job has been submitted with JobID <job_id>
+I[6a, 1]
+I[00, 1]
+I[a3, 1]
+I[8f, 1]

运行模式

除了完整执行外,还提供三种干运行模式,适用于 CI/CD 和开发调试:

模式 参数 说明
语法校验 --validate 解析并校验 SQL 语法,不编译也不执行。
编译 --compile 解析、校验并编译 SQL 脚本,输出优化后的执行计划 JSON。
生成资源模板 --init-resource 翻译 SQL 计划并输出资源配置模板,供用户调优。
# 校验 SQL 语法
$FLINK_HOME/bin/flink run ... --script-file job.sql --validate

# 编译并查看执行计划
$FLINK_HOME/bin/flink run ... --script-file job.sql --compile

# 生成资源模板
$FLINK_HOME/bin/flink run ... --script-file job.sql --init-resource

配置参考

资源调优 JSON

描述每个算子的资源配置。每个算子通过 uid(优先)或 name 匹配。

{
  "version": 1,
  "defaultParallelism": 2,
  "operators": [
    {
      "uid": "1_source",
      "name": "ods_words[1]",
      "parallelism": 1,
      "chainStrategy": "HEAD",
      "resource": {
        "profile": "small"
      }
    },
    {
      "uid": "5_group-aggregate",
      "name": "GroupAggregate[5]",
      "parallelism": 4,
      "chainStrategy": "ALWAYS",
      "resource": {
        "cpu": 1.0,
        "heap": "2048m",
        "managed": "256m"
      }
    }
  ]
}

字段说明:

字段 类型 说明
version int 配置格式版本,当前为 1
defaultParallelism int 全局默认并行度,0 表示不覆盖。
operators 数组 算子配置列表。
operators[].uid string 稳定 UID,用于精确匹配。会覆盖 Flink 自动生成的 UID。
operators[].name string 算子名称,作为 UID 匹配的兜底策略。
operators[].parallelism int 算子并行度,-1 表示使用 Flink 默认。
operators[].chainStrategy string HEADALWAYSNEVER,控制算子链合并策略。
operators[].resource.profile string 预置规格:smallnormallargexlarge
operators[].resource.cpu double CPU 核数(支持小数)。
operators[].resource.heap string Task 堆内存,如 "512 MB""2g"
operators[].resource.managed string 托管内存,如 "256m"

Catalog 快照 JSON

描述一个自包含的 Catalog,包含表、视图和 UDF。

{
  "version": 1,
  "snapshotId": "example-word-count",
  "catalogName": "platform",
  "databaseName": "default",
  "tables": [
    {
      "database": "default",
      "name": "ods_words",
      "columns": [
        { "name": "sentence", "type": "STRING", "nullable": true }
      ],
      "options": {
        "connector": "datagen",
        "rows-per-second": "1"
      }
    }
  ],
  "views": [],
  "udfs": [
    {
      "name": "my_reverse",
      "className": "examples.udf.MyReverseFunction",
      "functionLanguage": "JAVA",
      "jarRef": "example-udf-reverse.jar"
    }
  ]
}

架构设计

Flink SQL Bootstrap 是 Flink 官方 API 之上的一个轻量级编排层。不修改 Flink 引擎、Planner 或 SQL 语义。

+------------------+     +---------------------+     +---------------------+
|   SQL 脚本       | --> |  StreamingScript    | --> |  Flink Table API    |
|  (DDL + DML)     |     |     Executor        |     |  (InternalPlan)     |
+------------------+     +---------------------+     +---------------------+
                                |                              |
                                v                              v
+------------------+     +---------------------+     +---------------------+
| Catalog 快照     | --> |  非 DML:立即执行   |     |  compilePlan()      |
|   (JSON)         |     |  DML:延迟批量处理  |     |  translatePlan()    |
+------------------+     +---------------------+     +---------------------+
                                                              |
+------------------+     +---------------------+              |
| 资源调优配置     | --> | injectResourceSpec()|<-------------+
|   (JSON)         |     |    (算子级注入)     |
+------------------+     +---------------------+
                                |
                                v
                       +---------------------+
                       | executeInternal()   |
                       |   (反射提交作业)    |
                       +---------------------+

关键设计决策:

  • DDL 立即执行 —— 脚本切分过程中立即执行 DDL,因为后续语句可能依赖 Catalog 状态。
  • DML 延迟执行 —— 将 DML 批量解析、编译、翻译,以便在提交前向 Transformation DAG 注入资源规格。
  • 通过 SlotSharingGroup 注入资源 —— 绕过 Flink 的 isPartialResourceConfigured() 检查,实现 selective 的算子级资源调优。

能力边界

它是什么:

  • 一个生产级的 Flink SQL Application 模板。
  • 一座桥梁,连接 Flink SQL 脚本与外部元数据及细粒度资源控制。

它不是什么:

  • 不是 Flink SQL Gateway —— 遵循 flink run 作业提交范式,而非交互式 Gateway 模式。
  • 不是 工具类库 —— 它是一个带 main() 方法的应用模板,不是一个引入依赖即可使用的工具包。

边界承诺:

  • 零修改 Flink 引擎、Planner 或 SQL 语义。
  • 不提供任何自定义 SQL 方言 —— 执行结果与原生 Flink SQL 完全一致。
  • 用户配置的 Flink 参数原样透传,不做任何改动。
posted @ 2026-06-02 18:43  tonyabasy  阅读(7)  评论(0)    收藏  举报