一、背景与目标

随着实时计算场景对性能要求的不断提升,传统基于行式处理的 Flink 引擎在复杂 SQL 场景下面临性能瓶颈。为满足用户“降本增效”的核心诉求,我们于 2025 年启动 Flink 向量化引擎 自研项目,目标是通过向量化执行技术大幅提升流批一体场景下的端到端性能。

项目成果显著:

  • 简单 SQL 性能提升 ≥7 倍
  • 复杂 SQL(含 Join/Window 等)性能提升 ≥3 倍

二、整体架构设计

Flink 向量化引擎采用 三层架构

┌───────────────────────┐
│     用户 SQL (Flink)   │
└──────────┬────────────┘
           ▼
┌───────────────────────┐
│    Flink 内核层改造     │ ← 行转列 / 微批 / Plugin 机制
└──────────┬────────────┘
           ▼
┌───────────────────────┐
│      胶水层(Java)     │ ← RexNode → Substrait Plan
└──────────┬────────────┘
           ▼
┌───────────────────────┐
│    Native 执行层(C++) │ ← Substrait → DuckDB 计划 + SIMD + Zero Copy
└───────────────────────┘

核心创新点:

  • 行转列(Row-to-Columnar):在算子边界将 RowData 转换为列式内存格式,为向量化执行奠定基础。
  • 流转微批(Stream-to-MicroBatch):在保证低延迟前提下,聚合小批次数据以提升 SIMD 利用率。
  • SIMD 指令加速:利用 CPU 向量指令并行处理列数据。
  • Zero Copy 数据传输:减少 Java 与 Native 层间的数据拷贝开销。
  • Substrait 标准计划中间表示:实现 Flink 与 Native 执行器的解耦。

三、Flink 内核层改造

为支持向量化执行,我们在 Flink 源码中引入 Plugin 机制,实现非侵入式扩展。

1. 新增 StreamExecPlugin 接口(SPI)

public interface StreamExecPlugin {
    void updateGraph(StreamGraph graph);
}

通过 Java SPI 机制加载,允许外部模块在 StreamGraph 构建完成后介入,替换算子实现。

2. 关键类修改

表格

类名 修改内容
PlannerModule 在解析阶段通过 ServiceLoader 加载 StreamExecPlugin 实例
StreamExecutionEnvironment 在 executeAsync() 中调用 plugin.updateGraph(graph);新增 setExecContext(Object context) 方法用于传递 RelNode
CommonExecCalc 等算子 调用 setExecContext(relnode),将逻辑计划节点传递给胶水层
StreamTask 根据是否存在 Plugin 动态构建 OperatorChain,替换为 Native 算子
2.1 基于 SPI 的插件化扩展机制

通过PlanerModule类结合ServiceLoader实现StreamExecPlugin接口实现类的加载,打造胶水层与 Flink 内核的解耦胶水层,基于 Java SPI 机制实现插件的动态加载,无需修改 Flink 原生核心代码,提升架构扩展性。

2.2 定义 StreamExecPlugin 核心扩展接口

自定义实现StreamExecPlugin接口,核心包含updateGraph(StreamGraph graph)方法,该方法为胶水层的核心入口:胶水层实现此接口,遍历 StreamGraph 中的所有算子,将算子的原生 Java 实现替换为Native 实现,完成 StreamGraph 的向量化增强。

2.3 StreamTask 与 OperateChain 适配

改造StreamTask类,增加向量化插件判断逻辑:根据是否加载了StreamExecPlugin插件,动态更新OperateChain的构建逻辑,使 Task 执行时调用 Native 算子实现,而非 Flink 原生 Java 算子。

2.4 StreamExecutionEnvironment 扩展

对 Flink 核心执行环境类StreamExecutionEnvironment做两处关键扩展:

  1. executeAsync方法中加载向量化插件,并调用插件的updateGraph方法完成 StreamGraph 的增强;
  2. 添加setExecContent(Object obj)方法,支持将算子的RelNode(关系表达式节点)传递至胶水层,为后续计划转换提供核心元数据。
2.5 CommonExecCalc 算子改造

改造CommonExecCalc类(Flink 核心计算算子基类),在算子初始化阶段调用setExecContent方法,将当前算子的RexNode(核心表达式节点,包含 Project、Filter、Join 等计算逻辑)传递至胶水层,实现算子计算逻辑的透传。

2.6 StreamGraph 层核心增强

在 StreamGraph 层完成Native Plan 转换,为每个需要向量化执行的算子(Project、Filter、Join、Window、Source、Sink 等)添加 Native 标识,并将算子的 RexNode、RelNode 等核心信息透传至胶水层,作为后续生成 Native 执行计划的依据。

3. 执行流程集成

Flink 原有 Job 提交流程:

Program API → StreamGraph → JobGraph → ExecutionGraph → Physical Execution

向量化扩展后,在 StreamGraph 阶段 插入转换逻辑:

  • 遍历所有算子(Project/Filter/Join/Window 等)
  • 提取其 RexNode 表达式树
  • 通过 Plugin 将算子标记为“可向量化”
  • 替换 Operator 实现为 Native Wrapper

四、胶水层设计(Java + C++)

4.1 Java 胶水层

职责:

  • 接收 Flink 传递的 RexNode(如 RexInputRefRexCallRexLiteral
  • 将其转换为 Substrait IR Plan
  • 管理 Native 执行上下文生命周期
  • 实现数据格式转换:RowData → 列式存储格式(Arrow-like)

支持算子:

  • Source / Sink
  • Project / Filter
  • Hash Join / Window Agg
  • UDF(需注册映射)

具体功能如下:

  1. 自研 Substrait 转 DuckDB 执行计划:DuckDB 本身不支持 Substrait 计划解析,此功能为完全自研,实现标准化 Substrait 计划到 DuckDB 原生执行计划的转换,是向量化执行的核心自研点;
  2. 多数据源扩展:完成自研数据源适配,支持 Nexmark 基准数据源、Pulsar 消息队列数据源、Hudi 湖仓数据源的抽取与列式数据加载;
  3. UDF 扩展:开发 DuckDB 原生不支持的业务自定义函数,实现 UDF 的向量化执行,适配业务多样化的计算需求;
  4. Native 向量化计算执行:基于 DuckDB 引擎执行向量化计算,充分利用 SIMD、zero copy 等技术,最大化发挥硬件算力;
  5. 执行结果回传:将 Native 计算结果按需转换为列式 / 行式数据,回传至 Flink 内核或直接输出至 Sink。

4.2 C++ Native 执行层

职责:

  • 解析 Substrait Plan
  • 自研 Substrait → DuckDB 执行计划转换器(DuckDB 官方不支持 Substrait)
  • 扩展 DuckDB:
    • 支持 Pulsar、Hudi 数据源读取
    • 实现缺失的 UDF(如时间窗口函数、复杂类型处理)
    • 集成 SIMD 优化内核(如 AVX2/AVX-512)
  • 支持 Zero Copy 内存管理(通过 JNI DirectBuffer)

注:DuckDB 本身为嵌入式分析引擎,我们对其进行了深度定制,使其支持流式微批执行模型。

具体功能如下:

  1. RexNode 精细化解析:针对 Flink 算子的 RexNode 不同类型(RexInputRefRexCallRexLiteral)做差异化解析,提取算子的计算逻辑、输入引用、常量值等核心信息;
  2. Substrait 计划生成:将解析后的 RexNode 转换为标准化 Substrait 执行计划,Substrait 作为跨引擎的执行计划中间表示,实现 Flink 与 DuckDB 的计划解耦;
  3. 全算子 Native 实现开发:完成 Source、Sink、Project、Filter、Join、Window 等所有核心算子的 Native 胶水层实现,确保算子逻辑可被 Native 引擎识别与执行;
  4. 数据行转列转换:将 Flink 原生的行式RowData转换为列式存储格式数据(Column Storage Format Data),为 Native 向量化计算提供数据基础;
  5. 跨层通信适配:实现与 C++ 胶水层的高效通信,完成 Substrait 计划、列式数据的传递。

五、核心优化技术融合

Flink 向量化引擎的性能提升,基于多项底层优化技术的深度融合,贯穿从数据输入到计算执行的全流程,具体如下:

  1. 行转列(Row to Column):将行式存储的 RowData 转换为列式存储,使相同数据类型的字段连续存储,为 SIMD 硬件加速提供基础,提升内存访问效率与计算并行度;
  2. 流转微批(Stream to Micro-Batch):将流式的连续数据转换为微批处理,增大计算批处理量,减少调度开销,提升向量化计算的效率;
  3. SIMD 技术:单指令多数据,利用 CPU 的 SIMD 指令集,对列式数据进行批量并行计算,大幅提升计算效率;
  4. Zero Copy(零拷贝):在数据流转过程中,减少内存拷贝次数(如直接内存映射、堆外内存使用),降低 CPU 与内存开销,提升数据传输效率;
  5. Native 编译执行:基于 C++ 实现核心计算逻辑,避免 Java 虚拟机的性能开销(如 GC、解释执行),充分利用硬件底层算力。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐