兼职网站制作,做网站怎么调用栏目,长沙建站公司做网站,域名注册商怎么查Flink 系列文章
1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接
13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…Flink 系列文章
1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接
13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的table api与sql之流式概念-详解的介绍了动态表、时间属性配置如何处理更新结果、时态表、流上的join、流上的确定性以及查询配置 16、Flink 的table api与sql之连接外部系统: 读写外部系统的连接器和格式以及FileSystem示例1 16、Flink 的table api与sql之连接外部系统: 读写外部系统的连接器和格式以及Elasticsearch示例2 16、Flink 的table api与sql之连接外部系统: 读写外部系统的连接器和格式以及Apache Kafka示例3 16、Flink 的table api与sql之连接外部系统: 读写外部系统的连接器和格式以及JDBC示例4
16、Flink 的table api与sql之连接外部系统: 读写外部系统的连接器和格式以及Apache Hive示例6
20、Flink SQL之SQL Client: 不用编写代码就可以尝试 Flink SQL可以直接提交 SQL 任务到集群上
22、Flink 的table api与sql之创建表的DDL 24、Flink 的table api与sql之Catalogs
26、Flink 的SQL之概览与入门示例 27、Flink 的SQL之SELECT (select、where、distinct、order by、limit、集合操作和去重)介绍及详细示例1 27、Flink 的SQL之SELECT (SQL Hints 和 Joins)介绍及详细示例2 27、Flink 的SQL之SELECT (窗口函数)介绍及详细示例3 27、Flink 的SQL之SELECT (窗口聚合)介绍及详细示例4 27、Flink 的SQL之SELECT (Group Aggregation分组聚合、Over Aggregation Over聚合 和 Window Join 窗口关联)介绍及详细示例5 27、Flink 的SQL之SELECT (Top-N、Window Top-N 窗口 Top-N 和 Window Deduplication 窗口去重)介绍及详细示例6 27、Flink 的SQL之SELECT (Pattern Recognition 模式检测)介绍及详细示例7
29、Flink SQL之DESCRIBE、EXPLAIN、USE、SHOW、LOAD、UNLOAD、SET、RESET、JAR、JOB Statements、UPDATE、DELETE1 29、Flink SQL之DESCRIBE、EXPLAIN、USE、SHOW、LOAD、UNLOAD、SET、RESET、JAR、JOB Statements、UPDATE、DELETE2 30、Flink SQL之SQL 客户端通过kafka和filesystem的例子介绍了配置文件使用-表、视图等 32、Flink table api和SQL 之用户自定义 Sources Sinks实现及详细示例 41、Flink之Hive 方言介绍及详细示例 42、Flink 的table api与sql之Hive Catalog 43、Flink之Hive 读写及详细验证示例 44、Flink之module模块介绍及使用示例和Flink SQL使用hive内置函数及自定义函数详细示例–网上有些说法好像是错误的 文章目录 Flink 系列文章一、用户自定义 Sources Sinks 介绍1、Metadata元数据2、Planning解析器3、Runtime运行时的实现4、maven依赖5、需要实现的点1、动态表的工厂类DynamicTableFactory2、动态表的 source 端1、Scan Table Source2、Lookup Table Source3、source 端的功能接口 3、动态表的 sink 端1、sink 端的功能接口 4、编码与解码 二、用户自定义source 示例1、maven依赖2、工厂实现1、动态工厂实现-SocketDynamicTableFactory2、解码器工厂实现-ChangelogCsvFormatFactory 3、source 端与解码实现1、source端实现-SocketDynamicTableSource2、数据解码-ChangelogCsvFormat 4、运行时1、接收数据源数据-SocketSourceFunction2、接收的数据解析-ChangelogCsvDeserializer 5、工厂类配置6、验证1、确保nc已经完成并可用2、mysql创建表UserScoresSink3、创建java验证类4、验证插入INSERT数据5、验证删除DELETE数据6、验证更新UPDATE数据7、验证输入非string和int数据类型 本文简单介绍了Flink table api SQL用户自定义实现source和sink的步骤并以实际示例介绍了实现source端和验证步骤。 本文依赖flink和mysql集群能正常使用。 本文分为2个部分即自定义实现source和sink的需要做的工作以及自定义实现source端的具体示例、验证步骤。 本文示例均是在Flink 1.17版本的环境中运行的。
一、用户自定义 Sources Sinks 介绍
动态表是 Flink Table SQL API的核心概念用于统一有界和无界数据的处理。
动态表只是一个逻辑概念因此 Flink 并不拥有数据。相应的动态表的内容存储在外部系统 如数据库、键值存储、消息队列 或文件中。
动态 sources 和动态 sinks 可用于从外部系统读取数据和向外部系统写入数据。
Flink 为 Kafka、Hive 和不同的文件系统提供了预定义的连接器。有关内置 table sources 和 sinks 的更多信息参考4、介绍Flink的流批一体、transformations的18种算子详细介绍、Flink与Kafka的source、sink介绍
从 Flink v1.16 开始, TableEnvironment 引入了一个用户类加载器以在 table 程序、SQL Client、SQL Gateway 中保持一致的类加载行为。该类加载器会统一管理所有的用户 jar 包包括通过 ADD JAR 或 CREATE FUNCTION … USING JAR … 添加的 jar 资源。 在用户自定义连接器中应该将 Thread.currentThread().getContextClassLoader() 替换成该用户类加载器去加载类。否则可能会发生 ClassNotFoundException 的异常。该用户类加载器可以通过 DynamicTableFactory.Context 获得。
在许多情况下开发人员不需要从头开始创建新的连接器而是希望稍微修改现有的连接器或 hook 到现有的 stack。在其他情况下开发人员希望创建专门的连接器。
本节对这两种用例都有帮助。它解释了表连接器的一般体系结构从 API 中的纯粹声明到在集群上执行的运行时代码
实心箭头展示了在转换过程中对象如何从一个阶段到下一个阶段转换为其他对象。 1、Metadata元数据
Table API 和 SQL 都是声明式 API。这包括表的声明。因此执行 CREATE TABLE 语句会导致目标 catalog 中的元数据更新。
对于大多数 catalog 实现外部系统中的物理数据不会针对此类操作进行修改。特定于连接器的依赖项不必存在于类路径中。在 WITH 子句中声明的选项既不被验证也不被解释。
动态表的元数据 通过 DDL 创建或由 catalog 提供 表示为 CatalogTable 的实例。必要时表名将在内部解析为 CatalogTable。
2、Planning解析器
在解析和优化以 table 编写的程序时需要将 CatalogTable 解析为 DynamicTableSource 用于在 SELECT 查询中读取 和 DynamicTableSink 用于在 INSERT INTO 语句中写入 。
DynamicTableSourceFactory 和 DynamicTableSinkFactory 提供连接器特定的逻辑用于将 CatalogTable 的元数据转换为 DynamicTableSource 和 DynamicTableSink 的实例。在大多数情况下以工厂模式设计的目的是验证选项例如示例中的 ‘port’ ‘5022’ 配置编码解码格式 如果需要 并创建表连接器的参数化实例。
默认情况下DynamicTableSourceFactory 和 DynamicTableSinkFactory 的实例是使用 Java的 [Service Provider Interfaces (SPI)] (https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html) 发现的。 connector 选项例如示例中的 ‘connector’ ‘custom’必须对应于有效的工厂标识符。
尽管在类命名中可能不明显但 DynamicTableSource 和 DynamicTableSink 也可以被视为有状态的工厂它们最终会产生具体的运行时实现来读写实际数据。
规划器使用 source 和 sink 实例来执行连接器特定的双向通信直到找到最佳逻辑规划。取决于声明可选的接口 例如 SupportsProjectionPushDown 或 SupportsOverwrite规划器可能会将更改应用于实例并且改变产生的运行时实现。
3、Runtime运行时的实现
一旦逻辑规划完成规划器将从表连接器获取 runtime implementation。运行时逻辑在 Flink 的核心连接器接口中实现例如 InputFormat 或 SourceFunction。
这些接口按另一个抽象级别被分组为 ScanRuntimeProvider、LookupRuntimeProvider 和 SinkRuntimeProvider 的子类。
例如OutputFormatProvider 提供 org.apache.flink.api.common.io.OutputFormat 和 SinkFunctionProvider 提供org.apache.flink.streaming.api.functions.sink.SinkFunction都是规划器可以处理的 SinkRuntimeProvider 具体实例。
4、maven依赖
如果要实现自定义连接器或自定义格式通常以下依赖项就足够了 dependencygroupIdorg.apache.flink/groupIdartifactIdflink-table-common/artifactIdversion1.17.1/versionscopeprovided/scope
/dependency
如果开发一个需要与 DataStream API 桥接的连接器 即如果你想将 DataStream 连接器适配到 Table API你需要添加此依赖项 dependencygroupIdorg.apache.flink/groupIdartifactIdflink-table-api-java-bridge/artifactIdversion1.17.1/versionscopeprovided/scope
/dependency
在开发 connector/format 时我们建议同时提供 Thin JAR 和 uber JAR以便用户可以轻松地在 SQL 客户端或 Flink 发行版中加载 uber JAR 并开始使用它。 uber JAR 应该包含连接器的所有第三方依赖不包括上面列出的表依赖。
你不应该在生产代码中依赖 flink-table-planner_2.12。 使用 Flink 1.15 中引入的新模块 flink-table-planner-loader应用程序的类路径将不再直接访问 org.apache.flink.table.planner 类。 如果你需要 org.apache.flink.table.planner 的包和子包内部可用的功能请开启一个 issue。
5、需要实现的点
这一部分主要介绍扩展 Flink table connector 时可能用到的接口。
1、动态表的工厂类DynamicTableFactory
在根据 catalog 与 Flink 运行时上下文信息为某个外部存储系统配置动态表连接器时需要用到动态表的工厂类。 通过实现 org.apache.flink.table.factories.DynamicTableSourceFactory 接口完成一个工厂类来生产 DynamicTableSource 类。 通过实现 org.apache.flink.table.factories.DynamicTableSinkFactory 接口完成一个工厂类来生产 DynamicTableSink 类。
默认情况下Java 的 SPI 机制会自动识别这些工厂类同时将 connector 配置项作为工厂类的”IDENTIFIER标识符“。
在 JAR 文件中需要将实现的工厂类路径放入到下面这个配置文件
META-INF/services/org.apache.flink.table.factories.Factory
Flink 会对工厂类逐个进行检查确保其“标识符”是全局唯一的并且按照要求实现了上面提到的接口 (比如 DynamicTableSourceFactory)。
如果必要的话也可以在实现 catalog 时绕过上述 SPI 机制识别工厂类的过程。即在实现 catalog 接口时在org.apache.flink.table.catalog.Catalog#getFactory 方法中直接返回工厂类的实例。
2、动态表的 source 端
按照定义动态表是随时间变化的。
在读取动态表时表中数据可以是以下情况之一
changelog 流支持有界或无界在 changelog 流结束前所有的改变都会被源源不断地消费由 ScanTableSource 接口表示。处于一直变换或数据量很大的外部表其中的数据一般不会被全量读取除非是在查询某个值时由 LookupTableSource 接口表示。
一个类可以同时实现这两个接口Planner 会根据查询的 Query 选择相应接口中的方法。
1、Scan Table Source
在运行期间ScanTableSource 接口会按行扫描外部存储系统中所有数据。
被扫描的数据可以是 insert、update、delete 三种操作类型因此数据源可以用作读取 changelog 支持有界或无界。在运行时返回的 changelog mode 表示 Planner 要处理的操作类型。
在常规批处理的场景下数据源可以处理 insert-only 操作类型的有界/无界数据流。
在变更日志数据捕获即 CDC场景下数据源可以处理 insert、update、delete 操作类型的有界或无界数据流。
可以实现更多的功能接口来优化数据源比如实现 SupportsProjectionPushDown 接口这样在运行时在 source 端就处理数据。在 org.apache.flink.table.connector.source.abilities 包下可以找到各种功能接口下文中有列出。
实现 ScanTableSource 接口的类必须能够生产 Flink 内部数据结构因此每条记录都会按照org.apache.flink.table.data.RowData 的方式进行处理。Flink 运行时提供了转换机制保证 source 端可以处理常见的数据结构并且在最后进行转换。
2、Lookup Table Source
在运行期间LookupTableSource 接口会在外部存储系统中按照 key 进行查找。
相比于ScanTableSourceLookupTableSource 接口不会全量读取表中数据只会在需要时向外部存储其中的数据有可能会一直变化发起查询请求惰性地获取数据。
同时相较于ScanTableSourceLookupTableSource 接口目前只支持处理 insert-only 数据流。
暂时不支持扩展功能接口可查看 org.apache.flink.table.connector.source.LookupTableSource 中的文档了解更多。
LookupTableSource 的实现方法可以是 TableFunction 或者 AsyncTableFunctionFlink运行时会根据要查询的 key 值调用这个实现方法进行查询。
3、source 端的功能接口 接口名称接口描述SupportsFilterPushDown支持将过滤条件下推到 DynamicTableSource。为了更高效处理数据source 端会将过滤条件下推以便在数据产生时就处理。SupportsLimitPushDown支持将 limit期望生产的最大数据条数下推到 DynamicTableSource。SupportsPartitionPushDown支持将可用的分区信息提供给 planner 并且将分区信息下推到 DynamicTableSource。在运行时为了更高效处理数据source 端会只从提供的分区列表中读取数据。SupportsProjectionPushDown支持将查询列(可嵌套)下推到 DynamicTableSource。为了更高效处理数据source 端会将查询列下推以便在数据产生时就处理。如果 source 端同时实现了 SupportsReadingMetadata那么 source 端也会读取相对应列的元数据信息。SupportsReadingMetadata支持通过 DynamicTableSource 读取列的元数据信息。source 端会在生产数据行时在最后添加相应的元数据信息其中包括元数据的格式信息。SupportsWatermarkPushDown支持将水印策略下推到 DynamicTableSource。水印策略可以通过工厂模式或 Builder 模式来构建用于抽取时间戳以及水印的生成。在运行时source 端内部的水印生成器会为每个分区生产水印。SupportsSourceWatermark支持使用 ScanTableSource 中提供的水印策略。当使用 CREATE TABLE DDL 时可以使用 SOURCE_WATERMARK() 来告诉 planner 调用这个接口中的水印策略方法。SupportsRowLevelModificationScan支持将读数据的上下文 RowLevelModificationScanContext 从 ScanTableSource 传递给实现了 SupportsRowLevelDeleteSupportsRowLevelUpdate 的 sink 端。 上述接口当前只适用于 ScanTableSource不适用于LookupTableSource。 3、动态表的 sink 端
动态表是随时间变化的。
当写入一个动态表时数据流可以被看作是 changelog 有界或无界都可在 changelog 结束前所有的变更都会被持续写入。在运行时返回的 changelog mode 会显示 sink 端支持的数据操作类型。
在常规批处理的场景下sink 端可以持续接收 insert-only 操作类型的数据并写入到有界/无界数据流中。
在变更日志数据捕获即 CDC场景下sink 端可以将 insert、update、delete 操作类型的数据写入有界或无界数据流。
可以实现 SupportsOverwrite 等功能接口在 sink 端处理数据。可以在 org.apache.flink.table.connector.sink.abilities 包下找到各种功能接口更多内容可查看下文接口介绍。
实现 DynamicTableSink 接口的类必须能够处理 Flink 内部数据结构因此每条记录都会按照 org.apache.flink.table.data.RowData 的方式进行处理。Flink 运行时提供了转换机制来保证在最开始进行数据类型转换以便 sink 端可以处理常见的数据结构。
1、sink 端的功能接口 接口名称接口描述SupportsOverwrite支持 DynamicTableSink 覆盖写入已存在的数据。默认情况下如果不实现这个接口在使用 INSERT OVERWRITE SQL 语法时已存在的表或分区不会被覆盖写入SupportsPartitioning支持 DynamicTableSink 写入分区数据。SupportsWritingMetadata支持 DynamicTableSink 写入元数据列。Sink 端会在消费数据行时在最后接受相应的元数据信息并进行持久化其中包括元数据的格式信息。SupportsDeletePushDown支持将 DELETE 语句中的过滤条件下推到 DynamicTableSinksink 端可以直接根据过滤条件来删除数据。SupportsRowLevelDelete支持 DynamicTableSink 根据行级别的变更来删除已有的数据。该接口的实现者需要告诉 Planner 如何产生这些行变更并且需要消费这些行变更从而达到删除数据的目的。SupportsRowLevelUpdate支持 DynamicTableSink 根据行级别的变更来更新已有的数据。该接口的实现者需要告诉 Planner 如何产生这些行变更并且需要消费这些行变更从而达到更新数据的目的。 4、编码与解码
有的表连接器支持 K/V 型数据的各类编码与解码方式。
编码与解码格式器的工作原理类似于 DynamicTableSourceFactory - DynamicTableSource - ScanRuntimeProvider其中工厂类负责传参source 负责提供处理逻辑。
由于编码与解码格式器处于不同的代码模块类似于table factories它们都需要通过 Java 的 SPI 机制自动识别。为了找到格式器的工厂类动态表工厂类会根据该格式器工厂类的”标识符“来搜索并确认其实现了连接器相关的基类。
比如Kafka 的 source 端需要一个实现了 DeserializationSchema 接口的类用来为数据解码。那么 Kafka 的 source 端工厂类会使用配置项 value.format 的值来发现 DeserializationFormatFactory。
截至版本Flink 1.17支持使用如下格式器工厂类:
org.apache.flink.table.factories.DeserializationFormatFactory
org.apache.flink.table.factories.SerializationFormatFactory格式器工厂类再将配置传参给 EncodingFormat 或 DecodingFormat。这些接口是另外一种工厂类用于为所给的数据类型生成指定的格式器。
例如 Kafka 的 source 端工厂类 DeserializationFormatFactory 会为 Kafka 的 source 端返回 EncodingFormat
二、用户自定义source 示例
本示例介绍从一个自定义的数据源读取数据并存入mysql数据库中。实现的功能有自定义的解码器和scan table的source。数据源以socket为示例。 本示例是在IDE中运行的也可以放在Flink sql cli中运行前提是打包放在flink lib的目录此处不再赘述。 本部分涉及到配置factory所以示例代码将包名称带上了。
该示例实现的功能包含
创建工厂类实现配置项的解析与校验实现表连接器实现与发现自定义的编码/解码格式器其他工具类数据结构的转换器以及一个FactoryUtil类
source 端通过实现一个单线程的 SourceFunction 接口绑定一个 socket 端口来监听字节流字节流会被解码为一行一行的数据解码器是可插拔的。解码方式是将第一列数据作为这条数据的操作类型。
1、maven依赖
propertiesencodingUTF-8/encodingproject.build.sourceEncodingUTF-8/project.build.sourceEncodingmaven.compiler.source1.8/maven.compiler.sourcemaven.compiler.target1.8/maven.compiler.targetjava.version1.8/java.versionscala.version2.12/scala.versionflink.version1.17.0/flink.version/propertiesdependencies!-- https://mvnrepository.com/artifact/org.apache.flink/flink-clients --dependencygroupIdorg.apache.flink/groupIdartifactIdflink-clients/artifactIdversion${flink.version}/version/dependencydependencygroupIdorg.apache.flink/groupIdartifactIdflink-java/artifactIdversion${flink.version}/version/dependencydependencygroupIdorg.apache.flink/groupIdartifactIdflink-table-common/artifactIdversion${flink.version}/version/dependencydependencygroupIdorg.apache.flink/groupIdartifactIdflink-streaming-java/artifactIdversion${flink.version}/version/dependencydependencygroupIdorg.apache.flink/groupIdartifactIdflink-table-api-java-bridge/artifactIdversion${flink.version}/versionscopeprovided/scope/dependency !-- https://mvnrepository.com/artifact/org.apache.flink/flink-sql-gateway --dependencygroupIdorg.apache.flink/groupIdartifactIdflink-sql-gateway/artifactIdversion${flink.version}/version/dependency!-- https://mvnrepository.com/artifact/org.apache.flink/flink-table-planner --dependencygroupIdorg.apache.flink/groupIdartifactIdflink-table-planner_2.12/artifactIdversion${flink.version}/versionscopeprovided/scope/dependency !-- https://mvnrepository.com/artifact/org.apache.flink/flink-table-api-java-uber --dependencygroupIdorg.apache.flink/groupIdartifactIdflink-table-api-java-uber/artifactIdversion${flink.version}/version/dependency!-- https://mvnrepository.com/artifact/org.apache.flink/flink-table-runtime --dependencygroupIdorg.apache.flink/groupIdartifactIdflink-table-runtime/artifactIdversion${flink.version}/version/dependencydependencygroupIdorg.apache.flink/groupIdartifactIdflink-connector-jdbc/artifactIdversion3.1.0-1.17/versionscopeprovided/scope/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion5.1.38/version/dependency/dependencies2、工厂实现
介绍如何从 catalog 中解析元数据信息来构建表连接器的实例。
1、动态工厂实现-SocketDynamicTableFactory
SocketDynamicTableFactory 根据 catalog 表信息生成表的 source 端。由于 source 端需要进行对数据解码通过 FactoryUtil 类来找到解码器。
package org.tablesql.userdefine.factory;import java.util.HashSet;
import java.util.Set;import org.apache.flink.api.common.serialization.DeserializationSchema;
import org.apache.flink.configuration.ConfigOption;
import org.apache.flink.configuration.ConfigOptions;
import org.apache.flink.configuration.ReadableConfig;
import org.apache.flink.table.connector.format.DecodingFormat;
import org.apache.flink.table.connector.source.DynamicTableSource;
import org.apache.flink.table.data.RowData;
import org.apache.flink.table.factories.DeserializationFormatFactory;
import org.apache.flink.table.factories.DynamicTableSourceFactory;
import org.apache.flink.table.factories.FactoryUtil;
import org.apache.flink.table.types.DataType;
import org.tablesql.userdefine.source.SocketDynamicTableSource;/*** author alanchan**/
public class SocketDynamicTableFactory implements DynamicTableSourceFactory {// 定义ddl中的with子句内容public static final ConfigOptionString HOSTNAME ConfigOptions.key(hostname).stringType().noDefaultValue();public static final ConfigOptionInteger PORT ConfigOptions.key(port).intType().noDefaultValue();// 等同于 \npublic static final ConfigOptionInteger BYTE_DELIMITER ConfigOptions.key(byte-delimiter).intType().defaultValue(10);// 用于匹配 connector ...public static final String IDENTIFIER alan_socket;// 用于匹配 connector ...Overridepublic String factoryIdentifier() {return IDENTIFIER;}Overridepublic SetConfigOption? requiredOptions() {final SetConfigOption? options new HashSet();options.add(HOSTNAME);options.add(PORT);// 解码的格式器使用预先定义的配置项options.add(FactoryUtil.FORMAT);return options;}Overridepublic SetConfigOption? optionalOptions() {final SetConfigOption? options new HashSet();options.add(BYTE_DELIMITER);return options;}Overridepublic DynamicTableSource createDynamicTableSource(Context context) {// 使用提供的工具类或实现你自己的逻辑进行校验final FactoryUtil.TableFactoryHelper helper FactoryUtil.createTableFactoryHelper(this, context);// 找到合适的解码器final DecodingFormatDeserializationSchemaRowData decodingFormat helper.discoverDecodingFormat(DeserializationFormatFactory.class, FactoryUtil.FORMAT);// 校验所有的配置项helper.validate();// 获取校验完的配置项final ReadableConfig options helper.getOptions();final String hostname options.get(HOSTNAME);final int port options.get(PORT);final byte byteDelimiter (byte) (int) options.get(BYTE_DELIMITER);// 从 catalog 中抽取要生产的数据类型 (除了需要计算的列)final DataType producedDataType context.getCatalogTable().getResolvedSchema().toPhysicalRowDataType();// 创建并返回动态表 sourcereturn new SocketDynamicTableSource(hostname, port, byteDelimiter, decodingFormat, producedDataType);}}
2、解码器工厂实现-ChangelogCsvFormatFactory
ChangelogCsvFormatFactory 根据解码器相关的配置构建解码器。SocketDynamicTableFactory 中的 FactoryUtil 会适配好配置项中的键并处理 changelog-csv.column-delimiter 这样带有前缀的键。
由于这个工厂类实现了 DeserializationFormatFactory 接口它也可以为其他连接器比如 Kafka 连接器提供反序列化的解码支持。
package org.tablesql.userdefine.factory;import java.util.Collections;
import java.util.HashSet;
import java.util.Set;import org.apache.flink.api.common.serialization.DeserializationSchema;
import org.apache.flink.configuration.ConfigOption;
import org.apache.flink.configuration.ConfigOptions;
import org.apache.flink.configuration.ReadableConfig;
import org.apache.flink.table.connector.format.DecodingFormat;
import org.apache.flink.table.data.RowData;
import org.apache.flink.table.factories.DeserializationFormatFactory;
import org.apache.flink.table.factories.DynamicTableFactory.Context;
import org.apache.flink.table.factories.FactoryUtil;
import org.tablesql.userdefine.source.ChangelogCsvFormat;/*** author alanchan**/
public class ChangelogCsvFormatFactory implements DeserializationFormatFactory {// 定义所有配置项由于使用了新的changlog其他源端实现的csv编码格式的定义不再适用// 使用“,”作为数据的分隔符public static final ConfigOptionString COLUMN_DELIMITER ConfigOptions.key(column-delimiter).stringType().defaultValue(,);// with子句中的 format alan_changelog-csv 和 alan_changelog-csv.column-delimiter ,public static final String IDENTIFIER alan_changelog-csv;Overridepublic DecodingFormatDeserializationSchemaRowData createDecodingFormat(Context context, ReadableConfig formatOptions) {// 1、使用提供的工具类或实现你自己的逻辑进行校验FactoryUtil.validateFactoryOptions(this, formatOptions);// 2、获取校验完的配置项final String columnDelimiter formatOptions.get(COLUMN_DELIMITER);// 3、创建并返回解码器return new ChangelogCsvFormat(columnDelimiter);}Overridepublic String factoryIdentifier() {return IDENTIFIER;}Overridepublic SetConfigOption? requiredOptions() {return Collections.emptySet();}Overridepublic SetConfigOption? optionalOptions() {final SetConfigOption? options new HashSet();options.add(COLUMN_DELIMITER);return options;}}
3、source 端与解码实现
这部分介绍在计划阶段的 source 与 解码器实例是如何转化为运行时实例以便于提交给集群。
1、source端实现-SocketDynamicTableSource
SocketDynamicTableSource 在计划阶段中会被用到。本示例中我们不会实现任何功能接口因此getScanRuntimeProvider(…) 方法中就是主要逻辑对 SourceFunction 以及其用到的 DeserializationSchema 进行实例化作为运行时的实例。两个实例都被参数化来返回内部数据结构比如 RowData。
package org.tablesql.userdefine.source;import org.apache.flink.api.common.serialization.DeserializationSchema;
import org.apache.flink.streaming.api.functions.source.SourceFunction;
import org.apache.flink.table.connector.ChangelogMode;
import org.apache.flink.table.connector.format.DecodingFormat;
import org.apache.flink.table.connector.source.DynamicTableSource;
import org.apache.flink.table.connector.source.ScanTableSource;
import org.apache.flink.table.connector.source.SourceFunctionProvider;
import org.apache.flink.table.data.RowData;
import org.apache.flink.table.types.DataType;
import org.tablesql.userdefine.runtime.SocketSourceFunction;/*** author alanchan**/
public class SocketDynamicTableSource implements ScanTableSource {private final String hostname;private final int port;private final byte byteDelimiter;private final DecodingFormatDeserializationSchemaRowData decodingFormat;private final DataType producedDataType;public SocketDynamicTableSource(String hostname, int port, byte byteDelimiter, DecodingFormatDeserializationSchemaRowData decodingFormat, DataType producedDataType) {this.hostname hostname;this.port port;this.byteDelimiter byteDelimiter;this.decodingFormat decodingFormat;this.producedDataType producedDataType;}Overridepublic DynamicTableSource copy() {return new SocketDynamicTableSource(hostname, port, byteDelimiter, decodingFormat, producedDataType);}Overridepublic String asSummaryString() {return Socket Table Source;}Overridepublic ChangelogMode getChangelogMode() {// 在该示例中由解码器来决定 changelog 支持的模式 但是在 source 端指定也可以return decodingFormat.getChangelogMode();}/*** 对 SourceFunction 以及其用到的 DeserializationSchema 进行实例化作为运行时的实例。* 两个实例都被参数化来返回内部数据结构比如 RowData*/Overridepublic ScanRuntimeProvider getScanRuntimeProvider(ScanContext runtimeProviderContext) {// 创建运行时类用于提交给集群final DeserializationSchemaRowData deserializer decodingFormat.createRuntimeDecoder(runtimeProviderContext, producedDataType);final SourceFunctionRowData sourceFunction new SocketSourceFunction(hostname, port, byteDelimiter, deserializer);return SourceFunctionProvider.of(sourceFunction, false);}}
2、数据解码-ChangelogCsvFormat
ChangelogCsvFormat 在运行时使用 DeserializationSchema 为socket的输入数据进行解码这里支持处理 INSERT、DELETE 变更类型的数据如果输入数据类型为UPDATE_BEFORE或UPDATE_AFTER则忽略如果需要该种类型的数据则直接addContainedKind即可。 输入数据格式 // INSERT,alanchan,5 // DELETE,alan,10
package org.tablesql.userdefine.source;import java.util.List;import org.apache.flink.api.common.serialization.DeserializationSchema;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.table.connector.ChangelogMode;
import org.apache.flink.table.connector.format.DecodingFormat;
import org.apache.flink.table.connector.source.DynamicTableSource.Context;
import org.apache.flink.table.connector.source.DynamicTableSource.DataStructureConverter;
import org.apache.flink.table.data.RowData;
import org.apache.flink.table.types.DataType;
import org.apache.flink.table.types.logical.LogicalType;
import org.apache.flink.types.RowKind;
import org.tablesql.userdefine.runtime.ChangelogCsvDeserializer;/*** author alanchan**/
public class ChangelogCsvFormat implements DecodingFormatDeserializationSchemaRowData {private final String columnDelimiter;public ChangelogCsvFormat(String columnDelimiter) {this.columnDelimiter columnDelimiter;}Overridepublic ChangelogMode getChangelogMode() {// 支持处理 INSERT、DELETE 变更类型的数据如果输入数据类型为UPDATE_BEFORE或UPDATE_AFTER则忽略如果需要该种类型的数据则直接addContainedKind即可// 输入数据格式// INSERT,alanchan,5// DELETE,alan,10return ChangelogMode.newBuilder().addContainedKind(RowKind.INSERT).addContainedKind(RowKind.DELETE).build();}Overridepublic DeserializationSchemaRowData createRuntimeDecoder(Context context, DataType producedDataType) {// 为 DeserializationSchema 创建类型信息 (TypeInformationRowData)final TypeInformationRowData producedTypeInfo context.createTypeInformation(producedDataType);// DeserializationSchema 中的大多数代码无法处理内部数据结构 在最后为转换创建一个转换器final DataStructureConverter converter context.createDataStructureConverter(producedDataType);// 在运行时为解析过程提供逻辑类型final ListLogicalType parsingTypes producedDataType.getLogicalType().getChildren();// 创建运行时类return new ChangelogCsvDeserializer(parsingTypes, converter, producedTypeInfo, columnDelimiter);}}
4、运行时
该部分不是定义source端的必须部分仅是为了验证source端的自定义数据运行情况而实际的开发中该部分是必须的否则自定义的数据源不可能不被识别。 这部分介绍接收数据源数据( SourceFunction)和解析数据源数据(DeserializationSchema)。
1、接收数据源数据-SocketSourceFunction
SocketSourceFunction 会监听一个 socket 端口并持续消费字节流。它会按照给定的分隔符拆分每条记录并由 DeserializationSchema 进行解码。
package org.tablesql.userdefine.runtime;import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Socket;import org.apache.flink.api.common.serialization.DeserializationSchema;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.api.java.typeutils.ResultTypeQueryable;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.metrics.MetricGroup;
import org.apache.flink.streaming.api.functions.source.RichSourceFunction;
import org.apache.flink.table.data.RowData;
import org.apache.flink.util.UserCodeClassLoader;/*** author alanchan**/
public class SocketSourceFunction extends RichSourceFunctionRowData implements ResultTypeQueryableRowData {private final String hostname;private final int port;private final byte byteDelimiter;private final DeserializationSchemaRowData deserializer;private volatile boolean isRunning true;private Socket currentSocket;public SocketSourceFunction(String hostname, int port, byte byteDelimiter, DeserializationSchemaRowData deserializer) {this.hostname hostname;this.port port;this.byteDelimiter byteDelimiter;this.deserializer deserializer;}/*** 监听ddl定义的 socket 端口并持续消费字节流即从socket端持续读取数据并解析数据该示例的并行度为1*/Overridepublic void run(SourceContextRowData ctx) throws Exception {while (isRunning) {try (final Socket socket new Socket()) {currentSocket socket;socket.connect(new InetSocketAddress(hostname, port), 0);try (InputStream stream socket.getInputStream()) {ByteArrayOutputStream buffer new ByteArrayOutputStream();int b;while ((b stream.read()) 0) {// 持续写入 buffer 直到遇到分隔符if (b ! byteDelimiter) {buffer.write(b);}// 解码并处理记录else {ctx.collect(deserializer.deserialize(buffer.toByteArray()));buffer.reset();}}}} catch (Throwable t) {t.printStackTrace(); }Thread.sleep(1000);}}Overridepublic void cancel() {isRunning false;try {currentSocket.close();} catch (Throwable t) {// 忽略}}Overridepublic TypeInformationRowData getProducedType() {return deserializer.getProducedType();}Overridepublic void open(Configuration parameters) throws Exception {deserializer.open(new DeserializationSchema.InitializationContext() {Overridepublic UserCodeClassLoader getUserCodeClassLoader() {return (UserCodeClassLoader) getRuntimeContext().getUserCodeClassLoader();}Overridepublic MetricGroup getMetricGroup() {return getRuntimeContext().getMetricGroup();}});}}
2、接收的数据解析-ChangelogCsvDeserializer
ChangelogCsvDeserializer 的解析逻辑比较简单将字节流数据解析为由 Integer 和 String 组成的 Row 类型并附带这条数据的操作类型最后将其转换为内部数据结构。
package org.tablesql.userdefine.runtime;import java.io.IOException;
import java.util.List;import org.apache.flink.api.common.serialization.DeserializationSchema;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.table.connector.RuntimeConverter.Context;
import org.apache.flink.table.connector.source.DynamicTableSource.DataStructureConverter;
import org.apache.flink.table.data.RowData;
import org.apache.flink.table.types.logical.LogicalType;
import org.apache.flink.table.types.logical.LogicalTypeRoot;
import org.apache.flink.types.Row;
import org.apache.flink.types.RowKind;import com.google.re2j.Pattern;/*** author alanchan* 将字节流数据解析为由 Integer 和 String 组成的 Row 类型并附带这条数据的操作类型最后将其转换为内部数据结构*/
public class ChangelogCsvDeserializer implements DeserializationSchemaRowData {private final ListLogicalType parsingTypes;private final DataStructureConverter converter;private final TypeInformationRowData producedTypeInfo;private final String columnDelimiter;public ChangelogCsvDeserializer(ListLogicalType parsingTypes, DataStructureConverter converter, TypeInformationRowData producedTypeInfo, String columnDelimiter) {this.parsingTypes parsingTypes;this.converter converter;this.producedTypeInfo producedTypeInfo;this.columnDelimiter columnDelimiter;}Overridepublic TypeInformationRowData getProducedType() {// 为 Flink 的核心接口提供类型信息。return producedTypeInfo;}Overridepublic void open(InitializationContext context) {// 转化器必须要被开启。converter.open(Context.create(ChangelogCsvDeserializer.class.getClassLoader()));}Overridepublic RowData deserialize(byte[] message) throws IOException {// 按列解析数据其中一列是 changelog 标记。数据格式形如INSERT,alan,10final String[] columns new String(message).split(Pattern.quote(columnDelimiter));final RowKind kind RowKind.valueOf(columns[0]);final Row row new Row(kind, parsingTypes.size());for (int i 0; i parsingTypes.size(); i) {row.setField(i, parse(parsingTypes.get(i).getTypeRoot(), columns[i 1]));}// 转换为内部数据结构return (RowData) converter.toInternal(row);}//解析输入数据类型本文定义的数据有2种数据类型即string和int如果有更多的数据类型则在该处进行解析private static Object parse(LogicalTypeRoot root, String value) {switch (root) {case INTEGER:return Integer.parseInt(value);case VARCHAR:return value;default:throw new IllegalArgumentException();}}Overridepublic boolean isEndOfStream(RowData nextElement) {return false;}}
5、工厂类配置
如果确保工厂类配置能正常使用则需要进行配置。 本示例的目录结构如下 org.apache.flink.table.factories.Factory文件内容如下 即工厂实现部分定义的2个工厂类。
org.tablesql.userdefine.factory.SocketDynamicTableFactory
org.tablesql.userdefine.factory.ChangelogCsvFormatFactory6、验证
1、确保nc已经完成并可用
2、mysql创建表UserScoresSink
3、创建java验证类
该处功能有2个即创建自定义source端的表然后将源端数据解析并分组存储至mysql。 UserScores (name STRING, score INT)表只定义了String和int类型因为数据解析器只实现这两种实现方式如果有更多的实现方式则需要在解析器中实现。
package org.tablesql.userdefine;import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.Table;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
import org.apache.flink.types.Row;/*** author alanchan**/
public class App {public static String sql CREATE TABLE UserScores (name STRING, score INT)\r\n WITH (\r\n connector alan_socket,\r\n hostname 192.168.10.42,\r\n port 9999,\r\n byte-delimiter 10,\r\n format alan_changelog-csv,\r\n alan_changelog-csv.column-delimiter ,\r\n );;public static String sqlSink CREATE TABLE UserScoresSink (name STRING, scores BIGINT, PRIMARY KEY(name) NOT ENFORCED ) with ( connector jdbc,\r\n url jdbc:mysql://192.168.10.44:3306/test,\r\n table-name UserScoresSink );public static void main(String[] args) throws Exception {StreamExecutionEnvironment env StreamExecutionEnvironment.getExecutionEnvironment();StreamTableEnvironment tenv StreamTableEnvironment.create(env);tenv.executeSql(sql);tenv.executeSql(sqlSink);tenv.executeSql(INSERT INTO UserScoresSink SELECT name, sum(score) as sumScore FROM UserScores group by name);Table sink tenv.sqlQuery(select * from UserScoresSink);// interpret the insert-only Table as a DataStream againDataStreamRow resultStream tenv.toDataStream(sink);// add a printing sink and execute in DataStream APIresultStream.print();env.execute();}
}
4、验证插入INSERT数据
验证数据插入以及是否计算分组求和
nc输入数据
[alanchanserver2 bin]$ nc -lk 9999
INSERT,alan,10
INSERT,alanchan,5
INSERT,alan,30
INSERT,alanchan,15
INSERT,alanchan,20
mysql数据库存储数据
5、验证删除DELETE数据
验证输入delete数据是否进行了删除。为了保持数据的干净在验证的时候会删除上述示例中的数据。 验证该示例的时候注意逐条执行逐条观察mysql数据库的变化。
nc输入数据
[alanchanserver2 bin]$ nc -lk 9999
INSERT,alan,10
DELETE,alan,10
INSERT,alan,15
DELETE,alan,5
INSERT,alan,15
DELETE,alan,20
INSERT,alanchan,15
INSERT,alan,15
DELETE,alan,20
mysql数据库存储数据
6、验证更新UPDATE数据
验证输入delete数据是否进行了更新。该示例是在撒谎功能书验证删除的基础上做的。 验证该示例的时候注意逐条执行逐条观察mysql数据库的变化。
nc输入数据
[alanchanserver2 bin]$ nc -lk 9999
UPDATE_BEFORE,alanchan,15
UPDATE_AFTER,alanchan,10
UPDATE_AFTER,alan,20 mysql数据变化 应用程序控制台是否出现异常 应用程序控制台无异常
7、验证输入非string和int数据类型
该示例是在 验证update的数据示例基础上做的。
nc 输入数据
[alanchanserver2 bin]$ nc -lk 9999
INSERT,alan,10
INSERT,alan,10.5 mysql数据 应用程序控制台 以上简单介绍了用户自定义实现source和sink的步骤并以实际示例介绍了实现source端和验证步骤。