flink二开全流程
本文主要讲解一下fink二开相关工作,例如 模块分类,如何开发,开发后如何打包等等,顺路将一些开发经验与案例写入本文。
主要原因是前几天面试,被问到这部分东西,结果忘得光光的,好记性不如烂笔头啊。
1 flink二开基本知识
首先我们说下社区参与的问题,flink社区参与有很多方式,像代码提交,bug提交,帮助做代码审核 ,甚至文档翻译等等 都算。

首先我们看下这个 http@s:/@/iss@ues.apa@che.org/j@ira
issues,常搞开源的都清楚,是一个大家共同维护的提交代码的地方,flink自然也在这里
了解flink在模块设计上分为几个部分
flink的Component 分为一下几个大类,具体可以在下面看列表
https://issues.apache.org/jira/projects/FLINK?selectedItem=com.atlassian.jira.jira-projects-plugin:components-page
下面是我提交过的一个issue ,可以看到提交包括 类型,紧急度,模块,版本,标签,开发环境,语言等

等你提交完后台会自动分配一个commiter给你审核

很简单,下面我们说一下怎么做二开
2 flink 二开过程
1 打包
打包分很多种
1 完整分发包 包含所有模块的 tar.gz 安装包
2 打包修改后的单个模块 如果只修改了某个模块(如 flink-core),仅打包该模块避免全量编译
mvn clean install -pl flink-core -am -DskipTests -Dfast
2 如何二开
其实熟悉flink的同学都知道,flink是一个处理框架,将用户写的代码编译成graph,然后数据在里面游走,用户代码调用自己写的api与调用算子,其实本质上没有区别。
flink二开可以理解为是要实现一个数据处理逻辑,要开发一个方法类。
这个实现就是java中一些继承接口,实现功能等能力实现。
所以要提前确定,方法的入参 出参。而这个入参就来自我们要继承实现的方法定义。
我们这里需要明确,我们在调用api还好,我们知道指定的是哪个接口,在sql中,flink如何确定我们使用的是哪个方法呢,这个其实就是参数配置,在启动前各种参数会指定,我们解析哪些方法,例如连接器部分,他怎么知道我sink的是哪个数据库呢,就是写入的connector后指定的部分。
3 Merge Engin / DeltaJoin 实现案例
这里我们直接用一个实现案例来解释可能更清晰
1 Merge Engin
这是一个flink+paimon的能力,提供以sink端的一个操作能力,例如保存数据最大值,最小值,合集值等,这个合计,指的是目标库中已经存在的A,和本次来的数据B,得出A+B的结果并覆盖目标。这个能力跨越了数据的状态周期,同时也解决了flink超长周期状态过期的问题。

我们这里的二开就是实现一个flink+hologres的 merge engin能力。也就是connector的一个二开
1 flink连接器开发攻略
我们看一下flink1.9的代码目录

首先flink的的连接器模块 有一个基类 flink-connector-base 就是后续开发可以按照这个模板来
首先我们说一下 flink或flinksql 增加一个hologres的连接器 应该开发什么:

然后新增一个连接器需要:

flink-connector-hologres的代码目录模块应该是如下:
核心代码实现(分步详解)
步骤 1:编写 pom.xml(模块依赖)
核心是引入 Flink 基础依赖、Hologres JDBC 驱动,继承 Flink 父 pom:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connectors</artifactId>
<version>1.19.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>flink-connector-hologres</artifactId>
<name>Flink Connector Hologres</name>
<dependencies>
<!-- Flink 核心依赖 -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-common</artifactId>
<version>1.19.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-jdbc</artifactId>
<version>1.19.0</version>
</dependency>
<!-- Hologres JDBC 驱动 -->
<dependency>
<groupId>com.aliyun.hologres</groupId>
<artifactId>hologres-jdbc</artifactId>
<version>2.0.0</version>
<exclusions>
<exclusion>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 测试依赖(可选) -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-test-utils</artifactId>
<version>1.19.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<!-- 打包配置:生成 shaded 包,避免依赖冲突 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<artifactSet>
<includes>
<include>com.aliyun.hologres:*</include>
</includes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
步骤 2:定义配置项(HologresConnectorOptions)
声明 Hologres 连接器的专属配置(如 endpoint、dbname),以及通用 JDBC 配置:
package org.apache.flink.connector.hologres.config;
import org.apache.flink.configuration.ConfigOption;
import org.apache.flink.configuration.ConfigOptions;
import org.apache.flink.connector.jdbc.config.JdbcConnectorOptions;
import java.util.HashMap;
import java.util.Map;
public class HologresConnectorOptions {
// Hologres 专属配置
public static final ConfigOption<String> ENDPOINT = ConfigOptions
.key("endpoint")
.stringType()
.noDefaultValue()
.withDescription("Hologres 实例地址,格式:xxx-cn-hangzhou.hologres.aliyuncs.com:80");
public static final ConfigOption<String> DBNAME = ConfigOptions
.key("dbname")
.stringType()
.noDefaultValue()
.withDescription("Hologres 数据库名");
public static final ConfigOption<String> USERNAME = ConfigOptions
.key("username")
.stringType()
.noDefaultValue()
.withDescription("Hologres 用户名");
public static final ConfigOption<String> PASSWORD = ConfigOptions
.key("password")
.stringType()
.noDefaultValue()
.withDescription("Hologres 密码");
// 转换为 JDBC URL(Hologres 兼容 PostgreSQL JDBC 格式)
public static String getJdbcUrl(String endpoint, String dbname) {
return String.format("jdbc:postgresql://%s/%s?reWriteBatchedInserts=true", endpoint, dbname);
}
// 将 Hologres 配置转换为 JDBC 配置
public static Map<String, String> toJdbcOptions(Map<String, String> hologresOptions) {
Map<String, String> jdbcOptions = new HashMap<>(hologresOptions);
String endpoint = hologresOptions.get(ENDPOINT.key());
String dbname = hologresOptions.get(DBNAME.key());
jdbcOptions.put(JdbcConnectorOptions.URL.key(), getJdbcUrl(endpoint, dbname));
jdbcOptions.put(JdbcConnectorOptions.TABLE_NAME.key(), hologresOptions.get("table-name"));
jdbcOptions.put(JdbcConnectorOptions.USERNAME.key(), hologresOptions.get(USERNAME.key()));
jdbcOptions.put(JdbcConnectorOptions.PASSWORD.key(), hologresOptions.get(PASSWORD.key()));
return jdbcOptions;
}
}
步骤 3:实现工厂类(HologresDynamicTableFactory)
核心是注册连接器标识 hologres,解析配置并创建 Source/Sink 实例:
package org.apache.flink.connector.hologres.factory;
import org.apache.flink.configuration.ReadableConfig;
import org.apache.flink.connector.hologres.config.HologresConnectorOptions;
import org.apache.flink.connector.hologres.sink.HologresDynamicTableSink;
import org.apache.flink.connector.hologres.source.HologresDynamicTableSource;
import org.apache.flink.connector.jdbc.JdbcConnectorOptions;
import org.apache.flink.table.connector.sink.DynamicTableSink;
import org.apache.flink.table.connector.source.DynamicTableSource;
import org.apache.flink.table.factories.DynamicTableSinkFactory;
import org.apache.flink.table.factories.DynamicTableSourceFactory;
import org.apache.flink.table.factories.FactoryUtil;
import java.util.HashSet;
import java.util.Set;
public class HologresDynamicTableFactory implements DynamicTableSourceFactory, DynamicTableSinkFactory {
// 连接器唯一标识:SQL 中通过 'connector' = 'hologres' 引用
@Override
public String factoryIdentifier() {
return "hologres";
}
// 必填配置项
@Override
public Set<ConfigOption<?>> requiredOptions() {
Set<ConfigOption<?>> options = new HashSet<>();
options.add(HologresConnectorOptions.ENDPOINT);
options.add(HologresConnectorOptions.DBNAME);
options.add(HologresConnectorOptions.USERNAME);
options.add(HologresConnectorOptions.PASSWORD);
options.add(FactoryUtil.TABLE_NAME);
return options;
}
// 可选配置项(如写入批次大小、重试次数)
@Override
public Set<ConfigOption<?>> optionalOptions() {
Set<ConfigOption<?>> options = new HashSet<>();
options.add(JdbcConnectorOptions.SINK_BUFFER_FLUSH_MAX_ROWS);
options.add(JdbcConnectorOptions.SINK_BUFFER_FLUSH_INTERVAL);
return options;
}
// 创建 Source 实例
@Override
public DynamicTableSource createDynamicTableSource(Context context) {
FactoryUtil.TableFactoryHelper helper = FactoryUtil.createTableFactoryHelper(this, context);
ReadableConfig config = helper.getOptions();
helper.validate();
// 解析配置并创建 Source
return new HologresDynamicTableSource(config, context.getCatalogTable().getSchema());
}
// 创建 Sink 实例(核心:关联带 +1 逻辑的 Sink)
@Override
public DynamicTableSink createDynamicTableSink(Context context) {
FactoryUtil.TableFactoryHelper helper = FactoryUtil.createTableFactoryHelper(this, context);
ReadableConfig config = helper.getOptions();
helper.validate();
// 解析配置并创建带 +1 逻辑的 Sink
return new HologresDynamicTableSink(config, context.getCatalogTable().getSchema());
}
}
步骤 4:实现 Sink 核心逻辑(数字 +1 )
4.1 HologresRowDataStatementBuilder(参数绑定 +1 逻辑)
package org.apache.flink.connector.hologres.sink;
import org.apache.flink.connector.jdbc.internal.AbstractJdbcRowDataStatementBuilder;
import org.apache.flink.table.data.RowData;
import org.apache.flink.table.types.logical.DecimalType;
import org.apache.flink.table.types.logical.LogicalType;
import org.apache.flink.table.types.logical.LogicalTypeRoot;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* Hologres 参数绑定器:数字字段自动 +1
*/
public class HologresRowDataStatementBuilder extends AbstractJdbcRowDataStatementBuilder {
public HologresRowDataStatementBuilder(LogicalType[] fieldTypes) {
super(fieldTypes);
}
@Override
protected void setParameter(PreparedStatement stmt, int index, RowData row, int pos, LogicalType type) throws SQLException {
if (row.isNullAt(pos)) {
stmt.setNull(index, getJdbcType(type));
return;
}
LogicalTypeRoot typeRoot = type.getTypeRoot();
// 核心:数字类型 +1,非数字类型保持原样
switch (typeRoot) {
case TINYINT:
stmt.setByte(index, (byte) (row.getByte(pos) + 1));
break;
case SMALLINT:
stmt.setShort(index, (short) (row.getShort(pos) + 1));
break;
case INTEGER:
stmt.setInt(index, row.getInt(pos) + 1);
break;
case BIGINT:
stmt.setLong(index, row.getLong(pos) + 1L);
break;
case FLOAT:
stmt.setFloat(index, row.getFloat(pos) + 1.0f);
break;
case DOUBLE:
stmt.setDouble(index, row.getDouble(pos) + 1.0d);
break;
case DECIMAL:
DecimalType decimalType = (DecimalType) type;
BigDecimal value = row.getDecimal(pos, decimalType.getPrecision(), decimalType.getScale()).toBigDecimal();
stmt.setBigDecimal(index, value.add(BigDecimal.ONE));
break;
// 非数字类型:直接复用父类逻辑
default:
super.setParameter(stmt, index, row, pos, type);
break;
}
}
// 适配 Hologres 数据类型映射
private int getJdbcType(LogicalType type) {
// 简化实现:可根据 Hologres 类型扩展
return org.apache.flink.connector.jdbc.typeutils.JdbcTypeUtils.toJdbcType(type);
}
}
4.2 HologresDynamicTableSink(创建带 +1 逻辑的 Sink)
package org.apache.flink.connector.hologres.sink;
import org.apache.flink.configuration.ReadableConfig;
import org.apache.flink.connector.hologres.config.HologresConnectorOptions;
import org.apache.flink.connector.jdbc.JdbcSink;
import org.apache.flink.connector.jdbc.JdbcStatementBuilder;
import org.apache.flink.connector.jdbc.config.JdbcExecutionOptions;
import org.apache.flink.connector.jdbc.config.JdbcLookupOptions;
import org.apache.flink.table.connector.ChangelogMode;
import org.apache.flink.table.connector.sink.DynamicTableSink;
import org.apache.flink.table.connector.sink.SinkFunctionProvider;
import org.apache.flink.table.data.RowData;
import org.apache.flink.table.types.logical.LogicalType;
import org.apache.flink.table.types.logical.RowType;
import java.util.Map;
public class HologresDynamicTableSink implements DynamicTableSink {
private final ReadableConfig config;
private final RowType rowType;
public HologresDynamicTableSink(ReadableConfig config, RowType rowType) {
this.config = config;
this.rowType = rowType;
}
@Override
public ChangelogMode getChangelogMode(ChangelogMode requestedMode) {
// 仅支持追加模式(生产可扩展 Upsert)
return ChangelogMode.insertOnly();
}
@Override
public SinkRuntimeProvider getSinkRuntimeProvider(Context context) {
// 1. 解析配置
String endpoint = config.get(HologresConnectorOptions.ENDPOINT);
String dbname = config.get(HologresConnectorOptions.DBNAME);
String username = config.get(HologresConnectorOptions.USERNAME);
String password = config.get(HologresConnectorOptions.PASSWORD);
String tableName = config.get(FactoryUtil.TABLE_NAME);
// 2. 构建 JDBC URL
String jdbcUrl = HologresConnectorOptions.getJdbcUrl(endpoint, dbname);
// 3. 构建插入 SQL
String insertSql = buildInsertSql(tableName, rowType.getFieldCount());
// 4. 创建带 +1 逻辑的 StatementBuilder
LogicalType[] fieldTypes = rowType.getFields().stream()
.map(f -> f.getType())
.toArray(LogicalType[]::new);
JdbcStatementBuilder<RowData> statementBuilder = new HologresRowDataStatementBuilder(fieldTypes);
// 5. 构建 JdbcSink(复用 Flink JDBC Sink 核心逻辑)
JdbcExecutionOptions executionOptions = JdbcExecutionOptions.builder()
.withBatchSize(config.get(JdbcConnectorOptions.SINK_BUFFER_FLUSH_MAX_ROWS))
.withBatchIntervalMs(config.get(JdbcConnectorOptions.SINK_BUFFER_FLUSH_INTERVAL))
.build();
return SinkFunctionProvider.of(
JdbcSink.sink(
insertSql,
statementBuilder,
executionOptions,
new JdbcConnectionOptions.JdbcConnectionOptionsBuilder()
.withUrl(jdbcUrl)
.withUsername(username)
.withPassword(password)
.build()
)
);
}
// 构建插入 SQL:INSERT INTO table (col1, col2) VALUES (?, ?)
private String buildInsertSql(String tableName, int fieldCount) {
StringBuilder sql = new StringBuilder();
sql.append("INSERT INTO ").append(tableName).append(" VALUES (");
for (int i = 0; i < fieldCount; i++) {
if (i > 0) {
sql.append(", ");
}
sql.append("?");
}
sql.append(")");
return sql.toString();
}
@Override
public DynamicTableSink copy() {
return new HologresDynamicTableSink(config, rowType);
}
@Override
public String asSummaryString() {
return "Hologres Dynamic Table Sink (with numeric +1)";
}
}
步骤 4:Source 实现(简化版,生产需完善)
package org.apache.flink.connector.hologres.source;
import org.apache.flink.configuration.ReadableConfig;
import org.apache.flink.connector.hologres.config.HologresConnectorOptions;
import org.apache.flink.connector.jdbc.JdbcInputFormat;
import org.apache.flink.connector.jdbc.JdbcInputFormatBuilder;
import org.apache.flink.table.connector.ChangelogMode;
import org.apache.flink.table.connector.source.DynamicTableSource;
import org.apache.flink.table.connector.source.InputFormatProvider;
import org.apache.flink.table.types.logical.RowType;
public class HologresDynamicTableSource implements DynamicTableSource {
private final ReadableConfig config;
private final RowType rowType;
public HologresDynamicTableSource(ReadableConfig config, RowType rowType) {
this.config = config;
this.rowType = rowType;
}
@Override
public ChangelogMode getChangelogMode() {
return ChangelogMode.insertOnly();
}
@Override
public ScanRuntimeProvider getScanRuntimeProvider(ScanContext context) {
// 解析配置
String endpoint = config.get(HologresConnectorOptions.ENDPOINT);
String dbname = config.get(HologresConnectorOptions.DBNAME);
String username = config.get(HologresConnectorOptions.USERNAME);
String password = config.get(HologresConnectorOptions.PASSWORD);
String tableName = config.get(FactoryUtil.TABLE_NAME);
// 构建 JDBC InputFormat(复用 Flink JDBC Source 逻辑)
JdbcInputFormatBuilder builder = JdbcInputFormat.buildJdbcInputFormat()
.setDrivername("org.postgresql.Driver")
.setDBUrl(HologresConnectorOptions.getJdbcUrl(endpoint, dbname))
.setUsername(username)
.setPassword(password)
.setQuery("SELECT * FROM " + tableName);
return InputFormatProvider.of(builder.finish());
}
@Override
public DynamicTableSource copy() {
return new HologresDynamicTableSource(config, rowType);
}
@Override
public String asSummaryString() {
return "Hologres Dynamic Table Source";
}
}
步骤 5:SPI 注册工厂类
在 src/main/resources/META-INF/services/org.apache.flink.table.factories.Factory 文件中添加:
plaintext
org.apache.flink.connector.hologres.factory.HologresDynamicTableFactory
作用:Flink 启动时会扫描该文件,自动注册 Hologres 连接器工厂,SQL 中可通过 connector = ‘hologres’ 识别。
三、打包与生产集成
步骤 1:打包连接器
bash
运行
进入 Flink 源码根目录
cd flink
仅打包 hologres 连接器(跳过测试,加速)
mvn clean package -pl flink-connectors/flink-connector-hologres -am -DskipTests -Pshade
打包产物:flink-connectors/flink-connector-hologres/target/flink-connector-hologres-1.19.0-shaded.jar(带依赖,无冲突)。

1570

被折叠的 条评论
为什么被折叠?



