磁场转动100万匹头像

Day5 - Flink SQL 生产实战:连接器、CEP、Checkpoint 与 PyFlink UDF

前四天走完了 Flink 的入门 → DataStream API → 窗口原理 → Flink SQL 窗口与 JOIN。本篇聚焦"Day4 没覆盖的生产级能力":Flink SQL 命令行与执行模式、各类连接器(CDC/Doris/文件监控)、SQL Hint 与 DISTINCT 优化、Window Join、CEP 模式检测、Checkpoint 故障恢复、PyFlink 自定义函数。内容定位在"把 SQL 从写 demo 用到生产环境"。


一、SQL 命令行与执行模式

1.1 启动 SQL Client

有集群时先起 Yarn Session(Day1 的 Standalone 本地集群也可以,生产推荐 Yarn):

# 启动 Yarn 会话集群(-d 后台运行,-tm 4G 指定 TaskManager 内存)
yarn-session.sh -tm 4G -d

# 进入 SQL Client
sql-client.sh

Client 启动后,先设置输出模式(表格模式便于查看实时流更新):

SET 'sql-client.execution.result-mode' = 'table';
-- table 和 tableau 两种都可以,table 更直观

1.2 批模式 vs 流模式

Flink SQL 是批流一体的,同一套 SQL 可以切换两种执行模式:

-- 流处理模式(默认)
SET 'execution.runtime-mode' = 'streaming';

-- 批处理模式
SET 'execution.runtime-mode' = 'batch';
模式适用数据执行方式输出特点
streaming(流)有界/无界流都可持续运行、持续输出输出 INSERT/UPDATE/DELETE 三种变更
batch(批)仅无界流不行跑完退出输出最终结果,只有 INSERT

关键区别:流模式下无界表的聚合状态会无限增长,必须用窗口或加状态 TTL;批模式下程序跑完自动释放所有状态。

1.3 一个例子看清差异

用 filesystem connector 读 HDFS 上的 CSV 文件(有界流):

CREATE TABLE students (
  id STRING, name STRING, age INT, sex STRING, clazz STRING
) WITH (
  'connector' = 'filesystem',
  'path' = 'hdfs://master:9000/data/students',
  'format' = 'csv',
  'csv.field-delimiter' = ','
);

批模式执行聚合:

SET 'execution.runtime-mode' = 'batch';
INSERT INTO clazz_num
SELECT clazz, COUNT(1) AS num FROM students GROUP BY clazz;
-- 执行一次,输出最终结果后作业退出

流模式执行聚合:

SET 'execution.runtime-mode' = 'streaming';
INSERT INTO clazz_num
SELECT clazz, COUNT(1) AS num FROM students GROUP BY clazz;
-- 作业持续运行,每来一条新数据就输出新结果(包含 UPDATE)

流模式处理有界流的特点:作业不会自动结束,你需要手动 cancel(./bin/flink cancel <job-id>)。


二、连接器实战

Flink SQL 通过 connector 参数连接各种外部系统,这是流处理"数据进得来、结果出得去"的基础。

2.1 测试连接器 datagen

快速生成随机数据做功能验证,不需要依赖 Kafka。无界流,每秒生成指定条数。

CREATE TABLE students_datagen (
  id   STRING,
  name STRING,
  age  INT,
  sex  STRING,
  clazz STRING
) WITH (
  'connector' = 'datagen',
  'rows-per-second' = '5',          -- 每秒 5 条
  'fields.id.length'   = '10',      -- 字符串长度
  'fields.name.length' = '2',
  'fields.sex.length'  = '1',
  'fields.clazz.length' = '4',
  'fields.age.min'     = '1',       -- 数值范围
  'fields.age.max'     = '100'
);

SELECT * FROM students_datagen;

实战用途:本地写 SQL 逻辑时先用 datagen 验证,不用起 Kafka。确认逻辑对了再换 Kafka。

2.2 filesystem 连接器

连接 HDFS/S3/本地文件系统。有界流 vs 无界流的关键差异靠 source.monitor-interval:

-- 有界流:一次性读取目录下现有文件,读完退出
CREATE TABLE students_batch (...) WITH (
  'connector' = 'filesystem',
  'path' = 'hdfs://master:9000/data/students',
  'format' = 'csv'
);

-- 无界流:每隔 5 秒扫描目录,有新文件就读进来
CREATE TABLE students_stream (...) WITH (
  'connector' = 'filesystem',
  'path' = 'hdfs://master:9000/data/students',
  'format' = 'csv',
  'source.monitor-interval' = '5000'
);

流模式写入 HDFS 的坑:GROUP BY 产生的是更新结果流(同一个 key 会多次输出 UPDATE),直接写入 filesystem 会每行一条,文件无限增长。解决方案:

-- 方案 1:用 canal-json format 写变更流(每行带 INSERT/UPDATE/DELETE 类型)
CREATE TABLE sink_canal (...) WITH (
  'connector' = 'filesystem',
  'path' = '...',
  'format' = 'canal-json'   -- 变更流格式
);

-- 方案 2:只写 INSERT 流(过滤掉 UPDATE/DELETE)
INSERT INTO sink_json
SELECT * FROM (...) WHERE op = 'INSERT';

2.3 mysql-cdc 连接器(增量同步)

Day4 的 Regular Join/Lookup Join 都是读 JDBC(有界流,只读一次)。如果要实时同步 MySQL 的增删改,用 mysql-cdc——基于 Debezium,先全表扫描,再监控 Binlog。

前置要求(MySQL 端):

  • Binlog 格式必须是 ROW:SET GLOBAL binlog_format = 'ROW';
  • 账号需要 REPLICATION SLAVE, REPLICATION CLIENT, SELECT 权限
CREATE TABLE clazz_num_cdc (
  clazz STRING,
  num BIGINT,
  PRIMARY KEY (clazz) NOT ENFORCED
) WITH (
  'connector' = 'mysql-cdc',
  'hostname' = 'master',
  'port'     = '3306',
  'username' = 'root',
  'password' = '123456',
  'database-name' = 'bigdata',
  'table-name'    = 'clazz_num'
);

SELECT * FROM clazz_num_cdc;

CDC 输出的流包含三种变更:

  • +I INSERT:新增行
  • -U / +U UPDATE:旧行 + 新行
  • -D DELETE:删除行

把这个流接到其他 Sink(Doris、Kafka)就能实现实时数据同步管道。

2.4 Doris 连接器

Doris 是 OLAP 型数据库,列式存储、向量化执行,实时数仓的典型存储。写入 Doris 有几个避坑点:

CREATE TABLE students_doris (
  id STRING, name STRING, age INT, sex STRING, clazz STRING,
  PRIMARY KEY (id) NOT ENFORCED
) WITH (
  'connector' = 'doris',
  'fenodes'        = 'master:8034',   -- Doris FE HTTP 端口
  'benodes'        = 'master:8041',   -- Doris BE brpc 端口(可选)
  'table.identifier' = 'bigdata.students',
  'username'       = 'root',
  'password'       = '123456',
  'sink.label-prefix' = 'doris_label' -- 幂等写入标签前缀
);

INSERT INTO students_doris SELECT * FROM students_batch;

版本冲突坑:Flink Doris Connector 版本和 Flink/Doris 版本必须匹配。典型错误:

NoClassDefFoundError: org/apache/doris/flink/DorisExecutionEnv

对策:下载对应版本的 flink-doris-connector-<flink-version>-<doris-version>.jar,替换 Flink 的 lib/ 目录里的旧包后重启集群。

Doris 侧建表(用 UNIQUE KEY 模型,支持主键去重):

CREATE TABLE IF NOT EXISTS students (
  id VARCHAR(20), name STRING, age INT, sex STRING, clazz STRING
)
UNIQUE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 10
PROPERTIES ("replication_num" = "1");

2.5 print / blackhole(测试 Sink)

  • print:把结果打印到 TaskManager 日志里,快速验证;
  • blackhole:什么都不做,纯测试吞吐(压测用)。
-- print
CREATE TABLE print_table (...) WITH ('connector' = 'print');
INSERT INTO print_table SELECT * FROM students_datagen;

-- blackhole:把并发提升到 50000 测试极限吞吐
INSERT INTO blackhole_table
SELECT * FROM students_datagen /*+ OPTIONS('rows-per-second'='50000') */;

三、并行度与 SQL Hint

3.1 设置并行度

并行度 = 每个 TaskManager 的 Slot 数 × TaskManager 数。Flink 没显式设置时默认为 1:

SET 'parallelism.default' = '2';

每个算子独立并行度的设置(Hint 方式):

-- 只给本次查询设置 Source 并行度
SELECT * FROM cars /*+ OPTIONS('scan.parallelism'='4') */;

3.2 SQL Hint 实战

Hint 可以在不重新建表的情况下,临时修改连接器参数,常用于测试不同的消费起点或并行度:

-- 临时让这个查询从 earliest-offset 开始消费
SELECT * FROM cars /*+ OPTIONS('scan.startup.mode'='earliest-offset') */;

Hint 不影响表定义,只在本次查询里生效。常用 Hint:

Hint 参数作用
scan.startup.mode修改 Kafka 消费起点
scan.startup.timestamp-millis从指定时间戳开始
scan.parallelismSource 并行度

四、DISTINCT 与 ROW_NUMBER 去重

4.1 DISTINCT 的问题

流模式下直接用 DISTINCT 是全量去重,所有历史数据都要保存在状态里:

-- 状态无限增长,跑久了 OOM
SELECT DISTINCT * FROM students_datagen;

4.2 用 ROW_NUMBER 替代 DISTINCT

原理:按去重字段分区,取每个分区的第一条,状态只需要保存去重字段的主键值,内存占用大幅降低:

INSERT INTO blackhole_table
SELECT id, name, age, sex, clazz FROM (
  SELECT *,
    ROW_NUMBER() OVER (PARTITION BY id ORDER BY PROCTIME()) AS r
  FROM students_datagen /*+ OPTIONS('rows-per-second'='50000') */
) AS a
WHERE r = 1;

对比:

方案状态大小说明
DISTINCT *存所有已出现过的完整行数据量指数增长
ROW_NUMBER ... WHERE r = 1只存已出现过的 idO(N),可控

五、Window Join(窗口内关联)

Day4 讲了 Regular / Interval / Temporal / Lookup 四种 JOIN,漏掉了Window Join——把两个流先套同一个窗口,再在窗口内关联。

5.1 语法

SELECT a.id, a.name, b.cid, b.score
FROM (
  SELECT * FROM TABLE(
    TUMBLE(TABLE a_stream, DESCRIPTOR(eventtime), INTERVAL '1' MINUTES)
  )
) AS a
JOIN (
  SELECT * FROM TABLE(
    TUMBLE(TABLE b_stream, DESCRIPTOR(eventtime), INTERVAL '1' MINUTES)
  )
) AS b
ON a.id = b.sid
AND a.window_start = b.window_start
AND a.window_end   = b.window_end;

5.2 为什么状态有界

两个流各自开窗 → 窗口结束就输出 → 关联时只需要处理同一窗口时间范围内的数据,窗口关了数据就清空。状态大小 = 窗口大小内的数据量 × 并行度,有界。

5.3 实战:学生信息 + 成绩(按分钟关联)

CREATE TABLE students_kafka_eventtime (
  id STRING, name STRING, age INT, sex STRING, clazz STRING,
  eventtime TIMESTAMP(3),
  WATERMARK FOR eventtime AS eventtime
) WITH (
  'connector' = 'kafka', 'topic' = 'students',
  'properties.bootstrap.servers' = 'master:9092',
  'properties.group.id' = 'testGroup',
  'scan.startup.mode' = 'latest-offset',
  'format' = 'csv', 'csv.ignore-parse-errors' = 'true'
);
-- 输入:id,name,age,sex,clazz,eventtime
-- 例:1500100001,施笑槐,22,女,文科六班,2026-10-10 10:10:04

CREATE TABLE scores_kafka_eventtime (
  sid STRING, cid STRING, score DOUBLE,
  eventtime TIMESTAMP(3),
  WATERMARK FOR eventtime AS eventtime
) WITH (
  'connector' = 'kafka', 'topic' = 'scores',
  'properties.bootstrap.servers' = 'master:9092',
  'properties.group.id' = 'testGroup',
  'scan.startup.mode' = 'latest-offset',
  'format' = 'csv', 'csv.ignore-parse-errors' = 'true'
);
-- 输入:sid,cid,score,eventtime
-- 例:1500100001,1000001,98,2026-10-10 10:10:04

-- 1分钟窗口内关联,状态只有1分钟
SELECT a.id, a.name, b.cid, b.score, a.window_start, a.window_end
FROM (
  SELECT * FROM TABLE(
    TUMBLE(TABLE students_kafka_eventtime, DESCRIPTOR(eventtime), INTERVAL '1' MINUTES)
  )
) a
JOIN (
  SELECT * FROM TABLE(
    TUMBLE(TABLE scores_kafka_eventtime, DESCRIPTOR(eventtime), INTERVAL '1' MINUTES)
  )
) b
ON a.id = b.sid
AND a.window_start = b.window_start
AND a.window_end   = b.window_end;

5.4 五种 JOIN 完整对比(Day4 + Day5)

JOIN 类型状态典型场景
Regular Join无限(危险)有界流全量关联
Interval Join有界(时间区间)双流时间相近关联
Temporal Join小(版本表常驻)关联"当时的版本"
Lookup Join极小(实时查库)流关联外部维表
Window Join有界(窗口大小)同一时间窗口内的双流关联

六、CEP 模式检测(MATCH_RECOGNIZE)

Day4 没覆盖的全新能力:用 SQL 在流里检测复杂事件模式,典型场景——反欺诈、告警、趋势检测。

6.1 语法骨架

SELECT * FROM 表名
MATCH_RECOGNIZE (
  PARTITION BY 分组键       -- 按谁分组(如账户 ID)
  ORDER BY 时间列            -- 必须按时间升序
  MEASURES                   -- 匹配成功后输出哪些字段
    A.xxx AS a_xxx,
    B.yyy AS b_yyy
  PATTERN (规则)             -- 模式序列,如 (A B)、(A{3} B)、(A B+ C)
  DEFINE                     -- 定义每个模式变量的条件
    A AS 条件1,
    B AS 条件2
) AS T;

6.2 案例 1:交易欺诈检测

业务规则:同一个账户,如果出现一笔小于 $1 的交易,紧接着出现一笔大于 $500 的交易,判定为可疑。

先建表:

CREATE TABLE transactions (
  id STRING,
  price DOUBLE,
  eventtime TIMESTAMP(3),
  WATERMARK FOR eventtime AS eventtime
) WITH (
  'connector' = 'kafka', 'topic' = 'transactions',
  'properties.bootstrap.servers' = 'master:9092',
  'properties.group.id' = 'testGroup',
  'scan.startup.mode' = 'latest-offset',
  'format' = 'csv', 'csv.ignore-parse-errors' = 'true'
);

往 Kafka 输入数据:

001,100,2026-11-09 10:10:10   -- 正常
001,0.5,2026-11-09 10:10:11   -- A 条件触发(< $1)
001,600,2026-11-09 10:10:13   -- B 条件触发(> $500),匹配成功!
001,400,2026-11-09 10:10:20   -- 没有 < $1 的前置,不匹配
001,0.4,2026-11-09 10:10:21   -- 又一个 A
001,100,2026-11-09 10:10:22   -- 金额不够大,不匹配
001,1000,2026-11-09 10:10:24  -- B 条件触发,匹配成功!

用 LAG 也能做,但 CASE WHEN 复杂,而且要手工排 state。CEP 写法更清晰:

SELECT * FROM transactions
MATCH_RECOGNIZE (
  PARTITION BY id
  ORDER BY eventtime
  MEASURES
    A.price AS a_price,   -- 匹配到的 A 的价格
    B.price AS b_price,   -- 匹配到的 B 的价格
    A.eventtime AS a_time,
    B.eventtime AS b_time
  PATTERN (A B)
  DEFINE
    A AS price < 1,
    B AS price > 500
) AS T;

输出:每次匹配成功输出一行,包含 A 和 B 两个事件的字段。

6.3 案例 2:连续 N 次小额后大额 + 时间限制

规则升级:连续 3 次 < $1 的交易 + 紧跟着一笔 > $500 + 必须在 5 秒内完成。

SELECT * FROM transactions
MATCH_RECOGNIZE (
  PARTITION BY id
  ORDER BY eventtime
  MEASURES
    FIRST(A.price)  AS first_a_price,
    AVG(A.price)    AS avg_a_price,
    SUM(A.price)    AS sum_a_price,
    B.price         AS b_price,
    A.eventtime     AS a_time,
    B.eventtime     AS b_time
  PATTERN (A{3} B) WITHIN INTERVAL '5' SECOND   -- A 连续 3 次,且整体必须在 5 秒内
  DEFINE
    A AS price < 1,
    B AS price > 500
) AS T;

PATTERN 语法说明:

写法含义
(A B)A 后面紧跟 B
(A{3} B)A 连续出现 3 次后面跟 B
(A B+ C)A → B(1 次或多次)→ C
... WITHIN INTERVAL '5' SECOND整个模式匹配必须在 5 秒内完成

MEASURES 里可用的聚合函数:FIRST()、LAST()、AVG()、SUM()、MAX()、MIN(),都是针对匹配到的该模式变量的所有事件计算。

6.4 案例 3:股票价格持续下跌期

找出单只股票价格连续下跌(中间允许小幅反弹一次)的时期。

CREATE TABLE ticker (
  symbol STRING,
  price DOUBLE,
  tax DOUBLE,
  rowtime TIMESTAMP(3),
  WATERMARK FOR rowtime AS rowtime
) WITH (...);

输入数据:

ACME,12,1,2026-11-09 10:00:00
ACME,17,2,2026-11-09 10:00:01   -- 下跌
ACME,19,1,2026-11-09 10:00:02   -- 下跌
ACME,21,3,2026-11-09 10:00:03   -- 下跌
ACME,25,2,2026-11-09 10:00:04   -- 上涨,C 触发
ACME,18,1,2026-11-09 10:00:05
...
SELECT * FROM ticker
MATCH_RECOGNIZE (
  PARTITION BY symbol
  ORDER BY rowtime
  MEASURES
    A.price   AS a_price,
    B.price   AS b_price,
    C.price   AS c_price,
    A.rowtime AS a_time,
    C.rowtime AS c_time
  AFTER MATCH SKIP PAST LAST ROW    -- 匹配完成后,从最后匹配行的下一行继续
  PATTERN (A B+ C)                  -- A → B(下跌,1次或多次)→ C(反弹)
  DEFINE
    B AS (LAST(price, 1) IS NULL AND B.price < A.price)
         OR B.price < LAST(price, 1),   -- 比上一行低
    C AS C.price > B.price                -- 比上一行高,反弹
) AS T;

AFTER MATCH SKIP PAST LAST ROW:匹配到一次后,从模式的最后一个匹配行的下一行重新开始。不设置的话默认会从下一行开始,具体行为看 Flink 版本。

6.5 CEP vs LAG 的取舍

维度LAGCEP (MATCH_RECOGNIZE)
模式复杂度只能写简单相邻行逻辑支持序列、数量、时间窗口
可读性CASE WHEN 嵌套多了就乱PATTERN 规则清晰
灵活性需要预定义所有中间条件聚合 FIRST/LAST 很方便
代价每次新事件都要更新所有聚合只在匹配成功时输出一次

什么时候用哪个:简单相邻行逻辑(如"上一次价格 - 当前价格")用 LAG;复杂序列(如"连续 N 次 A 后接 B"、“趋势检测”)用 CEP。


七、Checkpoint 与故障恢复

Day3 讲过 Checkpoint 概念,这里讲SQL 层面怎么配置和怎么恢复。

7.1 flink-conf.yaml 配置

# 开启 checkpoint,每 10 秒一次
execution.checkpointing.interval: 10000

# 作业取消后是否保留 checkpoint(恢复时要用)
execution.checkpointing.externalized-checkpoint-retention: RETAIN_ON_CANCELLATION

# 同时进行的 checkpoint 数量(一般保持 1,避免互相影响)
execution.checkpointing.max-concurrent-checkpoints: 1

# 两个 checkpoint 之间的最小间隔(避免堆积)
execution.checkpointing.min-pause: 0

# 精确一次语义(生产必须 EXACTLY_ONCE,性能差一点)
execution.checkpointing.mode: EXACTLY_ONCE

# 超时时间(默认 10 分钟,作业大时要调大)
execution.checkpointing.timeout: 10min

# 允许连续失败几次 checkpoint 后作业挂
execution.checkpointing.tolerable-failed-checkpoints: 0

# 状态后端(hashmap 内存、rocksdb 磁盘)
state.backend: hashmap

# Checkpoint 持久化路径(HDFS/S3/OSS)
state.checkpoints.dir: hdfs://master:9000/flink/checkpoints
state.savepoints.dir: hdfs://master:9000/flink/savepoints

7.2 SQL 文件提交

编写 word_count.sql:

CREATE TABLE words (word STRING) WITH (...);

CREATE TABLE print_table (
  word STRING, num BIGINT
) WITH ('connector' = 'print');

INSERT INTO print_table
SELECT word, COUNT(1) AS num FROM words GROUP BY word;

首次提交(会从头消费):

sql-client.sh -f word_count.sql

7.3 故障恢复

作业中途挂了(比如重启 Yarn、kill 进程),怎么恢复?

  1. 找到 checkpoint 目录:HDFS 上 state.checkpoints.dir 下有每个作业的随机 ID 目录,里面有 chk-N 子目录:
hdfs dfs -ls /flink/checkpoints/
# c56e7c3dcf8352a285dfd954084c6258/

hdfs dfs -ls /flink/checkpoints/c56e7c3dcf8352a285dfd954084c6258
# chk-23/
  1. SQL 里加恢复路径(放在 INSERT 语句前面):
SET 'execution.savepoint.path' =
  'hdfs://master:9000/flink/checkpoints/c56e7c3dcf8352a285dfd954084c6258/chk-23';
  1. 重新提交:
sql-client.sh -f word_count.sql
# 作业从 chk-23 恢复,数据不丢不重

7.4 EXACTLY-ONCE vs AT-LEAST-ONCE

模式语义代价
EXACTLY_ONCE数据恰好一次,不丢不重Checkpoint 过程中会阻塞数据流(对齐栅栏)
AT_LEAST_ONCE数据至少一次,可能重复不阻塞,吞吐高,但下游要能容忍重复

生产推荐 EXACTLY_ONCE,除非对吞吐量要求极高且下游天然幂等。


八、PyFlink 自定义函数

Flink SQL 内置函数不够时,可以用 Python 写 UDF。

8.1 编写 Python UDF

# upper.py
from pyflink.table.udf import udf

@udf(result_type='STRING')
def upper(s: str):
    """把字符串转大写"""
    return s.upper()

8.2 启动 SQL Client 加载 Python

# --pyFiles 加载 Python 文件,--pyExecutable 指定 Python 解释器
sql-client.sh \
  --pyFiles upper.py \
  --pyExecutable /opt/miniconda3/envs/flink_env/bin/python3

8.3 注册与调用

CREATE FUNCTION python_upper AS 'upper.upper' LANGUAGE PYTHON;

SELECT python_upper('shujia');   -- 输出 SHUJIA
SELECT python_upper(name) FROM students_batch;   -- 批量转大写

注意:upper 是文件名,upper() 是函数名,完整限定为 upper.upper。

8.4 PyFlink 环境要求

  • Python 版本 >= 3.8;
  • 安装 pyflink:pip install apache-flink==1.18.0(和 Flink 版本对齐);
  • 集群上所有 TaskManager 都能访问到这个 Python 解释器路径。

九、典型坑点

坑点 1:filesystem 连接器扫不到新文件

现象:上传文件到目录,SQL 没有输出。

原因:忘了加 'source.monitor-interval',默认一次性读完就结束了。

对策:加监控间隔(毫秒):'source.monitor-interval' = '5000'。

坑点 2:mysql-cdc 全量扫描锁大表

现象:启动 CDC 任务后业务查询被阻塞。

原因:默认全量扫描用 LOCK TABLES 会对表加读锁。

对策:MySQL 开 gtid_mode=ON,CDC 配置 'scan.incremental.snapshot.enabled' = 'true'(增量快照模式,不锁表)。

坑点 3:Doris 写入报版本不兼容

现象:NoClassDefFoundError 或 NoSuchMethodError。

对策:连接器版本必须匹配 Flink 和 Doris,替换 lib/ 下的旧 jar,重启集群(sql-client 里热加载没用)。

坑点 4:DISTINCT 后作业越来越慢

原因:全量去重把所有历史数据都存在状态里。

对策:用 ROW_NUMBER + PARTITION BY 替代,只存去重主键。

坑点 5:Window Join 漏数据

原因:两个流各自有水位线,但 window_join 取的是各自窗口内的数据。如果某个流数据少导致窗口迟迟不触发,另一流的数据已经被清理了。

对策:保证两个流水位线都在推进(空闲分区用 withIdleness),或放宽 WHERE 里的时间条件到 INTERVAL JOIN。

坑点 6:CEP 匹配后结果丢了

原因:AFTER MATCH SKIP 控制不正确,导致同一个事件被多次匹配或跳过。

对策:测试时先去掉 AFTER MATCH,用默认行为验证结果,再加。

坑点 7:Checkpoint 太大导致超时

原因:窗口状态、CDC 状态没有配置好 TTL,状态无限增长。

对策:SET 'table.exec.state.ttl' = '1h';或用 RocksDB 状态后端(磁盘+内存,比 hashmap 省内存)。

坑点 8:保存点路径写错导致恢复失败

现象:execution.savepoint.path 设置了路径但作业还是从头消费。

原因:路径里的 chk-23 目录不存在,或路径格式错误。

对策:先 hdfs dfs -ls 确认目录存在,路径开头必须是 hdfs://(不是 hdfs:/)。

坑点 9:PyFlink 加载 UDF 报 ModuleNotFoundError

现象:No module named 'upper'。

原因:--pyFiles 路径没找到文件,或 Python 解释器版本不对。

对策:用绝对路径,比如 --pyFiles /home/flink/upper.py;确认 python3 -c "import upper" 在集群节点上能跑通。

坑点 10:Hint 改不了 connector 参数

原因:Hint 只能改可变参数(并行度、消费起点),connector URL 等必选参数不能被 Hint 覆盖。

对策:建表时就要确定好必选参数,Hint 只用于可调项。


十、五天知识串联

天主题核心关键词
Day1入门与环境概念、架构、WordCount、提交作业
Day2DataStream APISource / map / keyBy / sum / Sink / 并行度
Day3窗口原理事件时间、水位线、四类窗口、允许迟到
Day4Flink SQL 窗口与 JOINTVF、事件时间 DDL、Temporal/Lookup/Interval Join
Day5生产实战connector、datagen、mysql-cdc、doris、Hint、DISTINCT、Window Join、CEP、Checkpoint、PyFlink UDF

Day4 vs Day5 的分工(刻意避重):

能力Day4 深度Day5 深度
窗口 TVF(TUMBLE/HOP/CUMULATE/SESSION)✅ 原理 + 实战跳过,直接用
事件时间 DDL✅ 完整跳过
ORDER BY / OVER✅ 完整跳过
Regular/Interval/Temporal/Lookup Join✅ 完整跳过,新增 Window Join
CEP (MATCH_RECOGNIZE)❌ 没讲✅ 完整(三个案例)
Checkpoint 配置与恢复Day3 提概念✅ SQL 层面完整
PyFlink UDF❌ 没讲✅ 完整
连接器(datagen/mysql-cdc/doris)❌ 没讲✅ 完整
DISTINCT 优化❌ 没讲✅ 完整

Day6 可以继续学:状态管理(TTL、MapState/ValueState)、RocksDB 状态后端调优、Flink CDC 全链路实时数仓、CEP 复杂模式高级语法、Flink on K8s 部署、Table API 和 SQL 互操作。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/YEEMOLY/article/details/170556365

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--