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(带依赖,无冲突)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Direction_Wind

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值