Flink TableAPI和SQL(二十三)连接到外部系统

文章目录

  • 控制台
  • Kafka (重点)
  • 文件系统
  • JDBC
  • Elasticsearch
  • HBase

控制台

CREATE TABLE ResultTable (
	user STRING,
	cnt BIGINT
WITH (
	'connector' = 'print'
);

Kafka (重点)

Kafka 的 SQL 连接器可以从 Kafka 的主题(topic)读取数据转换成表,也可以将表数据写入 Kafka 的主题。创建表的时候指定连接器为 Kafka,则这个表既可以作为输入表,也可以作为输出表。

想要在 Flink 程序中使用 Kafka 连接器,需要引入如下依赖:

<dependency>
  <groupId>org.apache.flinkgroupId>
  <artifactId>flink-connector-kafka_${scala.binary.version}artifactId>
  <version>${flink.version}version>
dependency>

这里我们引入的 Flink 和 Kafka 的连接器,与之前 DataStream API 中引入的连接器是一样的。如果想在 SQL 客户端里使用 Kafka 连接器,还需要下载对应的 jar 包放到 lib 目录下。另外,Flink 为各种连接器提供了一系列的“表格式”(table formats),比如 CSV、JSON、Avro、Parquet 等等。这些表格式定义了底层存储的二进制数据和表的列之间的转换方式,相当于表的序列化工具。对于 Kafka 而言,CSV、JSON、Avro 等主要格式都是支持的,根据 Kafka 连接器中配置的格式,我们可能需要引入对应的依赖支持。以 CSV 为例:

<dependency>
  <groupId>org.apache.flinkgroupId>
  <artifactId>flink-csvartifactId>
  <version>${flink.version}version>
dependency>

创建连接到 Kafka 的表
创建一个连接到 Kafka 表,需要在 CREATE TABLE 的 DDL 中在 WITH 子句里指定连接器为 Kafka,并定义必要的配置参数。
下面是一个具体示例:

CREATE TABLE KafkaTable (
  ` USER ` STRING,
  ` url ` STRING,
  ` ts ` TIMESTAMP(3) METADATA
  FROM
    'timestamp'
) WITH (
  'connector' = 'kafka',
  'topic' = 'events',
  'properties.bootstrap.servers' = 'localhost:9092',
  'properties.group.id' = 'testGroup',
  'scan.startup.mode' = 'earliest-offset',
  'format' = 'csv'
)

这里定义了 Kafka 连接器对应的主题(topic),Kafka 服务器,消费者组 ID,消费者起始模式以及表格式。需要特别说明的是,在 KafkaTable 的字段中有一个 ts,它的声明中用到了METADATA FROM,这是表示一个“元数据列”(metadata column),它是由 Kafka 连接器的元数据“timestamp”生成的。这里的 timestamp 其实就是 Kafka 中数据自带的时间戳,把它直接作为元数据提取出来,转换成一个新的字段 ts。

Upsert Kafka
正常情况下,Kafka 作为保持数据顺序的消息队列,读取和写入都应该是流式的数据,对应在表中就是仅追加(append-only)模式。如果我们想要将有更新操作(比如分组聚合)的结果表写入 Kafka,就会因为 Kafka 无法识别撤回(retract)或更新插入(upsert)消息而导致异常。

为了解决这个问题,Flink 专门增加了一个“更新插入 Kafka”(Upsert Kafka)连接器。这个连接器支持以更新插入(UPSERT)的方式向 Kafka 的 topic 中读写数据。

具体来说,Upsert Kafka 连接器处理的是更新日志(changlog)流。如果作为 TableSource,连接器会将读取到的 topic中的数据(key, value),解释为对当前 key 的数据值的更新(UPDATE),也就是查找动态表中 key 对应的一行数据,将 value 更新为最新的值;因为是 Upsert 操作,所以如果没有 key 对应的行,那么也会执行插入(INSERT)操作。另外,如果遇到 value 为空(null),连接器就把这条数据理解为对相应 key 那一行的删除(DELETE)操作。

如果作为 TableSink,Upsert Kafka 连接器会将有更新操作的结果表,转换成更新日志(changelog)流。如果遇到插入(INSERT)或者更新后(UPDATE_AFTER)的数据,对应的是一个添加(add)消息,那么就直接正常写入 Kafka 主题;如果是删除(DELETE)或者更新前的数据,对应是一个撤回(retract)消息,那么就把 value 为空(null)的数据写入 Kafka。

由于 Flink 是根据键(key)的值对数据进行分区的,这样就可以保证同一个 key 上的更新和删除消息都会落到同一个分区中。
下面是一个创建和使用 Upsert Kafka 表的例子:

CREATE TABLE pageviews_per_region (
  user_region STRING,
  pv BIGINT,
  uv BIGINT,
  PRIMARY KEY (user_region) NOT ENFORCED
) WITH (
  'connector' = 'upsert-kafka',
  'topic' = 'pageviews_per_region',
  'properties.bootstrap.servers' = '...',
  'key.format' = 'avro',
  'value.format' = 'avro'
);

CREATE TABLE pageviews (
  user_id BIGINT,
  page_id BIGINT,
  viewtime TIMESTAMP,
  user_region STRING,
  WATERMARK FOR viewtime AS viewtime - INTERVAL '2' SECOND
) WITH (
  'connector' = 'kafka',
  'topic' = 'pageviews',
  'properties.bootstrap.servers' = '...',
  'format' = 'json'
);

– 计算 pv、uv 并插入到 upsert-kafka 表中

INSERT INTO
  pageviews_per_region
SELECT
  user_region,
  COUNT(*),
  COUNT(DISTINCT user_id)
FROM
  pageviews
GROUP BY
  user_region;

这里我们从 Kafka 表 pageviews 中读取数据,统计每个区域的 PV(全部浏览量)和 UV(对用户去重),这是一个分组聚合的更新查询,得到的结果表会不停地更新数据。为了将结果表写入 Kafka 的 pageviews_per_region 主题,我们定义了一个 Upsert Kafka 表,它的字段中需要用PRIMARY KEY来指定主键,并且在WITH子句中分别指定key和value的序列化格式。

文件系统

Flink 提供了文件系统的连接器,支持从本地或者分布式的文件系统中读写数据。这个连接器是内置在 Flink 中的,所以使用它并不需要额外引入依赖。
下面是一个连接到文件系统的示例:

CREATE TABLE MyTable (
  column_name1 INT,
  column_name2 STRING,
  part_name1 INT,
  part_name2 STRING
) PARTITIONED BY (part_name1, part_name2) WITH (
  'connector' = 'filesystem', -- 连接器类型
  'path' = '...', -- 文件路径
  'format' = '...' -- 文件格式
);

这里在 WITH 前使用了 PARTITIONED BY 对数据进行了分区操作。文件系统连接器支持对分区文件的访问。

JDBC

Flink 提供的 JDBC 连接器可以通过 JDBC 驱动程序(driver)向任意的关系型数据库读写数据,比如 MySQL、PostgreSQL、Derby 等。

作为 TableSink 向数据库写入数据时,运行的模式取决于创建表的 DDL 是否定义了主键(primary key)。如果有主键,那么 JDBC 连接器就将以更新插入(Upsert)模式运行,可以向外部数据库发送按照指定键(key)的更新(UPDATE)和删除(DELETE)操作;如果没有定义主键,那么就将在追加(Append)模式下运行,不支持更新和删除操作。

引入依赖
想要在 Flink 程序中使用 JDBC 连接器,需要引入如下依赖:

<dependency>
  <groupId>org.apache.flinkgroupId>
  <artifactId>flink-connector-jdbc_${scala.binary.version}artifactId>
  <version>${flink.version}version>
dependency>

此外,为了连接到特定的数据库,我们还用引入相关的驱动器依赖,比如 MySQL:

<dependency>
  <groupId>mysqlgroupId>
  <artifactId>mysql-connector-javaartifactId>
  <version>5.1.38version>
dependency>

这里引入的驱动器版本是 5.1.38,读者可以依据自己的 MySQL 版本来进行选择。

创建 JDBC 表
创建 JDBC 表的方法与前面 Upsert Kafka 大同小异。下面是一个具体示例:
– 创建一张连接到 MySQL 的 表

CREATE TABLE MyTable (
  id BIGINT,
  name STRING,
  age INT,
  status BOOLEAN,
  PRIMARY KEY (id) NOT ENFORCED
) WITH (
  'connector' = 'jdbc',
  'url' = 'jdbc:mysql://localhost:3306/mydatabase',
  'table-name' = 'users'
);

– 将另一张表 T 的数据写入到 MyTable 表中

INSERT INTO MyTable
SELECT id, name, age, status FROM T;

创建表的 DDL 中定义了主键,所以数据会以 Upsert 模式写入到 MySQL 表中;而到MySQL 的连接,是通过 WITH 子句中的 url 定义的。要注意写入 MySQL 中真正的表名称是users,而 MyTable 是注册在 Flink 表环境中的表。

Elasticsearch

Elasticsearch 作为分布式搜索分析引擎,在大数据应用中有非常多的场景。Flink 提供的Elasticsearch的SQL连接器只能作为TableSink,可以将表数据写入Elasticsearch的索引(index)。Elasticsearch 连接器的使用与 JDBC 连接器非常相似,写入数据的模式同样是由创建表的 DDL中是否有主键定义决定的。

引入依赖
想要在 Flink 程序中使用 Elasticsearch 连接器,需要引入对应的依赖。具体的依赖与Elasticsearch 服务器的版本有关,对于 6.x 版本引入依赖如下:

<dependency>
  <groupId>org.apache.flinkgroupId>
  <artifactId>flink-connector-elasticsearch6_${scala.binary.version}artifactId>
  <version>${flink.version}version>
dependency>

对于 Elasticsearch 7 以上的版本,引入的依赖则是:

<dependency>
  <groupId>org.apache.flinkgroupId>
  <artifactId>flink-connector-elasticsearch7_${scala.binary.version}artifactId>
  <version>${flink.version}version>
dependency>

创建连接到 Elasticsearch 的表
创建 Elasticsearch 表的方法与 JDBC 表基本一致。下面是一个具体示例:
– 创建一张连接到 Elasticsearch 的 表

CREATE TABLE MyTable (
  user_id STRING,
  user_name STRING uv BIGINT,
  pv BIGINT,
  PRIMARY KEY (user_id) NOT ENFORCED
) WITH (
  'connector' = 'elasticsearch-7',
  'hosts' = 'http://localhost:9200',
  'index' = 'users'
);

这里定义了主键,所以会以更新插入(Upsert)模式向 Elasticsearch 写入数据。

HBase

作为高性能、可伸缩的分布式列存储数据库,HBase 在大数据分析中是一个非常重要的工具。Flink 提供的 HBase 连接器支持面向 HBase 集群的读写操作。

在流处理场景下,连接器作为 TableSink 向 HBase 写入数据时,采用的始终是更新插入(Upsert)模式。也就是说,HBase 要求连接器必须通过定义的主键(primary key)来发送更新日志(changelog)。所以在创建表的 DDL 中,我们必须要定义行键(rowkey)字段,并将它声明为主键;如果没有用 PRIMARY KEY 子句声明主键,连接器会默认把 rowkey 作为主键。

引入依赖
想要在 Flink 程序中使用 HBase 连接器,需要引入对应的依赖。目前 Flink 只对 HBase 的1.4.x 和 2.2.x 版本提供了连接器支持,而引入的依赖也应该与具体的 HBase 版本有关。对于1.4 版本引入依赖如下:

<dependency>
  <groupId>org.apache.flinkgroupId>
  <artifactId>flink-connector-hbase-1.4_${scala.binary.version}artifactId>
  <version>${flink.version}version>
dependency>

对于 HBase 2.2 版本,引入的依赖则是:

<dependency>
  <groupId>org.apache.flinkgroupId>
  <artifactId>flink-connector-hbase-2.2_${scala.binary.version}artifactId>
  <version>${flink.version}version>
dependency>

创建连接到 HBase 的表
由于 HBase 并不是关系型数据库,因此转换为 Flink SQL 中的表会稍有一些麻烦。在 DDL创建出的 HBase 表中,所有的列族(column family)都必须声明为 ROW 类型,在表中占据一个字段;而每个 family 中的列(column qualifier)则对应着 ROW 里的嵌套字段。我们不需要将 HBase 中所有的 family 和 qualifier 都在 Flink SQL 的表中声明出来,只要把那些在查询中用到的声明出来就可以了。

除了所有 ROW 类型的字段(对应着 HBase 中的 family),表中还应有一个原子类型的字段,它就会被识别为 HBase 的 rowkey。在表中这个字段可以任意取名,不一定非要叫 rowkey。下面是一个具体示例:
– 创建一张连接到 HBase 的 表

CREATE TABLE MyTable (
  rowkey INT,
  family1 ROW < q1 INT >,
  family2 ROW < q2 STRING,q3 BIGINT >,
  family3 ROW < q4 DOUBLE,q5 BOOLEAN,q6 STRING >,
  PRIMARY KEY (rowkey) NOT ENFORCED
) WITH (
  'connector' = 'hbase-1.4',
  'table-name' = 'mytable',
  'zookeeper.quorum' = 'hadoop102:2181,hadoop103:2181,hadoop104:2181'
);

– 假设表 T 的字段结构是 [rowkey, f1q1, f2q2, f2q3, f3q4, f3q5, f3q6]

INSERT INTO MyTable
SELECT rowkey, ROW(f1q1), ROW(f2q2, f2q3), ROW(f3q4, f3q5, f3q6) FROM T;

我们将另一张 T 中的数据提取出来,并用 ROW()函数来构造出对应的 column family,最终写入 HBase 中名为 mytable 的表。

你可能感兴趣的:(#,Flink,flink,sql,kafka)