Java面试——Flink原理及应用(二)

Flink原理及应用

2、Flink的应用

Flink被广泛应用于流式计算和批量计算中,其高吞吐量、低延迟和简单易用的API得到业界的一致好评。下面对Flink的安装和Flink API的使用进行简单介绍。

2.1、Flink的安装

Flink可以在独立的集群模式下运行,也可以在YARN、Mesos、Docker、Kubernetes等虚拟化环境中运行,还可以在AWS、Google Computer Engine等云环境中运行。下面介绍Flink的独立集群模式安装和高可用模式安装,对于其他运行模式请读者参照官网。

2.1.1、Flink的独立集群模式安装
  • (1)安装Java环境,Flink要求JDK版本大于1.8。
  • (2)各个服务器之间互信配置。
  • (3)从Apache官网下载Flink最新安装包,目前最新版本为1.9。安装包中1.9.1为Flink的版本,2.11为Flink对应的Scala版本。下载页面如图所示。
  • (4)将Flink安装包复制到待安装目录,执行以下命令解压Flink安装包,并进入安装目录。
  • (5)执行以下命令,编辑Flink配置文件flink-conf.yaml。
  • (6)在flink-conf.yaml中修改如下核心配置。


    对上述配置参数说明如下。
    • ①jobmanager.heap.size:Job Manager JVM的堆内存大小。由于Job Manager主要用于Job分发和运行状态监控等,具体的计算任务均在Task Manager上被执行,因此不需要太大的内存资源。
    • ②taskmanager.heap.size:Task Manager JVM的堆内存大小。Flink计算任务均在Task Manager上被执行,且Flink任务主要在内存中完成快速计算,因此一般会把操作系统主要的内存都分配给该任务。如果服务器只有一个Flink Task Manager,则建议内存分配策略为:操作系统预留10%的内存资源,taskmanager.heap.size预留60%的内存资源,其余内存资源留给堆外内存使用。
    • ③taskmanager.numberOfTaskSlots:每个TaskManager分配的Slot数量。每个Slot都对应一个串行的 Pipeline,一般配置的建议值为CPU核数,这样可以避免CPU上下文切换,使得一个CPU持续为某个任务服务。
    • ④parallelism.default:Flink集群的默认并行度,默认值为1。
    • ⑤io.tmp.dirs:Flink临时数据的存放地址。
    • ⑥state.backend:Backend计算状态的存储方式,包括jobmanager(Memery StateBackend)、filesystem(FsStateBackend)和rocksdb(RockDBStateBockend)。
    • ⑦state.checkpoints.dir:CheckPoint的存储地址,用于计算状态的存储和故障恢复。存储地址需要与state.backend的配置对应。当Flink任务启动时可以指定CheckPoint地址,让应用程序从指定状态开始运行。从指定状态启动的命令如下。
    • ⑧state.savepoints.dir:SavePoint的存储地址,需要与state.backend的配置对应。
    • ⑨taskmanager.network.memory.fraction:用于网络缓冲区的JVM内存份数,该参数决定了Task Manager可以同时具有多少个流数据交换通道及通道的缓冲程度。
    • ⑩taskmanager.network.memory.min:网络缓冲区的内存最小值。
    • ⑪taskmanager.network.memory.max:网络缓冲区的内存最大值,由于Flink集群中的数据大多来自外部系统,比如Kafka消息系统、Socket端口、S3等,这些数据均要通过网络的形式在集群中传输,其值一般为该集群能处理的最大数据量。
  • (7)执行以下命令,配置Master地址。
  • (8)在masters配置文件中加入Master服务地址和端口号,具体如下。
  • (9)执行以下命令,配置Slave地址。
  • (10)在slaves配置文件中加入Slave服务地址,具体如下。
  • (11)执行以下命令,启动集群。
  • (12)启动日志如下。

  • (13)执行Jps,查看进程。
  • (14)在浏览器地址栏中输入127.0.0.1:8081访问Flink集群监控界面,如图所示。
2.1.2、Flink的高可用模式安装

在Flink集群中,Job Manager是任务的管理节点,TaskManager是任务的计算节点,因此Task Manager可以按照计算需求进行水平扩展。Flink的高可用(HighAvailability,HA)主要指Job Manager的高可用。

Job Manager的高可用依赖ZooKeeper进行节点状态的管理和选举工作。Job Manager高可用模式为主(Leader)从(Flower)模式,在正常情况下由JobManager主节点为整个集群提供服务,Job Manager从节点处于Standby状态。在主节点宕机后集群会在现有从节点中选举一个节点作为主节点对外提供服务,在之前主节点启动后将以从节点的角色加入集群。一个包含2个JobManager和3个Task Manager的Flink HA集群如图所示。

Flink高可用模式的配置如下。

  • (1)ZooKeeper的安装、配置和启动,具体步骤请读者参照ZooKeeper章节。
  • (2)执行以下命令,编辑conf/flink-conf.yaml配置文件。
  • (3)在conf/flink-conf.yaml配置文件中加入以下HA配置。

    上述配置中的high-availability:zookeeper用于声明Flink集群以高可用模式运行,且高可用模式依赖ZooKeeper服务。high-availability.zookeeper.quorum: 129.168.1.100:2181配置了高可用模式下的ZooKeeper地址,high-availability.storageDir配置了高可用模式下的状态存储地址,该地址必须是一个Job Manager和Task Manager均可访问的地址,比如HDFS、S3、Cpeh、NFS等。Flink会将集群运行过程的主要数据存储在该地址上,当JobManager发生意外宕机时,集群会从high- availability.storageDir中获取最近一次的状态数据进行状态还原和故障恢复。
  • (4)按照以下配置修改conf/master配置文件。

    上述配置定义了2个Job Manager,服务地址分别为129.168.1.100:8081和129.168.1.101:8081。
  • (5)按照以下配置修改conf/salver配置文件。

    上述配置定义了3个Task Manager,服务地址分别为192.168.1.102、192.168.1.103、192.168.1.104。
  • (6)执行以下命令,启动集群。

2.2、Flink实战案例

Flink支持基于Java、Scala和Python进行开发。下面以Java为基础介绍Flink的使用。

2.2.1、构建一个基于Java的Flink应用

构建一个简单的Flink应用的步骤如下。

  • (1)新建一个名为Flink的Maven项目。
  • (2)在pom.xml中添加以下Flink依赖包。


    在上述依赖中,flink-java是Flink开发必需的核心包,flink-streaming-java_2.11是Flink流数据开发的依赖包。其中,scope为provided的意思,指将依赖包进行编译,但是不将其打包到JAR文件中,这样做不但能够减小JAR文件的大小,还能避免客户端依赖包版本和服务端依赖包版本不一致引起的JAR包冲突问题。
  • (3)Maven构建配置:在pom.xml中添加Maven构建插件。

  • (4)新建一个SocketFlinkDemo类,在类中定义一个简单的Flink统计程序。


    上述代码定义了一个简单的监听和获取Socket上输入的单词并对单词进行统计的Flink程序。其核心步骤如下。
    • (1)定义(获取)StreamExecutionEnvironment实例env:StreamExecutionEnvironment是Flink程序运行的上下文环境,每个流式计算都在一个StreamExecutionEnvironment上运行。
    • (2)定义数据源:上述代码通过调用env的socketTextStream方法定义一个数据流,来实时监听和获取对应的Socket上的数据。
    • (3)定义转换流:上述代码定义了3个转换流,分别是flatMap、keyBy、reduce操作。flatMap用于遍历每个数据并获取数据内容,构造WordWithCount数据结构并输出;keyBy以word字段为Key对数据进行分组;reduce对单词执行统计计算,并返回计算结果。上述代码还定义了一个长度为5s的时间窗口,来定时触发计算。
    • (4)定义数据输出:为数据流添加一个Sink算子将计算结果输出,在上述代码中,Sink通过覆写invoke方法获取计算结果,并打印计算结果。
    • (5)触发任务执行:调用env的execute触发并执行任务。
      在上述计算中使用到的数据模型WordWithCount如下。

    • (6)打开终端,输入nc-l 8000,在8000端口上构建一个Socket端口监听。
    • (7)程序运行:在SocketFlinkDemo上单击右键,执行该Flink程序,Flink会在程序内部启动一个程序级别的集群(Flink Application Cluster)并运行该程序。FlinkApplication Cluster是一个专用的Flink Cluster,仅用于执行单个Flink Job。Flink Cluster的生命周期与FlinkJob的生命周期对应。在运行后可以看到如下Flink启动核心日志。

注意:以下日志仅为与集群启动状态有关的核心日志,其他日志省略,以#开头的注释用于方便读者阅读,在真实日志中不存在该注释。

  • (8)在Socket端口上输入数据:在终端输入以下字符串。
  • (9)查看Flink程序统计结果。

DataStream是Flink基于数据流封装的标准流式处理API,支持数据过滤(Filter)​、状态更新(UpdateState)​、窗口定义(Windows Defining)和数据聚合(Aggregating)等操作。DataStream的数据来源多样,包括Socket服务、消息系统(Kafka、Kinesis、RabbitMQ)​、文件系统。其计算结果可以通过Sink操作写入文件、标准数据库存储、消息系统、日志等任何应用程序可以输出的目标地址。

DataStream API编程的核心流程是获取数据源(DataSource)​、执行数据转换(Data Transformation)操作、输出计算结果(Data Sink)​。

** 1)数据源。**

数据源是流式计算的起点,Flink通过数据源将外界数据接入系统进行计算。应用程序客户端通过StreamExecutionEnvironment.addSource(sourceFunction)向数据流加入一个数据源。Flink默认实现了多种数据源,具体如表所示。

2)数据转换操作。

数据转换操作将一个或多个DataStream转换为一个新的DataStream。应用程序通过连接和组合多个数据转换操作形成一个数据流拓扑图。常用的数据转换操作如下。

(1)Map。

  • 转换操作名称:Map。
  • 转换操作说明:输入DataStream的元素通过Map函数转换返回一个新的元素。
  • 转换操作类型:将DataStream转换为DataStream。
  • 使用示例:将DataStream中的每个Integer类型的元素都计算平方后输出。

    (2)FlatMap。
  • 转换操作名称:FlatMap。
  • 转换操作说明:输入DataStream的元素通过FlatMap函数转换返回零个、一个或多个新的元素。
  • 转换操作类型:将DataStream转换为DataStream。
  • 使用示例:将字符串按照空格分隔输出。

    (3)Filter。
  • 转换操作名称:Filter。
  • 转换操作说明:对每个元素都进行布尔函数运算,返回布尔函数运算结果为true的元素。
  • 转换操作类型:将DataStream转换为DataStream。
  • 使用示例:返回DataStream中的偶数。


    (4)KeyBy。
  • 转换操作名称:KeyBy。
  • 转换操作说明:在逻辑上将数据流划分为多个分区,所有具有相同Key的元素都被分配到同一个分区。keyBy()内部基于Hash函数实现分区。有多种指定Key的方式。
  • 转换操作类型:将DataStream转换为KeyedStream。
  • 使用示例:以someKey为Key对数据分区;以Tuple的第一个元素为Key对数据分区。

    注意:POJO类型的数据如果没有实现hashCode方法,则不能进行分区。

(5)Reduce。

  • 转换操作名称:Reduce。
  • 转换操作说明:对KeyedStream上的数据进行Reduce操作,返回一个新的元素。
  • 转换操作类型:将KeyedStream转换为DataStream。
  • 使用示例:对KeyedStream上的元素求和。

    (6)Fold。
  • 转换操作名称:Fold。
  • 转换操作说明:对KeyedStream上的元素进行叠加操作,将当前元素和上次叠加操作的元素进行组合,输出一个新的元素。
  • 转换操作类型:将KeyedStream转换为DataStream。
  • 使用示例:对序列(1,2,3,4,5)执行Fold后输出,输出结果为:"start-1","start-1-2","start-1-2-3"......

(7)Aggregation。

  • 转换操作名称:Aggregation。
  • 转换操作说明:对KeyedStream上的元素进行聚合操作,min返回统计结果中的最小值,minBy返回统计结果中最小值对应的元素。
  • 转换操作类型:将KeyedStream转换为DataStream。
  • 使用示例:对KeyedStream中的元素进行求和、求最小值、求最大值。

    (8)Window。
  • 转换操作名称:Window。
  • 转换操作说明:Window操作根据设置(例如在过去5s内到达的数据)将每个KeyedStream中的数据都分配到Window中,Flink将流数据分配到不同的Window中执行计算。
  • 转换操作类型:将KeyedStream转换为WindowedStream。
  • 使用示例:定义一个每5s执行一次的Window翻滚窗口。

    (9)WindowAll。
  • 转换操作名称:WindowAll。
  • 转换操作说明:WindowAll操作根据设置(例如在过去5s内到达的数据)将所有DataStream中的数据都分配到一个Window中,WindowAll为非并行转换操作。执行WindowAll操作后的所有元素都会被分配到一个Task上进行计算。
  • 转换操作类型:将DataStream转换为AllWindowedStream。
  • 使用示例:定义一个每5s执行一次的WindowAll翻滚窗口。

    (10)Window Apply。
  • 转换操作名称:Window Apply。
  • 转换操作说明:自定义窗口计算函数,将WindowedStream或AllWindowedStream中的元素经过Apply函数计算转换为DataStream。
  • 转换操作类型:将WindowedStream或AllWindowedStream转换为DataStream。
  • 使用示例:定义一个每10s触发一次的TimeWindowAll翻滚窗口,统计每个Window中的数据个数和最大值。


    上述代码通过timeWindowAll(Time.seconds(10))定义了一个每10s触发一次的TimeWindowAll翻滚窗口,并在apply方法中实现了对窗口中的数据(values)进行遍历,计算窗口中数据的最大值,最后将结果输出。
    同时,为了便于读者理解,apply方法统计并打印了窗口中数据的个数和窗口的开始时间和结束时间。向socketTextStream中输入数据1、3、5,打印出以下结果。通过输出能够明显地看出,窗口的开始时间1575123820000ms和结束时间1575123830000ms相差10s,也就是TimeWindowAll翻滚窗口的时间长度。

    (11)Window Reduce。
  • 转换操作名称:Window Reduce。
  • 转换操作说明:对WindowedStream上的数据执行Reduce操作,并将Reduce的结果返回。
  • 转换操作类型:将WindowedStream转换为DataStream。
  • 使用示例:对WindowedStream中的数据类型为Tuple2<String,Integer>的第二个值执行求和操作。

    (12)Window Fold。
  • 转换操作名称:Window Fold。
  • 转换操作说明:对WindowedStream上的数据执行叠加操作。
  • 转换操作类型:将WindowedStream转换为DataStream。
  • 使用示例:将WindowedStream中的数据使用"_"连接起来。

    (13)Aggregation on Window。
  • 转换操作名称:Aggregation on Window。
  • 转换操作说明:按照指定的统计函数对WindowedStream上的数据执行统计。
  • 转换操作类型:将WindowedStream转换为DataStream。
  • 使用示例:对WindowedStream中的元素进行求和、最小值、最大值统计。

    (14)Union。
  • 转换操作名称:Union。
  • 转换操作说明:将多个DataStream联合(Union)起来构成一个新的DataStream。
  • 转换操作类型:将多个DataStream转换为一个DataStream。
  • 使用示例:将dataStream1和otherStream2、otherStream3等多个dataStream联合起来构成一个新的dataStream。

    (15)Window Join。
  • 转换操作名称:Window Join。
  • 转换操作说明:根据指定的Key将两个DataStream上的数据流进行Join。
  • 转换操作类型:将两个DataStream转换为一个DataStream。
  • 使用示例:将otherStream和dataStream进行Join,Join的结果为一个每5s执行一次的翻滚窗口。

    (16)Connect。
  • 转换操作名称:Connect。
  • 转换操作说明:将两个DataStream连接,生成一个新的ConnectedStreams数据流。
  • 转换操作类型:将两个DataStream转换为一个ConnectedStreams。
  • 使用示例:定义两个DataStream并将其连接起来。

(17)CoMap和CoFlatMap。

  • 转换操作名称:CoMap和CoFlatMap。
  • 转换操作说明:CoMap、CoFlatMap的功能与Map、FlatMap的功能类似,不同之处在于CoMap、CoFlatMap作用于ConnectedStreams上。
  • 转换操作类型:将ConnectedStreams转换为DataStream。
  • 使用示例:在connectedStreams上分别执行map和flatMap操作。

    (18)Split。
  • 转换操作名称:Split。
  • 转换操作说明:将DataStream按照指定的规则拆分(Split)为多个SplitStream。
  • 转换操作类型:将DataStream转换为SplitStream。
  • 使用示例:将someDataStream拆分为两个dataStream,一个outputName为even,另一个outputName为odd。


    (19)Select。
  • 转换操作名称:Select。
  • 转换操作说明:从SplitStream中通过选择器选择部分数据,组成一个新的DataStream。
  • 转换操作类型:将SplitStream转换为DataStream。
  • 使用示例:将someDataStream数据流根据选择器分为三个dataStream,一个outputName为even,一个outputName为odd,另外一个outputName为even和odd。

    (20)Custom Partitioning。
  • 转换操作名称:Custom Partitioning。
  • 转换操作说明:对DataStream上的数据按照指定Key进行分区操作。
  • 转换操作类型:将DataStream转换为DataStream。
  • 使用示例:将dataStream分别按照"someKey"分区和按照第一个值自动分区。

    (21)Random Partitioning。
  • 转换操作名称:Random Partitioning。
  • 转换操作说明:对DataStream上的数据进行随机分区操作,随机分区的结果一般比较均匀。
  • 转换操作类型:将DataStream转换为DataStream。
  • 使用示例:将dataStream随机分区。

(22)Broadcasting。

  • 转换操作名称:Broadcasting。
  • 转换操作说明:将DataStream中的数据(一般为配置信息等公共数据流)广播(Broadcasting)到每个分区上。
  • 转换操作类型:将DataStream转换为DataStream。
  • 使用示例:对dataStream中的数据进行广播。

    3)数据输出。

Flink可以将计算的结果数据输出到File、Socket、外部存储或者将结果打印到日志中。常用的Flink数据输出操作如表所示。

除了上述输出,Flink还提供了丰富的连接器,用于将结果输出到Kafka、Cassandra、Redis等消息系统和数据库。

(1)将结果输出到Kafka。

如下代码将dataStream中的数据输出到Kafka。

(2)将结果输出到RabbitMQ。

如下代码将dataStream中的数据输出到RabbitMQ。

(3)将结果输出到Cassandra。

如下代码将dataStream中的数据输出到Cassandra。

除了基于SQL的方式将结果输出到Cassandra,Flink还执行以POJO形式将结果输出到Cassandra。

(4)将结果输出到Kinesis。

如下代码将dataStream中的数据输出到Kinesis。

(5)将结果输出到ElasticSearch。

如下代码将dataStream中的数据输出到ElasticSearch。

(6)将结果输出到Hadoop FileSystem。

如下代码将dataStream中的数据输出到HadoopFileSystem。

(7)将结果输出到Flume。

如下代码将dataStream中的数据输出到Flume。

(8)将结果输出到Redis。

如下代码将dataStream中的数据输出到Redis。

(9)自定义Sink。

如下代码将自定义一个SinkLog,将dataStream中的数据通过SinkLog输出。

Flink封装了DataSet API用于处理批量数据。Flink处理批量数据的流程为:Flink系统内部将接收的数据转换成DataSet数据集,然后将DataSet数据集并行分布在集群的每个节点上;最后基于DataSet数据集进行各种转换操作(例如Map、Filter等)​,最终通过DataSink操作将结果数据集输出到外部系统。

1)数据源。

Flink可以基于通用文件格式创建数据集。其创建数据集的方法位于ExecutionEnvironment。

常用的基于文件系统创建DataSet的操作如下。

  • (1)readTextFile(path):按行读取文件并将其作为字符串返回。
  • (2)readTextFileWithValue(path):按行读取文件并将其作为可变字符串返回。
  • (3)readCsvFile(path):按行读取文件,使用逗号或其他字符分隔,并返回元组或对象的数据集。
  • (4)readFileOfPrimitives(path,delimiter):解析基本数据类型(字符串或整数)的文件,以换行符或其他字符分隔。
  • (5)readSequenceFile(Key,Value,path):从指定路径读取并解析SequenceFile,返回<Key,Value>元组。

常用的基于集合创建DataSet的操作如下。

  • (1)fromCollection(Seq):用对象集合创建数据集。集合中的所有元素必须属于同一类型。
  • (2)fromCollection(Iterator):用迭代器创建数据集,指定迭代器返回元素的数据类型。
  • (3)fromElements(elements:_*):根据给定的对象序列创建数据集。所有对象必须属于同一类型。
  • (4)fromParallelCollection(SplittableIterator):并行地从迭代器创建数据集。指定迭代器返回元素的数据类型。
  • (5)generateSequence(from,to):并行生成给定间隔的数字序列。

常用的基于文件系统创建DataSet的操作如下。

  • (1)readFile(inputFormat,path):接收文件输入格式为inputFormat的数据。
  • (2)createInput(inputFormat):接收通用输入格式,Flink会自动识别文件格式。

2)数据转换操作。

DataSet的Transformation操作和DataStream基本类似,常用方法如下。

  • (1)Map:根据一个元素生成一个新的元素。
  • (2)FlatMap:根据一个元素生成多个元素。
  • (3)MapPartition:将DataStream进行分区操作。
  • (4)Filter:对每个数据都执行布尔函数,只保存函数返回true的数据。
  • (5)Distinct:对数据集中的元素去重并返回新的数据集。
  • (6)Reduce:作用于整个DataSet,合并该数据集的元素。
  • (7)Aggregate:对一组数据求聚合操作。
  • (8)ReduceGroup:通过将此数据集中的所有元素传递给函数,创建一个新的数据集。该函数可以使用收集器输出零个或多个元素,也可以作用于完整的数据集,迭代器会返回完整的数据集的元素。
  • (9)Join:将两个DataSet连接生成一个新的DataSet。两个数据集对指定要连接的Key进行Join,默认是一个Inner Join。可以使用JoinFunction将该组连接的元素转化为单个元素,也可以使用FlatJoinFunction将该组元素转化为任意多个元素(包括none)。
  • (10)OuterJoin:两个数据集执行左连接(leftOuterJoin)、右连接(rightOuterJoin)或全外连接(fullOuterJoin)。与Join(Inner Join)的区别在于,如果在另一侧没有找到匹配的数据,则保存这一侧的记录。
  • (11)CoGroup:对一个或多个字段中的每个输入都进行分组,然后加入组。每组都调用转换函数。
  • (12)Union:构建两个数据集的并集。
  • (13)Cross:构建两个输入数据集的笛卡尔积。可选择使用CrossFunction将元素对转换为单个元素。
  • (14)Rebalance:均匀地重新对数据集进行并行分区以消除数据偏差。
  • (15)Hash Partition:根据给定的Key对数据集做Hash分区。可以是Position Keys、Expression Keys或者KeySelector Functions。
  • (16)Range Partition:根据给定的Key对一个数据集进行Range分区。可以是Position Keys、Expression Keys或者Key Selector Functions。
  • (17)Sort Partition:本地按照指定顺序在指定字段上对数据集的所有分区进行排序,可以指定Field Position或Filed Expression。

** 3)数据输出。**

DataSet通过Data Sink将DataStream中的数据输出到外部系统。DataSet常用的输出操作如下。

  • (1)writeAsText():将元素以字符串形式写入文件。
  • (2)writeAsCsv(...):将元组字段以逗号分隔写入文件,可配置行和字段分隔符。
  • (3)print():将每个元素都调用toString()打印输出。
  • (4)write():自定义文件输出方法的基类,支持自定义对象到字节的转换。
  • (5)output():通用输出方法。

4)DataSet API使用实例。

以下代码实现一个最基本的DataSet处理流程,步骤如下。

  • (1)定义(获取)Flink的执行环境ExecutionEnvironment。
  • (2)从本地文件读取文件到DataSet。
  • (3)在DataSet上执行转换操作,调用filter操作,只返回偶数数据。
  • (4)输出结果,通过print方法打印执行结果。

Flink有两种关系型API:Table API和SQL。Flink通过Table API和SQL来统一流处理和批处理。Table API是Scala和Java的集成查询API,允许以非常直观的方式组合执行关系运算(例如,Selection、Filter和Join)的查询。Flink的SQL基于遵循SQL标准的ApacheCalcite实现。无论批处理(DataSet)还是流处理(DataStream)​,在两个接口中指定的查询都具有相同语义且具有相同结果。

Table API、SQL接口、DataSet、DataStream相互之间紧密集成在一起,Flink各个API的关系如图11-27所示。应用可以轻松地在各种类型API库之间切换。例如,可以使用CEP库从DataStream中提取数据,然后使用TableAPI或SQL执行查询扫描、筛选和聚合批处理,最终将结 果输出。

1)Flink关系型API的原理。

Flink基于Apache Calcite实现SQL语义解析。Flink通过利用Calcite的查询优化框架与SQL解释器来完成SQL的解析、查询优化、逻辑树生成,得到Calcite的RelRoot类的一棵逻辑执行计划树,并最终生成Flink的Table。

Table中的执行计划会转化成DataSet或DataStream执行具体的数据计算。Flink关系型API的执行流程如图所示。

  • (1)Table Source、DataSet、DataStream注册Catalog。
  • (2)调用Table API或SQL执行SQL查询。
  • (3)SQL解析、验证,并生成统一的Calcite的逻辑执行计划(Logical Plan)。
  • (4)根据数据源的性质(DataSet、DataStream)使用不同规则进行优化。
  • (5)最终优化后的执行计划将被转换成常规的FlinkDataSet或DataStream程序。

2)Flink Table API和SQL的使用流程。

Table API和DataSet、DataStream关联密切,可以首先通过一个DataSet或DataStream创建出一个Table,然后调用类似Filter、Join、Select的关系型转换操作来转换为一个新的Table对象,最后将一个Table对象转换为一个DataSet或DataStream并将结果输出。从内部实现上来说,所有应用于Table的转换操作都变成一棵逻辑表操作树,在Table对象被转换回DataSet或者DataStream之后,转换器会将逻辑表操作树转化为对应的DataSet或者DataStream操作符。其使用流程如下。

  • (1)创建一个TableEnvironment:TableEnvironment对象是Table API和SQL集成的核心,主要用于注册一个Table、注册一个外部的Catalog、执行SQL查询、注册一个用户自定义的Function、将DataStream或DataSet转换成Table等操作。
  • (2)注册Table:将一个Table注册到TableEnvironment,具体步骤为,将一个TableSource(MySQL、HBase、CSV、Kakfa、RabbitMQ等)注册到TableEnvironment;将一个外部的Catalog注册到TableEnvironment,访问外部系统的数据或文件;将DataStream或DataSet注册为Table。
  • (3)查询Table上的数据:执行Table API或SQL语句进行查询,查询的输出结果是一个新的Table。
  • (4)输出Table:为了将Table输出,可以使用TableSink。TableSink是一个通用接口,支持各种各样的文件格式(例如CSV、Parquet、Avro),也支持各种各样的外部系统(例如JDBC、HBase、Cassandra,、ElasticSearch),同样支持各种各样的消息服务(例如Kafka、RabbitMQ)。批量数据的导出Table使用BatchTableSink。流数据的导出Table使用AppendStreamTableSink、RetractStreamTableSink和UpsertStreamTableSink。
  • (5)解析Query并执行:在步骤(4)中输出的Table和SQL查询被解析成DataStream或DataSet。一次查询为一个Logical Query Plan,解析Logical Query Plan分为优化Logical Plan、将Logical Plan转化为DataStream或DataSet两步。在Table API和SQL解析完毕后,其查询会被当作普通DataStream或DataSet进行执行。

如下代码实现了一个基于Table API的批量数据查询操作。

上述代码将List中Persion的数据转换为Table,并注册Table,最后基于SQL执行查询并将计算结果转换为DataSet输出。

Table不但能用于批量数据查询,还能基于实时流数据执行查询。如下代码从Kafka中获取实时流数据,并将数据转换为实时Table执行查询。

上述代码将Kafka中的数据转换为StreamTable,然后基于该StreamTable执行Table API和SQL的查询操作。

相关推荐
深念Y23 分钟前
数据库层设计的取舍:ORM 便利性与手写 SQL 的安全性权衡
java·数据库·sql·golang·框架·语言·ome
小江的记录本25 分钟前
【ORM 框架】MyBatis-Plus 核心特性、条件构造器、分页插件、乐观锁插件(附《思维导图》、《问题排查与实践清单》和《面试高频考点汇总》)
java·windows·spring boot·python·spring·面试·mybatis
clorinda27 分钟前
SQL 快速入门:题目单知识点精炼总结
java·数据库·sql
AI人工智能+电脑小能手36 分钟前
大白话说Java设计模式-40-备忘录模式(业务实战篇)
java·设计模式·事务回滚·备忘录模式·状态保存·spring transactional·购物车快照
SimonKing37 分钟前
白嫖国产多模态大模型:商汤 SenseNova 接入指南
java·后端·程序员
小江的记录本37 分钟前
【ORM框架】MyBatis核心原理、ORM思想、MyBatis vs JPA
java·数据库·后端·spring·spring cloud·oracle·mybatis
_明月1 小时前
汇川技术股份有限公司面试--Java外包岗
java·面试·职场和发展
BD_Marathon1 小时前
Hadoop常用端口号
java·大数据·hadoop
水管在开花.1 小时前
RAG:知识库问答系统教学②-零基础保姆级教程
人工智能·面试·agent·rag