摘要:本文整理自俞航翔、陈婧敏、黄鹏程老师所撰写的大状态作业调优实践指南。由于内容丰富,本文分享 Flink SQL 作业大状态导致反压的调优原理与方法,主要分为以下三个部分:
-
状态算子的产生
-
问题诊断方法
-
调优方法
前篇:Flink⼤状态作业调优实践指南:Datastream 作业篇
Tips:点击「阅读原文」跳转阿里云实时计算 Flink~
05Flink SQL 作业大状态导致反压的调优原理与方法
作为一种特定领域语言,SQL 的设计初衷是隐藏底层数据处理的复杂性,让用户通过声明式语言来进行数据操作。而Flink SQL 由于其架构的特殊性,在实现层面通常需引入状态后端 配合 checkpoint 来保证计算结果的最终一致性。目前 Flink SQL 生成状态算子的策略由优化器根据配置项 + SQL 语句来推导,想要在处理有状态的大规模数据和性能调优方面游刃有余的话,用户还是需要对 SQL 状态算子生成机制和管理策略有一定了解。
5.1 运行原理:状态算子的产生
5.1.1 基于优化器推导产生的状态算子
状态算子 | 状态清理机制 |
---|---|
ChangelogNormalize | 生命周期 TTL |
SinkUpsertMaterlizer | |
LookupJoin(*) |
(1)ChangelogNormalize
ChangelogNormalize 作为一个状态算子,旨在对涉及主键语义的数据变更日志进行标准化处理 [1] 。通过这一算子,可以有效地整合和优化数据变更记录,确保数据的一致性和准确性。该状态算子会在以下两种场景出现 [2] :
-
使用了带有主键的 upsert 源表
upsert 源表特指在保持主键顺序一致性的前提下,仅产生基于主键的 UPDATE(包括 INSERT 和 UPDATE_AFTER)及 DELETE 操作的变更数据表。例如,upsert-kafka 便是支持这类操作的典型连接器之一。此外,用户也可以通过重写自定义源表连接器中的 getChangelogMode 方法,实现 upsert 功能。
@Override
public ChangelogMode getChangelogMode() {
return ChangelogMode.upsert();
}
-
用户显式设置 'table.exec.source.cdc-events-duplicate' = 'true'
解析:在使用 at-least-once 语义进行CDC事件处理时,可能会产生重复的变更日志。在需要 exactly-once 语义时,用户需要开启此配置项来对变更日志进行去重。
举例:
当出现该算子时,上游数据将按照 FlinkSQL 源表 DDL 中定义的主键做一次 hash shuffle 操作后使用 ValueState 来存储当前主键下最新的整行记录。更新状态和向下游发送变更的过程如下图所示。处理第二条 -U(2, 'Jerry', 77) 的时候 state 已经是 empty 了, 说明截止目前 +I/+UA 和 -D/-UB 已经两两抵销, 当前这条 retract 消息就是重复的, 可以丢弃。
(2)SinkUpsertMaterializer
SinkUpsertMaterializer 是一种状态算子,专门用于处理具有主键定义的结果表,并确保数据的物化操作 符合upsert语义。在数据流更新过程中,如果无法保证upsert的特定要求,即按照主键进行更新时保持数据的唯一性和有序性,优化器会自动引入此算子。它通过维护基于结果表主键的状态信息,来确保这些约束得到满足。
具体来说,upsert语义包含两个方面:唯一性和有序性。唯一性指的是在传统数据库中,主键必须唯一,不能有重复的值。而有序性则意味着对于任何一次主键的更新,相关的变更日志必须遵循特定的顺序,即UPDATE_BEFORE操作必须在UPDATE_AFTER操作之前进行。
为了实现这一功能,当SinkUpsertMaterializer被触发时,系统会首先根据FlinkSQL结果表DDL中定义的主键,对上游数据执行hash shuffle操作。然后,使用ValueState来存储每个主键下的所有不可合并数据。这里的“合并”指的是,对于特定的数据项,增加(+I或+U)和删除(-D和-U)操作可以相互抵消,从而保持数据的一致性。更多关于这一主题的深入讨论和解释,可以参考相关文档[3]。
常见的三种场景:
① 结果表定义主键,而写入该结果表的数据丢失了唯一性
解析:这些操作通常包括但不限于
-
源表缺少主键,而结果表却设置了主键。
-
在向结果表插入数据时,忽略了主键列的选择,或者错误地使用了源表的非主键数据填充结果表的主键。
-
源表的主键数据在转换或经过分组聚合后出现精度损失,例如将BIGINT类型降为INT类型。
-
对源表的主键列或经过分组聚合之后的唯一键进行了运算,如数据拼接或将多个主键合并为单一字段。
CREATE TABLE students (
student_id BIGINT NOT NULL,
student_name STRING NOT NULL,
course_id BIGINT NOT NULL,
score DOUBLE NOT NULL,
PRIMARY KEY(student_id) NOT ENFORCED
) WITH (...);
CREATE TABLE perf