Flink自定义实现ElasticSearch Table Source

Flink版本:1.12.1

ES Maven版本:elasticsearch-rest-client:6.3.1

FLINK TableSource官方文档:https://ci.apache.org/projects/flink/flink-docs-release-1.12/zh/dev/table/sourceSinks.html

Flink自定义Table Source需要实现3个类,DynamicTableSourceFactory -> DynamicTableSource -> SourceFunction。

在工厂类中解析建表时的选项字段,并将其作为参数。最后在SourceFunction中实现数据来源,并写入上下文。且在Table Source中需要将数据序列化为RowData,而普通DataStream自定义Source则不需要序列化。

一、ESReader

在这个类中实现了ES服务器的连接和查询方法,查询的方法可以在SourceFunction中直接调用获取数据。这里ES的连接可以根据不同的版本和需求自己写。最后的query方法是按照索引和时间范围查找。

@Slf4j
public class ESReader {

    // 测试使用
    public static void main(String[] args) throws Exception {
        ParameterTool params = getExecuteEnvParams(args);
        String esServerAddress = ;
        String username = ;
        String password = ;
        //...
        RestHighLevelClient client = getClient(esServerAddress, username, password, caPath);
        List<JSONObject> result = queryLog("", 1625537750000L, 1630894572761L, "", client);
        System.out.println(result);
        client.close();
    }

    /**
     * @return RestHighLevelClient 获取操作es索引的对象
     */
    public static RestHighLevelClient getClient() {
        //省略...
        return restClient;
    }


    public static  RestClientBuilder getRestClientBuilder(){
        //省略...
        return restClientBuilder;
    }

    public static List<JSONObject> queryLog() {
        //省略...
        return eventInfo;
    }
}

二、ESSqlFactory

工厂类中主要对建表数据源字段解析,并设置解码器。这里因为从ES中取出的数据为json,不重新新建formatFactory而直接使用Kafka的decoding。

public class ESSqlFactory implements DynamicTableSourceFactory {

    public static final ConfigOption<String> HOSTNAME = ConfigOptions.key("hostname").stringType().noDefaultValue();
    public static final ConfigOption<String> USERNAME = ConfigOptions.key("username").stringType().noDefaultValue();
    public static final ConfigOption<String> PASSWORD = ConfigOptions.key("password").stringType().noDefaultValue();
    //...


    /**
     *  用于 'connector' = '...'
     * @return elasticsearch
     */
    @Override
    public String factoryIdentifier() {
        return "elasticsearch";
    }

    /**
     * 必选字段
     */
    @Override
    public Set<ConfigOption<?>> requiredOptions() {
        final Set<ConfigOption<?>> options = new HashSet<>();
        options.add(HOSTNAME);
        options.add(USERNAME);
        options.add(PASSWORD);
        //...

        options.add(FactoryUtil.FORMAT);    // use pre-defined option for format
        return options;
    }

    /**
     * 可选字段
     */
    @Override
    public Set<ConfigOption<?>> optionalOptions() {
        final Set<ConfigOption<?>> options = new HashSet<>();
        return options;
    }

    public DynamicTableSource createDynamicTableSource(Context context) {
        final FactoryUtil.TableFactoryHelper helper = FactoryUtil.createTableFactoryHelper(this, context);

        // 获取解码器
        final DecodingFormat<DeserializationSchema<RowData>> valueFormat =
                (DecodingFormat)helper.discoverOptionalDecodingFormat(
                        DeserializationFormatFactory.class, FactoryUtil.FORMAT).orElseGet(() -> {
            return helper.discoverDecodingFormat(DeserializationFormatFactory.class, KafkaOptions.VALUE_FORMAT);
        });

        final DecodingFormat<DeserializationSchema<RowData>> decodingFormat = helper.discoverDecodingFormat(
                DeserializationFormatFactory.class, FactoryUtil.FORMAT);

        helper.validate();

        final ReadableConfig options = helper.getOptions();
        final String hostname = options.get(HOSTNAME);
        final String username = options.get(USERNAME);
        final String password = options.get(PASSWORD);
        //...

        final DataType producedDataType = context.getCatalogTable().getSchema().toPersistedRowDataType();

        return new ESDynamicTableSource(hostname, username, password,..., valueFormat, producedDataType);
    }
}

三、ESDynamicTableSource

从工厂类中调用动态表源类,该类实现了ScanTableSource,做全部查询,其中的核心方法为getScanRuntimeProvider

public class ESDynamicTableSource implements ScanTableSource {

    private final String hostname;
    private final String username;
    private final String password;
    //...
    private final DecodingFormat<DeserializationSchema<RowData>> decodingFormat;
    private final DataType producedDataType;

    public ESDynamicTableSource(String hostname,
                                String username,
                                String password,
                                //...
                                DecodingFormat<DeserializationSchema<RowData>> decodingFormat,
                                DataType producedDataType) {
        this.hostname = hostname;
        this.username = username;
        this.password = password;
        //...
        this.decodingFormat = decodingFormat;
        this.producedDataType = producedDataType;
    }

    @Override
    public ChangelogMode getChangelogMode() {
        return decodingFormat.getChangelogMode();
    }

    @Override
    public ScanRuntimeProvider getScanRuntimeProvider(ScanContext runtimeProviderContext) {
        final DeserializationSchema<RowData> deserializer = decodingFormat.createRuntimeDecoder(
                runtimeProviderContext,
                producedDataType);

        final SourceFunction<RowData> sourceFunction = new ESSourceFunction(
                hostname, username, password,
                ...,deserializer);

        return SourceFunctionProvider.of(sourceFunction, false);
    }

    @Override
    public DynamicTableSource copy() {
        return new ESDynamicTableSource(hostname, username, password,
                ..., decodingFormat, producedDataType);
    }

    @Override
    public String asSummaryString() {
        return "elastic Table Source";
    }
}

四、ESSourceFunction

该类中实现了对读取ES数据的读取,通过在run()方法中调用ES读取方法。读取后需要使用传进来的deserializer转换为RowData

public class ESSourceFunction extends RichSourceFunction<RowData> implements ResultTypeQueryable<RowData> {

    private final String hostname;
    private final String username;
    private final String password;
    //...
    private final DeserializationSchema<RowData> deserializer;

    private volatile boolean isRunning = true;
    RestHighLevelClient client;


    public ESSourceFunction(String hostname, String username, String password, ..., DeserializationSchema<RowData> deserializer) {
        this.hostname = hostname;
        this.username = username;
        this.password = password;
        //...
        this.deserializer = deserializer;
    }

    @Override
    public TypeInformation<RowData> getProducedType() {
        return deserializer.getProducedType();
    }

    @Override
    public void run(SourceContext<RowData> ctx) throws Exception {
        // 数据源获取
        // 省略...
        JSONObject result = queryLog(...);
 		ctx.collect(deserializer.deserialize(result.toJSONString().getBytes()));
        cancel();
    }

    @Override
    public void cancel() {
        isRunning = false;
        try {
            client.close();
        } catch (Throwable t) {
            // ignore
        }
    }
}

五、使用

在flink Table建表语句中调用即可:

CREATE TABLE ...
WITH (
'connector' = 'elasticsearch',
'hostname' = '',
'password' = ''... ,
'format' = 'json',
'json.fail-on-missing-field' = 'false',
'json.ignore-parse-errors' = 'true')
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: Flink自定义Sink和Source是指用户可以根据自己的需求,编写自己的数据源和数据输出方式。Flink提供了一些内置的Sink和Source,但是有时候用户需要根据自己的业务需求,自定义数据源和数据输出方式。 自定义Sink和Source需要实现Flink提供的接口,例如实现SinkFunction接口来自定义数据输出方式,实现SourceFunction接口来自定义数据源。用户可以根据自己的需求,实现这些接口,然后将自定义的Sink和Source应用到Flink程序中。 自定义Sink和Source可以帮助用户更好地满足自己的业务需求,提高数据处理的效率和准确性。 ### 回答2: Flink自定义Sink和Source方便开发人员根据特定业务需求定制化的数据输入和输出。这也是Flink作为DataStream处理引擎的一个强大特性。 自定义Sink的主要作用是将Flink处理的数据流输出到外部存储或处理系统中,如Kafka、Hadoop、Elasticsearch、MySQL等。通过自定义Sink,我们可以满足不同业务场景下,数据输出的不同需求。 自定义Sink的实现需要继承Flink提供的`RichSinkFunction`或者`SinkFunction`抽象类,并实现其抽象方法。`RichSinkFunction`中提供了一些状态管理的方法,如`open`、`close`等,我们可以在这些方法中添加额外的代码逻辑。自定义的SinkFunction可以重写invoke方法,将不需要状态管理的代码集中在此方法中。 自定义Source的主要作用是将外部数据源中的数据读取并发送给Flink的DataStream处理模块。自定义Source可以读取各种类型的数据源,如Kafka、文件、Socket等。 自定义Source实现需要继承Flink提供的`RichParallelSourceFunction`或者`SourceFunction`抽象类,并实现其抽象方法。`RichParallelSourceFunction`中支持在并行算子中运行,因此对于大规模数据的处理尤为适合。 在自定义Source中,需要实现一个`run`方法和一个`cancel`方法。`run`方法中是数据源处理逻辑的主要实现,`cancel`方法用于停止数据源的读取。我们还可以通过Flink提供的Checkpoint机制来管理数据源。 总之,自定义Sink和SourceFlink处理数据流的重要特性,使得开发人员可以根据业务需求灵活定制化的输入输出逻辑。 ### 回答3: Flink是一个开源流式处理框架,它提供了丰富的内置Sink和Source,同时也支持用户自定义的Sink和Source,以便满足不同的业务需求。 自定义Sink可以用于将流式数据写入外部系统中,比如数据库、消息队列和文件系统等。Flink提供了一个简单的接口SinkFunction,通过实现该接口可以快速开发自己的Sink。 SinkFunction接口定义了一个抽象方法invoke(),该方法是在每个输入元素处理完成时被调用。开发者需要编写自己的业务逻辑,在invoke()中实现将数据写入目标系统的逻辑。 自定义Source可以用于从外部系统读取数据,并将其逐个交付给Flink程序进行处理。同样地,Flink也提供了一个简单的接口SourceFunction,通过实现该接口可以快速开发自己的SourceSourceFunction接口定义了两个抽象方法:run()和cancel()。run()方法是在源自生命周期内调用的,它是源自执行主逻辑的地方。cancel()方法是用于清理资源的。开发者需要在run()方法中编写从外部系统读取数据的逻辑,并且能够异步地产生数据,最后将数据通过SourceContext将数据一条一条源源不断地输出。 自定义Sink和SourceFlink框架中非常常用的一个扩展方式,它可以满足用户自定义的需求,在具体的业务场景中,能够灵活的使用自定义Sink和Source对数据的处理进行个性化的定制化。同时,自定义Sink和Source的开发也相对简单,可以通过实现简单的接口,快速完成自定义Sink和Source的开发。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值