本文整理自宋辛童(阿里云智能高级技术专家)老师,梅源(阿里云智能资深技术专家)、李麟(阿里云智能高级技术专家)老师,在 Flink Forward Asia 2024 主会场的分享。三位老师共同为大家带来这场关于 Flink 2.0 的技术分享。主要分为以下几个内容:
一、Streaming
二、Stream-Batch Unification
三、Streaming Lakehouse
四、AI
宋辛童(阿里云智能高级技术专家):Apache Flink 于 2014 年被捐赠给 Apache 基金会,其第一个正式版本 Flink 1.0 则于 2016 年 3 月发布。从 Flink 1.0 发布以来,这些年 Flink 一直以小版本的形式进行技术演进,例如 1.16、1.17 等。然而,到了去年,我们意识到随着 Flink 技术的发展和迭代,会面临越来越多的新挑战和问题。为了有效解决这些问题,我们需要对 Flink 的现有技术架构进行系统性的大规模升级。因此,去年 4 月,我们在 Apache Flink 社区开始筹备 Flink 2.0 版本的计划。
整个筹备过程经历了相当长的时间,经过 Flink 1.18、1.19、1.20 三个小版本的迭代,终于在不久前的 10 月,在柏林的 Flink Forward 会议上,我们发布了 Flink 2.0 的预览版本。正式的 Flink 2.0 版本预计将在明年年初与大家见面。Flink 2.0 的筹备过程耗时接近两年,从去年的 4 月到明年年初发布,其原因除了技术架构升级的复杂性,还有就是我们将在这次大版本升级中引入一系列非兼容性的改动,希望为用户和生态合作伙伴留出足够的时间来适应这些改动。
这些改动包括移除一些旧的、过时的 API,例如 DataSet API、Scala DataStream API 和旧的 Connector API 等等。同时,我们对现有的 API,如 DataStream API、Table API、REST API 和 Flink/SQL Client,也进行了小幅度的更新。
在配置方面,旧的 flink-conf.yaml 配置文件被彻底移除,新的 config.yaml 配置文件全面对接标准的 YAML 生态。此外我们也清理了一系列陈旧的配置项。需要提醒大家的是,Flink 1.X 和 Flink 2.0 之间无法保证 100% 的 Checkpoint (CP) 和 Savepoint (SP) 状态兼容性。这主要是因为 Flink 对其序列化框架进行了多项升级和改造。不过大家不用担心,Flink 社区将准备相应的迁移工具,来帮助用户进行非兼容性状态的迁移。此外,Java 8 的支持将不再提供。Per-job 部署模式也将在 2.0 版本中移除,用户可以更广泛地采用 Application 的部署模式。
在这里,我们提到的一个重要改动是移除了陈旧的 Connector API。这意味着,如果现有的 Connector 使用了这些老的 API,就需要经过适配过程才能在 Flink 2.0 中继续使用。Flink 社区计划在 Flink 2.0 发布时,首批支持几个最常见的 Connector,如 Kafka、Paimon、JDBC 和 Elasticsearch。后续,我们将陆续支持其他 Connector。我们也非常欢迎社区的开发者们加入我们的行列,共同加速 Flink 2.0 Connector 生态的适配过程。
前面我们已经介绍了 Flink 2.0 版本筹备的进程以及非兼容性的改动。接下来,我们将主要介绍 Flink 2.0 中,以及未来几年我们的一些重点技术方向和技术演进情况。
一、Streaming-Flink 2.0存算分离云原生化
梅源(阿里云智能资深技术专家):接下来由我来给大家分享 Flink 2.0 中关于流处理部分最重要的能力提升,也就是我们的存算分离云原生化架构升级,以及使这一架构升级成为可能的 ForSt DB。存算分离架构升级主要解决状态变大带来的问题,比如成本变大,性能降低,Checkpoint 不稳定以及恢复慢等问题。
今年是 Flink 成为 Apache 顶级项目的十周年。在这十年间,Flink 已经发展成为流计算的事实标准,拥有非常丰富的上下游生态,并广泛应用于各行各业。然而,回归本质,Flink 流计算的核心要义仍旧集中在三个方面:分布式、高性能和有状态计算。这三个特点是 Flink 成为流计算标准并取得成功的核心因素。
1. 分布式高性能有状态计算
什么是有状态计算?对于流处理来说,输入数据是无界且持续的,并根据这些持续的输入实时输出数据,状态实际上是计算的中间结果。每处理一条新的数据,都需要访问和更新中间状态结果。因此,状态存储的访问延迟对处理性能有着至关重要的影响。也正是这个原因,Flink 将计算和存储放在一起,如中间的图示所示,以加速计算和提升处理性能。
状态计算需要周期性地进行快照,以便在发生故障时能够重新加载恢复或在扩缩容时在不同节点重新分配状态。同时,作为一个分布式系统,Flink 需要确保状态快照的一致性。这里的一致性,是指对于一个完整的快照,其所有分布式节点的快照都需要对应于相同的输入位点,这就是 Flink 的 Checkpoint 流程。
2. 云原生新需求
存算一体的架构为 Flink 提供了高性能支持,但随着时间的推移,我们也发现这种架构对 Flink 的发展带来了一些限制。特别是当状态数据量增大,以及云原生部署变得越来越普遍时,我们对 Flink 的整体架构提出了四个新的需求,总结如下:
- 计算和存储解绑:希望计算和存储能够独立地进行扩展和缩减,以解决状态存储变大带来的额外成本
- 容器化资源的均匀使用:目前在存算一体架构下,一致性快照过程需要在短时间内占用大量资源,导致资源使用出现高峰。因此,希望资源使用能够更加均匀和平缓,提升系统稳定性。
- 利用海量低价云存储:随着硬件的发展,使用本地盘不再是强需求,希望能够充分利用海量低价云存储,进一步降低成本
- 带状态的快速扩缩容:这是云原生的弹性需求,无状态容器的扩缩容相对简单轻量,但有状态的容器因为涉及到状态重新分配,需要更多时间。如何实现秒级的状态重新分配,也是存算分离目标解决的需求。
Flink 2.0 中的存算分离云原生化架构升级正是为这些问题而设计的。这些问题归根结底是存储的问题,目前市场上没有可直接解决这些问题的存储解决方案。因此,我们开发并开源了 ForSt DB。ForSt 是 "For Streaming" 的缩写,这是一个非常简单直接且富有美好愿景的名字。
3. ForSt DB
那么 ForSt DB 可以实现什么功能呢?它最大的特点是原生支持 DFS(分布式文件系统)。简单来说,就是从左边的存算一体架构变到右边的架构:从将本地盘与计算绑定在一起,到可以直接进行 DFS 的直读直写。
通过这种方式,可以很自然地解决之前提到的四个问题:
- 状态存储不再依赖本地盘:因为可以直接读写 DFS,所以不再需要依赖本地盘。
- 快速扩缩容和容错恢复:由于可以直接从 DFS 读取数据,因此扩缩容和故障恢复的速度可以非常快。
- 轻量化的 Checkpoint:因为状态和 Checkpoint 都存储在 DFS 上,可以共享文件,所以 Checkpoint 的设计可以更加轻量。
- 资源使用的平缓稳定:由于 Checkpoint 流程变得轻量,资源使用也会更加平稳和稳定。
从左到右的转变看似简单,似乎只是将本地盘替换为 DFS。但实际上,这并非易事。如果仅仅是这样简单的替换,这个问题不会花费多年来解决。真正的挑战在于如何高效地实现这一转变,并确保系统的性能和可靠性在新架构下能够得到保障。
首先要解决的问题是 DFS 直读直写对 Flink 带来的性能回退影响。正如之前提到的,存储访问延迟对 Flink 的性能有非常关键的影响。要知道,本地盘访问的存储延迟与 DFS 直读直写的访问延迟之间基本上有 10 倍的差距。如果我们直接连接 DFS,性能回退 10 倍,这基本上是不可接受的,也无法使用。这其实也是为什么多年来 Flink 存算分离的尝试很多,但都不怎么成功的原因。我们是如何解决这个问题的呢?
其次,对于 Flink 这样的流计算系统,其快照框架和内置数据库需要原生且无缝的深度集成。只有这样,才能保证快照的一致性,否则从理论上是无法保证一致性的,会产生和写外部数据库一样的重复问题。因此,我们在这部分也做了大量的工作。
第三,对存算分离来说,缓存(Cache)是非常重要的性能提升部分。如果有本地盘可以作为缓存,我们也不能忽视这一点。因此,ForSt DB 主要从以上三个方面全面升级了 Flink 的存算分离架构。
我们计划在明年初发布 Flink 2.0,上图左边蓝框内的六大部分即为存算分离在 2.0 版本中包含的内容。这里想特别提到的是异步化执行部分。异步化执行的主要目的是实现计算和存储的分离执行,从而在执行层面实现存算分离。这其实是我们能够使存算分离性能达标的一个最主要原因。
我们的 ForSt DB 预览版本已经在两个月前发布了,大家如果感兴趣,可以去看看,里面有一些实验数据的分享https://github.com/ververica/ForSt/releases/tag/v0.1.2-beta 。正式版本将与 Flink 2.0 一起发布,届时将支持端到端的 Flink 存算分离各项功能,并完成 70% SQL 异步算子的改写。
以下是 2.0 存算分离版本的所有技术文档,社区公开可见,大家感兴趣可以查看。下期会有兰兆千老师对这部分(存算分离的状态存储 ForSt DB)的分享,大家如果感兴趣,可以关注公众号的下期推送。
上图是 ForSt DB 预览版的实验结果,这些结果都是与目前社区最新版本的 Apache Flink 1.20 进行对比得出的,这里强调三点:
- 在同步模式下,存算分离的性能预期内下降了 10 倍。如果我们仅仅将本地盘替换为 DFS,或者使用当前的外置数据库,我们得到的结果就是性能下降 10 倍。因此为了加速这个过程,采用了异步模式。
- 在异步执行模式下,即使没有任何本地盘缓存,进行 DFS 直读直写的情况下,存算分离的性能仅略有下降。这个略有下降在测试中表现为大约 15% 的性能差异(从 19.2 到 16)。
- 在异步模式下,如果我们使用 50% 的本地盘作为缓存,存算分离的性能比 Flink 1.20 还提升了 50%。如果你对这个神奇现象的实现感兴趣,可以期待下期兰兆千老师的分享。
二、Stream-Batch Unification
1. 什么是流批一体?
李麟(阿里云智能高级技术专家):接下来,我将与大家探讨 Flink 的流批一体。什么是流批一体呢?从数据工程的角度来看,可以用一句话来描述:存储一份数据,只需编写一份代码,用一套引擎来同时满足离线和实时的数据需求。
一份数据意味着存储的统一,而只写一份代码则隐含着对计算引擎的统一要求。因为即便是用 SQL 语言作为用户 API,也难免会遇到不同 SQL 方言的差异,最直接的方式就是使用一套执行引擎来同时支持流处理和批处理。
然而,流批一体的理想非常美好,但在实际的实现过程中仍然面临着许多问题和挑战。
让我们通过数据工程的架构演变,来看看现实中遇到的问题。左边是经典的 Lambda 架构,从数据链路分层来看,首先是数据的摄入层,完成数据的入仓入湖。接下来是存储层,分别包括离线存储和实时消息队列。最后,面向具体的计算引擎进行业务开发。
从上到下,离线和实时是两套不同的技术栈和开发流程,这就导致了一系列的问题,存储冗余、技术栈复杂、离线和实时需要开发两遍,还容易出现数据不一致。在 2020 年的“双十一”期间,我们与天猫的技术团队合作,在数仓的明细层到 ADS 层落地了新的流批一体数据开发架构。但受限于当时的存储成本,没能进一步推广。
这两年来流批一体的湖存储开始流行。在 Lakehouse 架构下,像 Paimon 这样的存储能够同时支持流读流写、批读批写,结合 Flink 计算引擎,能在很大程度上实现计算和业务开发的统一。然而,过程中仍然存在一些未能很好解决的问题。例如,当业务需要进行数据回刷时,流模式可能太慢,而使用性能更好的批模式刷新时,原有的 SQL 无法直接复用,仍需进行修改。
回到 Flink 的视角,十年来,从流批一体的引擎架构出发,到 SQL 引擎生产可用,实现了计算的统一。Paimon,新的流批一体湖存储实现了存储的统一。Flink CDC 为用户提供了摄入层的统一。要做到全链路的流批一体,让用户只开发一份代码究竟还缺什么?
流批代码无法统一的核心问题在于编程模型的区别。批处理是面向静态数据编程,通常会基于分区操作,比如 INSERT OVERWRITE PARTITION,每个分区的数据有确定的过滤条件。而流计算是面向无限流编程,不会有这种分区操作。这就导致两种模型下写出来的 SQL 无法做到完全统一。
2. 流批一体 Materialized Table
为了解决上述问题,我们引入了全新的流批一体 Materialized Table(物化表)。以下是一个简单的物化表创建脚本,它由 3 部分组成
首先是CREATE MATERIALIZED TABLE
来创建表对象,在 Paimon Catalog 下创建的就是一张 Paimon 表。
第二部分是用户期望的数据新鲜度 FRESHNESS 定义,可以根据需求来灵活设置。
最后是 Query 部分来描述用户的业务逻辑。
通过使用物化表,用户可以从原来的命令式 SQL 转变为一种声明式的一体化 SQL。用户只需要关注业务逻辑和数据的新鲜度,剩下的工作交由 Flink 引擎自动完成。
使用 Materialized Table 后,用户能够只写一份代码,同时还能简化日常运维工作。例如,在大型促销活动期间,用户可以向业务方提供秒级实时数据。在促销活动结束后,为了降低运行成本,用户可以通过一键切换,将数据的新鲜度调整为天级。在数仓中经常使用的数据回刷,也只要一行语句就能触发完成。
除了帮助用户实现只写一份代码、提高开发运维效率之外,Materialized Table 还提供了更多的成本优化空间。Materialized Table 支持流式持续刷新、批式全量刷新以及增量刷新 3 种模式。
从上图右侧的成本曲线来看,流式持续刷新 也就是 Flink 流模式,能够提供秒级的新鲜度,但成本并不会因为改成小时级新鲜度就明显降低,因为流模式需要常驻的计算资源。全量刷新就是批计算,在小时到天级别的一次性计算中,成本最低,但如果用全量刷新去实现分钟级的新鲜度,就会因为每次刷新都是基于全量数据重算,导致成本剧增。增量刷新介于两者之间,在分钟到小时级新鲜度上,通过只计算增量数据来降低成本。
这样,Materialized Table 可以为用户在数据新鲜度、时效性和成本上提供灵活的切换选择。用户只需提出期望的数据新鲜度,Flink 会自动根据成本选择最佳性价比的刷新方式。
Materialized Table 补全了 Flink 流批一体化的最后一块拼图,但这仅仅是一个开始。未来,我们将继续推进计算和存储的联合优化。在计算层面,我们将适配新的存算分离存储,以实现更好的算子异步化吞吐,并持续优化计算执行模式,包括物化数据的共享。在存储的联动上,我们将充分发挥新存储的能力,感知数据分布,实现更好的计算下推和卸载。
面向新的湖流一体存储,我们希望通过融合,为用户提供从秒级、分钟级到小时级的灵活切换体验。
三、Streaming Lakehouse
宋辛童(阿里云智能高级技术专家):流式湖仓架构在之前的分享中已经介绍过,这是基于 Flink 和 Paimon 两个项目打造的全链路数据实时化、全链路流批一体化以及全链路数据可查询的数据分析架构。为了更好地在生产环境中落地,这套架构对 Flink 和 Paimon 两个项目的深度集成提出了很高的要求。为此,Flink 和 Paimon 的社区进行了紧密合作,在这方面进行了大量努力。
首先,在场景方面,我们针对一些用户常用的场景进行了深度优化。例如,在宽表拼接场景中,用户通常会使用 Join 操作,而 Join 操作在数据处理中是相对昂贵的。有了 Flink 和 Paimon 的组合,在某些特殊情况下(如有唯一主键时),我们可以使用 Paimon 的 Partial Update 特性来取代 Join,从而大幅降低计算成本。
此外,在需要使用 Paimon 作为维表进行查询和关联的场景中,我们也针对不同的细分场景进行了深度定制优化。例如,在 Flink 的 Lookup Join 中,会考虑 Paimon 中数据的分布情况来优化存储和计算。针对单 Key 热点问题,我们适配了 Flink 的 Skew Join 特性。此外,Flink 目前正在开发一个名为 Proc-time Temporal Join 的功能,这在某些场景下,尤其是维表更新不频繁的情况下,可以取代 Lookup Join 提升计算效率。在流批一体新场景下,例如 Flink CDC 的全增量一体化数据同步过程中,或者李麟老师刚刚介绍的 Materialized Table 新场景下,我们也做了很好的支持和适配。
在引擎能力方面,最重要的是 Flink 读写 Paimon 表的性能问题。我们在这方面做了大量工作,包括对接 Paimon 最新的 Feature——Deletion Vectors,并对 Flink 写入到 Paimon 表中的数据进行按字段分区和排序,从而优化下游业务访问 Paimon 数据的读取性能。此外,我们还做了文件读写的 Native 实现。在可维护性方面,目前 Paimon 所有的湖表管理操作(如 Compaction、元数据清理和管理等)都可以通过 Flink SQL 中的 Procedures 方便快捷地完成。
半结构化数据在 AI 和特征工程等场景下尤为常见,为了更好地读写和处理这些半结构化数据,Flink 和 Paimon 社区正在合作探索一些新技术,例如在 SQL 中引入 Variant 数据类型的技术。以上是关于 Flink 与 Paimon 集成情况的介绍。
四、AI
最后一部分是关于 AI 的话题,这是我们在社区和与合作伙伴交流时经常被问到的问题。现在 AI 大模型非常火热,那么 Flink 在其中能够发挥什么作用呢?今天我想和大家聊一聊这个话题。
在这里,我们展示了最近非常火热的 RAG(Retrieval-Augmented Generation)业务场景。RAG 的核心思想是利用特定的知识库(例如企业内部数据的知识库或特定领域的知识库)来辅助大模型进行内容生成。
在这种场景下,主要分为两条链路:
- 知识库数据的预处理:将知识库中的数据调用大语言模型进行 Embedding 向量化,并将得到的向量存储在向量数据库中。
- 用户查询请求的处理:当用户发起查询请求时,对该查询进行 Embedding 向量化,将得到的向量与向量数据库中的其他向量进行近似搜索匹配,找出与查询最相关的知识库文档,并将这些文档作为上下文与查询一起提供给大语言模型进行内容生成。
在这样的 RAG 架构下,Flink 能够发挥哪些作用呢?Flink 的最大优势在于实时计算和实时处理。我们可以换个角度思考,RAG 架构下是否存在实时计算和处理的需求?答案显然是有的。
在知识库方面,知识库并不是一成不变的静态数据集,而是不断发展、演进和变化的。当知识库中的文档更新时,能否快速反映在大语言模型生成的内容中?这对上面这条黄色的链路有很高的实时处理/实时计算需求。其次,用户发起查询后希望尽快看到大模型生成的内容结果,在这个过程中可能很多时间花费在大语言模型的内容生成上,但链路本身的实时性也有很高的要求。
在这个架构中,有一条关键链路是对大语言模型的调用,无论是进行 Embedding 向量化还是内容生成,都会反复调用大语言模型。为了更好地支持这条关键链路的实时化,我们在 Flink SQL 中引入了全新的语法,原生支持一些 AI 模型的使用。
现在,用户可以在 Flink SQL 中像定义 Catalog 一样定义一些 AI 模型,包括输入输出的数据结构以及一些模型参数等。通过这样的定义,用户可以在 SQL 查询中像使用函数或表值函数一样调用 AI 模型。通过这种方法,我们可以将对大语言模型的调用与调用前后的数据处理、结果校验等逻辑有机地整合在一起。
目前这项工作的设计方案在 Flink 社区已经讨论通过,处于开发阶段。这也是 Flink 社区在 AI 大模型领域的初步探索。我们相信未来 Flink 社区将在 AI 大模型领域进行更多的探索和实践,包括对半结构化和非结构化数据的处理,以及对向量数据库的查询访问和处理等。
以上就是我们今天为大家带来的关于 Flink 2.0 的一些技术分享。感谢大家的关注和支持!