简介
Flink 自身提供了不同级别的抽象来支持我们开发流式或者批量处理程序,下图描述了 Flink 支持的 4 种不同级别的抽象。
Table API 和 SQL 处于最顶端,是 Flink 提供的高级 API 操作。Flink SQL 是 Flink 实时计算的简化计算模型,降低用户使用实时计算门槛而设计的一套符合标准 SQL 语义的开发语言。
Flink 在编程模型上提供了 DataStream 和 DataSet 两套 API,并没有做到事实上的流批统一,用户和开发者开发了两套代码。正是因为 Flink Table & SQL 的加入, Flink 在某种程度上做到了事实上的批流一体。
与传统的SQL查询相比,Flink SQL是动态表查询,SQL不会中止,会不断的执行,且增加了窗口和排序。FLink从 0.9 版本中支持FLink SQL,但是目前为止,Flink SQL和Table没有支持完全的业务场景,有些场景没有调优等。所以需要到官网进行查询是否支持业务场景。
原理
Hive的底层对 SQL 的解析用到了 Apache Calcite,Flink 同样把 SQL 的解析、优化和执行交给了Calcite。
下图是一张经典的 Flink Table & SQL 实现原理图,可以看到 Calcite 在整个架构中处于绝对核心地位。
从图中可以看到无论是批查询 SQL 还是流式查询 SQL,都会经过对应的转换器 Parser 转换成为节点树 SQLNode tree,然后生成逻辑执行计划 Logical Plan,逻辑执行计划在经过优化后生成真正可以执行的物理执行计划,交给 DataSet 或者 DataStream 的 API 去执行。
一个完整的 Flink Table & SQL Job 也是由 Source、Transformation、Sink 构成:
Source 部分来源于外部数据源,我们经常用的有 Kafka、MySQL 等;
Transformation 部分则是 Flink Table & SQL 支持的常用 SQL 算子,比如简单的 Select、Groupby 等,当然在这里也有更为复杂的多流 Join、流与维表的 Join 等;
Sink 部分是指的结果存储比如 MySQL、HBase 或 Kakfa 等。
Flink Table & SQL案例
本实例自定义源源不断的数据源,模拟student信息,根据id的奇偶筛选出两批学生。将数据输入到注册的临时表中,并将两表join,查询输出到控制台。
pom依赖
Table & SQL API依赖说明:
- flink-table-common: 公共模块,比如自定义函数、格式等需要依赖的。
- flink-table-api-java: Table 和 SQL API,使用 Java 语言编写的,给纯 table 程序使用(还在早期开发阶段,不建议使用)
- flink-table-api-scala: Table 和 SQL API,使用 Scala 语言编写的,给纯 table 程序使用(还在早期开发阶段,不建议使用)
- flink-table-api-java-bridge: Table 和 SQL API 结合 DataStream/DataSet API 一起使用,给 Java 语言使用。
- flink-table-api-scala-bridge: Table 和 SQL API 结合 DataStream/DataSet API 一起使用,给 Scala 语言使用。
- flink-table-planner: table Planner 和运行时。这是在1.9之前 Flink 的唯一的 Planner,但是从1.11版本开始我们不推荐继续使用。
- flink-table-planner-blink: 新的 Blink Planner,从1.11版本开始成为默认的 Planner。
- flink-table-runtime-blink: 新的 Blink 运行时。
- flink-table-uber: 把上述模块以及 Old Planner 打包到一起,可以在大部分 Table & SQL API 场景下使用。打包到一起的 jar 文件 flink-table-*.jar 默认会直接放到 Flink 发行版的 /lib 目录下。
- flink-table-uber-blink: 把上述模块以及 Blink Planner 打包到一起,可以在大部分 Table & SQL API 场景下使用。打包到一起的 jar 文件 flink-table-blink-*.jar 默认会放到 Flink 发行版的 /lib 目录下。
将本实例的flink程序相关的依赖加进来,此时在本地idea环境下运行:
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java_2.11</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-api-java</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-api-java-bridge_2.11</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-planner-blink_2.11</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-runtime-blink_2.11</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-table-common</artifactId>
<version>1.10.0</version>
</dependency>
Student数据类型实现
import lombok.Data;
@Data
public class Student {
int id;
String name;
}
数据源实现
此时需要模拟一个实时的数据流。
import org.apache.flink.streaming.api.functions.source.SourceFunction;
import java.util.ArrayList;
import java.util.Random;
class MyStreamingSource implements SourceFunction<Student> {
private boolean isRunning = true;
@Override
public void run(SourceContext<Student> ctx) throws Exception {
while (isRunning) {
Student student = generateStudent();
ctx.collect(student);
//睡一会
Thread.sleep(1000);
}
}
@Override
public void cancel() {
isRunning = false;
}
private Student generateStudent() {
int i = new Random().nextInt(1000);
ArrayList<String> list = new ArrayList<>();
list.add("LaoLiu");
list.add("LaoXu");
list.add("LaoWang");
list.add("LaoTan");
Student student = new Student();
student.setName(list.get(new Random().nextInt(4)));
student.setId(i);
return student;
}
}
案例实现
import org.apache.flink.api.common.typeinfo.TypeHint;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.api.java.tuple.Tuple4;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.datastream.SplitStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.EnvironmentSettings;
import org.apache.flink.table.api.Table;
import org.apache.flink.table.api.java.StreamTableEnvironment;
import java.util.ArrayList;
public class Demo {
public static void main(String[] args) throws Exception {
EnvironmentSettings bsSettings = EnvironmentSettings.newInstance().useBlinkPlanner().inStreamingMode().build();
StreamExecutionEnvironment bsEnv = StreamExecutionEnvironment.getExecutionEnvironment();
StreamTableEnvironment bsTableEnv = StreamTableEnvironment.create(bsEnv, bsSettings);
SingleOutputStreamOperator<Student> source = bsEnv.addSource(new MyStreamingSource());
DataStream<Student> splitAll = source.split(student -> {
ArrayList<String> output = new ArrayList<>();
if (student.getId() % 2 == 0) {
output.add("even");
} else {
output.add("odd");
}
return output;
});
//将两个流筛选出来
DataStream<Student> evenSelect = ((SplitStream<Student>) splitAll).select("even");
DataStream<Student> oddSelect = ((SplitStream<Student>) splitAll).select("odd");
//把这两个流在我们的Flink环境中注册为临时表
bsTableEnv.createTemporaryView("default_catalog.default_database.evenTable", evenSelect, "name,id");
bsTableEnv.createTemporaryView("default_catalog.default_database.oddTable", oddSelect, "name,id");
Table queryTable = bsTableEnv.sqlQuery("select a.id,a.name,b.id,b.name from evenTable as a join oddTable as b on a.name=b.name");
queryTable.printSchema();
// 把表转化成流
bsTableEnv.toRetractStream(queryTable, TypeInformation.of(new TypeHint<Tuple4<Integer, String, Integer, String>>() {
})).print();
bsEnv.execute("demo");
}
}
输出结果如下: