流批统一分析与问题
为了了避免批处理理和流处理理维护两套代码,Flink社区⼀一直试图在【批处理理是流处理理的⼀一个特例例】的思想指引下从底层 架构层⾯面实现流批统⼀一,上层通过SQL的⽀支持统⼀一编程模型,但是整个过程是曲折的,最初架构如下:
上述架构中上层SQL⽀持的不好,底层也存在诸多问题:
- 从Flink⽤户⻆度
- 开发的时候,Flink SQL⽀持的不好,就需要在两个底层API中进⾏选择,甚⾄维护两套代码 不同的语义、
- 不同的connector⽀持、不同的错误恢复策略…
- Table API也会受不同的底层API、不同的connector等问题的影响
- 从Flink开发者⻆度
- 不同的翻译流程,不同的算⼦实现、不同的Task执⾏…
- 代码难以复⽤
- 两条独⽴的技术栈需要更多⼈⼒功能开发变慢、性能提升变难,bug变多
流批统一架构演进
阿⾥⼀直是Flink的忠实⽤户和贡献者,⼀度在内部维护了⼀套⾃⼰的Flink衍⽣版本,就是所谓的Blink,其主要的 改进就是流批统⼀。
Bink最初的想法就是:既然批是流的⼀个特例,是否可以统一?
Blink本身就在做去DataSet的⼯作,在 Blink 捐赠给 Apache Flink 之后,社区就致⼒于为 Table API 和 SQL 集成 Blink 的查询优化器和 runtime。第⼀步,我们将 flink-table 单模块重构成了多个⼩模块(FLIP-32)。这对于 Java 和 Scala API 模块、优化器、以及 runtime 模块来说,有了⼀个更清晰的分层和定义明确的接⼝。
紧接着,社区扩展了 Blink 的 planner 以实现新的优化器接⼝,所以现在有两个插件化的查询处理器来执⾏ Table API 和 SQL:1.9 以前的 Flink 处理器和新的基于 Blink 的处理器。基于 Blink 的查询处理器提供了更好地 SQL 覆盖 率(1.9 完整⽀持 TPC-H,TPC-DS 的⽀持在下⼀个版本的计划中)并通过更⼴泛的查询优化(基于成本的执⾏计 划选择和更多的优化规则)、改进的代码⽣成机制、和调优过的算⼦实现来提升批处理查询的性能。除此之外,基 于 Blink 的查询处理器还提供了更强⼤的流处理能⼒,包括⼀些社区期待已久的新功能(如维表 Join,TopN,去 重)和聚合场景缓解数据倾斜的优化,以及内置更多常⽤的函数。
注意:Flink1.12之前的版本,Table API和SQL处于活跃开发阶段,并没有实现流批统⼀的所有特性,所以使⽤的时 候需要慎重
注意:从Flink1.12开始,Table API和SQL就已经成熟了,可以在⽣产上放⼼使⽤
下图是Flink Table API/SQL的执⾏过程:
理解动态表和连续查询
为了在流处理上使⽤关系代数(Table API/SQL),Flink引⼊了动态表(Dynamic Tables)的概念。 因为流处理⾯对的数据是⽆界数据流,这和我们熟悉的关系型数据库中保存的“表” 完全不同,所以⼀个设想就是把 数据流转换成 Table,然后执⾏SQL操作,但是SQL的执⾏结果就不是⼀成不变的,⽽是随着新数据的到来不断更 新的。 可以随着新数据的到来,不断在之前的结果上更新,这样得到的表,在 Flink Table API 概念⾥,就叫做“动态 表”(Dynamic Tables)。
动态表是 Flink 对流数据的 Table API 和 SQL ⽀持的核⼼概念。与表示批处理数据的静态 表不同,动态表是随时间 变化的。动态表可以像静态的批处理表⼀样进⾏查询,查询⼀个动 态表会产⽣持续查询(Continuous Query)。 连续查询永远不会终⽌,并会⽣成另⼀个动态表。 查询(Query)会不断更新其动态结果表,以反映其动态输⼊表 上的更改。
上图是在数据流上执⾏关系查询时数据流与动态表的转换关系图,主要步骤如下:
- 将数据流转换为动态表
- 在动态表上进⾏连续查询,并⽣成新的动态表
- ⽣成的动态表再转换为新的数据流
动态表
这⾥以⼀个如下schema的点击事件流查询来帮助⼤家理解动态表和连续查询的概念:
CREATE TABLE clicks ( user VARCHAR, -- ⽤户名 url VARCHAR, -- ⽤户访问的URL cTime TIMESTAMP(3) -- 访问时间 ) WITH (...);
为了执⾏关系查询,⾸先得把数据流转换为动态表。下图左侧为点击流,右侧为动态表,流上的新增事件都会对应 动态表上的insert操作
连续查询
接下来,我们在动态表上执⾏连续查询⽣成⼀个新的动态表(结果表),连续查询不会停⽌,它会根据输⼊表新数 据的到来不断查询计算并更新结果表
在上图中,我们在click动态表上执⾏了group by count聚合查询,随着时间推移,右边动态结果表随着左测输⼊表 每条数据的变化⽽变化。
上图稍微复杂⼀些,group by count聚合,另外还加⼊了⼀个翻滚窗⼝,统计1⼩时翻滚窗⼝内每个⽤户的访问次 数。随着时间推移,右边动态结果表随着左测输⼊表数据的变化⽽变化,但是每个窗⼝的结果是独⽴的,且计算是 在每个窗⼝结束时才触发的。
动态表转换数据流
与常规的数据库表⼀样,动态表可以通过插⼊(Insert)、更新(Update)和删除(Delete) 更改,进⾏持续的 修改。将动态表转换为流或将其写⼊外部系统时,需要对这些更改进⾏编 码。Flink 的 Table API 和 SQL ⽀持三种 ⽅式对动态表的更改进⾏编码:
1)仅追加流(Append-only stream,即insert-only)
仅通过插⼊INSERT更改来修改的动态表,可以直接转换为“仅追加”流。这个流中发出的数据就是动态表中新增的每 ⼀个事件。
2)撤回流(Retract stream)
插⼊、更新、删除都⽀持的动态表会转换为撤回流。
撤回流包含两类消息:添加(Add)消息和撤回(Retract)消息。
动态表通过将 INSERT 编码为 add 消息、DELETE 编码为 retract 消息、UPDATE 编码为被更改⾏(更改前)的 retract 消息和更新后⾏(新⾏)的 add 消息,转换为 retract 流。
下图显示了将动态表转换为 Retract 流的过程:
3)更新插⼊流(Upsert流)
Upsert 流包含两种类型的消息:Upsert 消息和 delete 消息。
转换为 upsert 流的动态表, 需要有唯⼀的键 (key)。 通过将 INSERT 和 UPDATE 更改编码为 upsert 消息,将 DELETE 更改编码为 DELETE 消息, 就可以将具有唯⼀键 (Unique Key)的动态表转换为流。
下图显示了将动态表转换为 upsert 流的过程: