FlinkSQL基本API
Table API 和 SQL 可以看作联结在一起的一套 API,这套 API 的核心概念就是"表"(Table)。在我们的程序中,输入数据可以定义成一张表;然后对这张表进行查询,就可以得到新的表,这相当于就是流数据的转换操作;最后还可以定义一张用于输出的表,负责将处理结果写入到外部系统。
整体处理流程可以分为读取数据源(Source)、转换(Transform)、输出数据(Sink)三部分,只需要将用于输入和输出的表定义出来,然后进行转换查询就可以。
// 创建表环境 TableEnvironment tableEnv = ...; // 创建输入表,连接外部系统读取数据 tableEnv.executeSql("CREATE TEMPORARY TABLE inputTable ... WITH ( 'connector' = ... )"); // 注册一个表,连接到外部系统,用于输出 tableEnv.executeSql("CREATE TEMPORARY TABLE outputTable ... WITH ( 'connector' = ... )"); // 执行 SQL 对表进行查询转换,得到一个新的表 Table table1 = tableEnv.sqlQuery("SELECT ... FROM inputTable... "); // 使用 Table API 对表进行查询转换,得到一个新的表 Table table2 = tableEnv.from("inputTable").select(...); // 将得到的结果写入输出表 TableResult tableResult = table1.executeInsert("outputTable");
通过执行 DDL 来直接创建一个表。这里执行的 CREATE 语句中用 WITH 指定了外部系统的连接器,于是就可以连接外部系统读取数据了。这其实是更加一般化的程序架构,因为这样我们就可以完全抛开DataStream API,直接用 SQL 语句实现全部的流处理过程。
创建表环境
使用 TableAPI 和 SQL 需要一个特别的运行时环境,这就是"表环境"(TableEnvironment)。主要负责:
- 注册Catalog 和表;
- 执行 SQL 查询;
- 注册用户自定义函数(UDF)
- DataStream 和表之间的转换
Catalog 就是"目录"主要用来管理所有数据库(database)和表(table)的元数据(metadata),通过 Catalog 可以方便地对数据库和表进行查询的管理,可以认为我们所定义的表都会"挂靠"在某个目录下,这样就可以快速检索。在表环境中可以由用户自定义Catalog,并在其中注册表和自定义函数(UDF)。默认的 Catalog就叫作 default_catalog。
每个表和 SQL 的执行,都必须绑定在一个表环境(TableEnvironment)中。TableEnvironment是 Table API 中提供的基本接口类,通过调用静态的 create()方法来创建一个表环境实例。方法需要传入一个环境的配置参数EnvironmentSettings,可以指定当前表环境的执行模式和计划器(planner)。执行模式有批处理和流处理两种选择,默认是流处理模式;计划器默认使用 blink planner。
import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.TableEnvironment; EnvironmentSettings settings = EnvironmentSettings .newInstance() .inStreamingMode() // 使用流处理模式 .build(); TableEnvironment tableEnv = TableEnvironment.create(settings);
另一种更加简单的方式来创建表环境:
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.EnvironmentSettings; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
" 流式表环境 "( StreamTableEnvironment ), 它是继承自TableEnvironment 的子接口。调用它的 create() 方法, 只需要直接将当前的流执行环境(StreamExecutionEnvironment)传入,就可以创建出对应的流式表环境
三、创建表
为了方便地查询表,表环境中会维护一个目录(Catalog)和表的对应关系。所以表都是通过Catalog 来进行注册创建的。表在环境中有一个唯一的 ID,由三部分组成:目录(catalog) 名,数据库(database)名,以及表名。在默认情况下,目录名为 default_catalog,数据库名为default_database。所以如果我们直接创建一个叫作 MyTable 的表,它的 ID 就是:
default_catalog.default_database.MyTable
创建表的方式,有通过连接器(connector)和虚拟表(virtual tables)两种
- 连接器表(Connector Tables)
通过连接器(connector)连接到一个外部系统,然后定义出 对应的表结构。例如可以连接到 Kafka 或者文件系统,将存储在这些外部系统的数据以"表" 的形式定义出来,对表的读写就可以通过连接器转换成对外部系统的读写。当在表 环境中读取这张表,连接器就会从外部系统读取数据并进行转换;而当向这张表写入数据, 连接器就会将数据输出(Sink)到外部系统中。
可以调用表环境的 executeSql()方法,可以传入一个 DDL 作为参数执行SQL 操作。这里我们传入一个CREATE 语句进行表的创建,并通过 WITH 关键字指定连接到外部系统的连接器:
tableEnv.executeSql("CREATE [TEMPORARY] TABLE MyTable ... WITH ( 'connector' = ... )");
如果希望使用自定义的目录名和库名,可以在环境中进行设置:
tEnv.useCatalog("custom_catalog");
tEnv.useDatabase("custom_database");
2. 虚拟表(Virtual Tables)
环境中注册之后,我们就可以在 SQL 中直接使用这张表进行查询转换
Table newTable = tableEnv.sqlQuery("SELECT ... FROM MyTable... ");
调用了表环境的 sqlQuery()方法,直接传入一条 SQL 语句作为参数执行查询,得到的结果是一个 Table 对象。Table 是 Table API 中提供的核心接口类,就代表了一个 Java 中定义的表实例。
得到的 newTable 是一个中间转换结果,如果之后又希望直接使用这个表执行 SQL,又该怎么做呢?由于 newTable 是一个 Table 对象,并没有在表环境中注册;所以我们还需要将这个 中 间 结 果 表 注 册 到 环 境 中 , 才 能 在 SQL 中 使 用 :
tableEnv.createTemporaryView("NewTable", newTable);
这里的注册其实是创建了一个"虚拟表"(Virtual Table)。这个概念与 SQL 语法中的视图(View)非常类似,所以调用的方法也叫作创建"虚拟视图"(createTemporaryView)。视图之所以是"虚拟"的,是因为我们并不会直接保存这个表的内容,并没有"实体";只是在用到这张表的时候,会将它对应的查询语句嵌入到 SQL 中。注册为虚拟表之后,我们就又可以在 SQL 中直接使用 NewTable 进行查询转换了。
读入表–读入内存格式
// 创建表 tableEnv.executeSql("CREATE TABLE EventTable (" + "user_name STRING," + "url STRING," + "ts BIGINT)" + " with (" + " 'connector' = 'filesystem'," + " 'path' = 'input/clicks.txt'," + " 'format' = 'csv'" + ")");
输出表–输出存储格式
// 创建输出表 tableEnv.executeSql("CREATE TABLE outTable (" + "user_name STRING," + "url STRING" + ") with (" + " 'connector' = 'filesystem'," + " 'path' = 'output'," + " 'format' = 'csv'" + ")");
四、表的查询
对一个表的查询(Query)操作,就对应着流数据的转换(Transform)处理。
1. 执行 SQL 进行查询
Flink 基于 Apache Calcite 来提供对SQL 的支持,Calcite 是一个为不同的计算平台提供标准 SQL 查询的底层工具,很多大数据框架比如 Apache Hive、Apache Kylin 中的SQL 支持都是通过集成 Calcite 来实现的。
只要调用表环境的 sqlQuery()方法,传入一个字符串形式的 SQL 查询语句就可以了。执行得到的结果,是一个 Table 对象。
// 查询用户 Alice 的点击事件,并提取表中前两个字段 Table aliceVisitTable = tableEnv.sqlQuery( "SELECT user, url " + "FROM EventTable " + "WHERE user = 'Alice' " );
也可以直接将查询的结果写入到已经注册的表中,这需要调用表环境的executeSql()方法来执行DDL,传入的是一个INSERT 语句:
// 将查询结果输出到 OutputTable 中 tableEnv.executeSql ( "INSERT INTO OutputTable " + "SELECT user, url " + "FROM EventTable " + "WHERE user = 'Alice' ");
2. 调用 Table API 进行查询
嵌入在 Java 和 Scala 语言内的查询 API,核心就是 Table 接口类,通过一步步链式调用 Table 的方法,就可以定义出所有的查询转换操作。每一步方法调用的返回结果,都是一个Table。
由于Table API 是基于Table 的Java 实例进行调用的,因此我们首先要得到表的Java 对象。基于环境中已注册的表,可以通过表环境的 from()方法非常容易地得到一个Table 对象:
Table eventTable = tableEnv.from("EventTable");
传入的参数就是注册好的表名。注意这里 eventTable 是一个Table 对象,而EventTable 是在环境中注册的表名。得到 Table 对象之后,就可以调用 API 进行各种转换操作了,得到的是一个新的Table 对象:
Table maryClickTable = eventTable .where($("user").isEqual("Alice")) .select($("url"), $("user"));
"$"符号用来指定表中的一个字段。上面的代码和直接执行 SQL 是等效的。 Table API 是嵌入编程语言中的DSL,SQL 中的很多特性和功能必须要有对应的实现才可以使用,因此跟直接写 SQL 比起来肯定就要麻烦一些。
五、输出表
对应着流处理中的读取数据源(Source)和转换(Transform);而最后一个步骤 Sink,也就是将结果数据输出到外部系统,就对应着表的输出操作。在代码上,输出一张表最直接的方法,就是调用 Table 的方法executeInsert()方法将一个Table 写入到注册过的表中,方法传入的参数就是注册的表名。
// 将结果表写入已注册的输出表中 result.executeInsert("OutputTable");
在底层,表的输出是通过将数据写入到TableSink 来实现的。TableSink 是Table API 中提供的一个向外部系统写入数据的通用接口,可以支持不同的文件格式(比如 CSV、Parquet)、存储数据库(比如 JDBC、HBase、Elasticsearch)和消息队列(比如 Kafka)。
浙公网安备 33010602011771号