Flink高性能读写StarRocks内表剖析

188 阅读5分钟

StarRocks 是一种高性能的分布式分析型数据库,广泛应用于实时数据分析场景。Apache Flink 作为流批一体化的计算框架,与 StarRocks 的结合能够高效处理大规模数据。为实现更高的读写性能,推荐使用 StarRocks 官方提供的 flink-connector-starrocks,而非 Flink 官方的 Flink JDBC Connector。

一、Flink-Connector-StarRocks 概述

1.1 为什么选择 Flink-Connector-StarRocks?

相较于 Flink JDBC Connector,StarRocks 自研的 Flink Connector 具有以下显著优势:

  • 高性能并行读取:支持从 StarRocks 集群的各个 Backend(BE)节点并行读取数据,充分利用分布式架构,大幅提升读取效率。
  • 高效批量写入:通过 Stream Load 机制,Flink 在内存中缓存小批量数据后以 HTTP PUT 请求的方式导入,StarRocks 的 Frontend(FE)将请求分发至 BE 节点,由 BE 节点作为 Coordinator 协调导入任务,降低 FE 负载。
  • 事务支持:从 StarRocks 2.4 和 Flink Connector 1.2.4 开始,支持通过两阶段提交(2PC)实现 exactly-once 语义的写入。
  • 灵活性与可扩展性:支持 DataStream API 和 Flink SQL 两种开发方式,满足不同开发需求。

1.2 适用场景

  • 实时 ETL:从 StarRocks 读取数据,经过 Flink 加工后写入目标表。
  • 数据分析:结合 Flink 的流批处理能力,对 StarRocks 数据进行实时或批量分析。
  • 数据湖加速:通过高效读写,加速数据湖与 StarRocks 之间的数据交互。

二、Flink 读取 StarRocks 数据

Flink 支持通过 DataStream API 和 Flink SQL 从 StarRocks 表中读取数据。读取操作要求用户账号具备目标表的 SELECT 权限。

2.1 DataStream API 读取示例(基于 Flink 1.16)

以下是一个完整的 DataStream API 读取示例:

2.1.1 添加依赖

在项目中添加 flink-connector-starrocks 依赖:

<dependency><groupId>com.starrocks</groupId><artifactId>flink-connector-starrocks</artifactId><version>1.2.10_flink-1.16</version><!-- 对于 Flink 1.19,可使用 <version>1.2.10_flink-1.19</version> --><scope>provided</scope></dependency>

2.1.2 代码示例

public class StarRocksSourceApp {public static void main(String[] args) throws Exception {//构建 StarRocksSourceStarRocksSourceOptions options = StarRocksSourceOptions.builder()                          .withProperty("scan-url""xxx:8030")                          .withProperty("jdbc-url""jdbc:mysql://xxx:9030")                .withProperty("username""xxx")                     .withProperty("password""123456")             .withProperty("table-name""score_board")            .withProperty("database-name""unload")               .build();TableSchema tableSchema = TableSchema.builder()                 .field("id", DataTypes.INT())                          .field("name", DataTypes.STRING())                          .field("score", DataTypes.INT())                          .build();StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();env.addSource(StarRocksSource.source(tableSchema, options)).setParallelism(5).print();env.execute("StarRocks flink source");    }}

2.2 Flink SQL 读取示例

Flink SQL 提供了更简洁的声明式方式,适合快速开发。

CREATE TABLE flink_sr_table(    `id` INT,    `name` STRING,    `score` INT)WITH(    'connector'='starrocks',    'scan-url'='xxx:8030',    'jdbc-url'='jdbc:mysql://xxx:9030',    'username'='xxx',    'password'='xxx',    'database-name'='unload',    'table-name'='score_board');
select * from flink_sr_table;

三、Flink 写入 StarRocks 数据

Flink 支持通过 DataStream API 和 Flink SQL 向 StarRocks 表写入数据。写入操作要求用户账号具备目标表的 SELECT 和 INSERT 权限。

3.1 DataStream API 写入示例(基于 Flink 1.16)

3.1.1 添加依赖

与读取相同,添加以下依赖:

<dependency><groupId>com.starrocks</groupId><artifactId>flink-connector-starrocks</artifactId><version>1.2.10_flink-1.16</version><scope>provided</scope></dependency>

3.1.2 代码示例

public class StarRocksSinkApp {public static void main(String[] args) throws Exception {     StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();     String[] records = new String[]{"500\tstarrocks-csv\t100","501\tflink-csv\t100"};     DataStream<String> source = env.fromElements(records);     StarRocksSinkOptions options = StarRocksSinkOptions.builder()                     .withProperty("jdbc-url""jdbc:mysql://xxx:9030")                               .withProperty("load-url""xxx:8030")                               .withProperty("database-name""unload")                               .withProperty("table-name""score_board")                               .withProperty("username""xxx")                               .withProperty("password""xxx")                               .withProperty("sink.properties.format""csv")                               .withProperty("sink.properties.column_separator""\t")                               .build();     // Create the sink with the options.     SinkFunction<String> starRockSink = StarRocksSink.sink(options);     source.addSink(starRockSink);     env.execute("StarRocks flink source");     }}

3.2 Flink SQL 写入示例

3.2.1 创建表

CREATE TABLE `score_board` (    `id` INT,    `name` STRING,    `score` INT,    PRIMARY KEY (id) NOT ENFORCED) WITH (   'connector' = 'starrocks',    'jdbc-url' = 'jdbc:mysql://xxx:9030',    'load-url' = 'xxx:8030',    'database-name' = 'unload',    'table-name' = 'score_board',    'username' = 'xxx',    'password' = 'xxx');
INSERT INTO `score_board` VALUES (1'starrocks'99), (2'flink'100);

3.3 写入并发与批量控制

  • 并发控制:
    • DataStream API:通过 Flink 作业的并行度设置(如 setParallelism)。
    • Flink SQL:通过 sink.parallelism 参数设置写入并发。
  • 批量写入触发条件:
    • 缓存数据大小达到 sink.buffer-flush.max-bytes。
    • 缓存行数达到 sink.buffer-flush.max-rows。
    • 距离上次刷新超过 sink.buffer-flush.interval-ms。

四、高性能实现原理

4.1 读取高性能实现

  1. 初始化:Flink 的 Source 任务通过 StarRocksSourceFunction 连接 StarRocks 的 FE,获取查询计划(QueryPlan),包含数据分片(Tablet)的分布信息。
  2. 并行读取:根据查询计划,Source 任务并行连接多个 BE 节点,每个子任务通过 TStarrocksExternalService.Client 使用 Thrift RPC 协议直接读取 Tablet 数据。
  3. 核心类:
    • StarRocksDynamicSourceFunction:负责连接 FE、获取查询计划并分发读取任务。

    • StarRocksSourceBeReader:通过 Thrift RPC 实现 BE 节点的数据读取。

在StarRocksDynamicSourceFunction构造函数中,会根据查询sql连接FE获取查询计划

图片图片

**
**

open方法,根据查询计划获取BE Tablet分区,同时构建对应的读取器StarRocksSrouceBeReader

图片

**
**

run方法,通过StarRocksSourceBeReader进行循环读取

图片

StarRocksSourceBeReader里通过 Thrift RPC 对 BE 进行实际的数据读取

图片

4.2 写入高性能实现

  • 批量写入:通过 Stream Load(或事务性 Stream Load)将数据缓存到内存后批量发送,减少网络交互。
  • 事务性 Stream Load(V2 模式):
    • 从 StarRocks 2.4 和 Flink 连接器 1.2.4 开始,支持事务性写入,通过两阶段提交(2PC)实现 exactly-once。
  • 异步写入与重试:异步提交数据,支持配置重试次数(如 sink.max-retries)。
  • 内存管理:通过 sink.buffer-flush.max-rows 和 sink.buffer-flush.max-bytes 控制缓存数据量,优化吞吐量与延迟。

StarRocksDynamicSinkFunction,在invoke方法里对数据进行序列化,然后通过 StarRocksSinkManager 写入

图片

DefaultStreamLoader,实现 Stream Load 的 HTTP 请求和响应处理,通过构建 HttpPut 生成对应的 Stream Load 任务

图片

具体管理事务的生命周期 TransactionStreamLoader(begin、prepare、commit、 rollback)

StarRocksSinkManager,在内存攒一批数据,超过条件就flush

图片
以上就是简单扼要的剖析flink-connector-starrocks对starrocks高效读写的原理,而jdbc本质还是通过mysql协议对starrocks进行读写操作,就必须经过FE,而这种方式,在读取比较大的结果集时,会对FE造成比较大的负担,因为结果集流量要通过FE;在写入的时候如果频繁的通过构造insert语句请求,也会对FE造成负担。

**