Flink笔记06——浅谈Table API

前言

在 Spark 中有 DataFrame 这样的关系型编程接口,因其强大且灵活的表达能力,能够让 用户通过非常丰富的接口对数据进行处理,有效降低了用户的使用成本。Flink 也提供了关 系型编程接口 Table API 以及基于 Table API 的 SQL API,让用户能够通过使用结构化编程 接口高效地构建 Flink 应用。同时 Table API 以及 SQL 能够统一处理批量和实时计算业务, 无须切换修改任何应用代码就能够基于同一套 API 编写流式应用和批量应用,从而达到真正 意义的批流统一。
Flink笔记06——浅谈Table API_第1张图片
1.在进行相关开发前,我们首先导入相关的依赖。

		
        
            org.apache.flink
            flink-table-planner_2.11
            1.9.1
        
        
            org.apache.flink
            flink-table-api-scala-bridge_2.11
            1.9.1
        

2.创建 TableEnvironment 对象

	//使用Flink原生的代码创建TableEnvironment
    val fsSettings = EnvironmentSettings.newInstance().useOldPlanner().inStreamingMode().build()
    val fsEnv = StreamExecutionEnvironment.getExecutionEnvironment
    val fsTableEnv = StreamTableEnvironment.create(fsEnv, fsSettings)

Table API

在 Flink 中创建一张表有两种方法:

  1. 从一个文件中导入表结构(Structure)(常用于批计算)(静态)
  2. 从 DataStream 或者 DataSet 转换成 Table (动态)

创建 Table

Table API 中已经提供了 TableSource 从外部系统获取数据,例如常见的数据库、文件 系统和 Kafka 消息队列等外部系统。

  1. 从文件中创建Table(静态表)
    Flink 允许用户从本地或者分布式文件系统中读取和写入数据,在 Table API 中可以通 过 CsvTableSource 类来创建,只需指定相应的参数即可。但是文件格式必须是 CSV 格式的。 其 他 文 件 格 式 也 支 持 ( 在 Flink 还 有 Connector 的 来 支 持 其 他 格 式 或 者 自 定 义 TableSource)。

    demo如下:

    package com.flink.primary.TableAndSQL
    
    import org.apache.flink.api.scala.typeutils.Types
    import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
    import org.apache.flink.table.api.EnvironmentSettings
    import org.apache.flink.table.api.scala.StreamTableEnvironment
    import org.apache.flink.table.sources.CsvTableSource
    
    /**
      * 从文件中创建 Table(静态表)
      * @author xjh 2020.5.30
      */
    object CreateTableEnvironmentByFile {
      def main(args: Array[String]): Unit = {
        //使用Flink原生的代码创建TableEnvironment
        val fsSettings = EnvironmentSettings.newInstance().useOldPlanner().inStreamingMode().build()
        val fsEnv = StreamExecutionEnvironment.getExecutionEnvironment
        val fsTableEnv = StreamTableEnvironment.create(fsEnv, fsSettings)
    
        //读取数据
        val source = new CsvTableSource("/station.log",
          Array[String]("f1", "f2", "f3", "f4", "f5", "f6"),
          Array(Types.STRING, Types.STRING, Types.STRING, Types.STRING, Types.LONG, Types.LONG)
        )
    
        //注册数据表
        fsTableEnv.registerTableSource("t_station_log",source)
    
       //打印表结构,或者使用Table API,需要得到Table对象
        val table = fsTableEnv.scan("t_station_log")
        table.printSchema() //打印表结构
      }
    }	
    

    值得注意的是,改代码中并没有流计算逻辑,所以没有写fsEnv.execute()。

  2. 从DataStream中创建Table(动态表)
    前面已经知道 Table API 是构建在 DataStream API 和 DataSet API 之上的一层更高级 的抽象,因此用户可以灵活地使用 Table API 将 Table 转换成 DataStream 或 DataSet 数据集,也可以将 DataSteam 或 DataSet 数据集转换成 Table,这和 Spark 中的 DataFrame 和 RDD 的关系类似。

    demo如下:

    package com.flink.primary.TableAndSQL
    
    import com.flink.primary.DataSource.StationLog
    import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
    import org.apache.flink.table.api.EnvironmentSettings
    import org.apache.flink.table.api.scala.StreamTableEnvironment
    
    /**
      * 从文件中创建 Table(静态表)
      * @author xjh 2020.5.30
      */
    object CreateTableEnvironmentByDataStream {
      def main(args: Array[String]): Unit = {
        //使用Flink原生的代码创建TableEnvironment
        val fsSettings = EnvironmentSettings.newInstance().useOldPlanner().inStreamingMode().build()
        val fsEnv = StreamExecutionEnvironment.getExecutionEnvironment
        val fsTableEnv = StreamTableEnvironment.create(fsEnv, fsSettings)
        import org.apache.flink.api.scala._
        
        //读取数据
        val stream = fsEnv.socketTextStream("m1", 8888)
          .map(line => {
            val arr = line.split(",")
            new StationLog(arr(0).trim, arr(1).trim, arr(2).trim, arr(3).trim, arr(4).trim.toLong, arr(5).trim.toLong)
          })
    
        //注册数据表
        fsTableEnv.registerDataStream("t_table2",stream)
    
        //打印表结构,或者使用Table API,需要得到Table对象
        val table = fsTableEnv.scan("t_table2")
        table.printSchema() //打印表结构
    
        fsEnv.execute()
      }
    }
    

    这里还可以使用fromDataStream来代替registerDataStream:

    //把DataStream对象变成一个Table 
    val table: Table = fsTableEvn.fromDataStream(stream) //直接变成table对象 table.printSchema() //打印表结构 streamEnv.execute()
    

修改Table中字段名

Flink 支持把自定义 POJOs 类的所有 case 类的属性名字变成字段名,也可以通过基于 字段偏移位置和字段名称两种方式重新修改:

//导入table库中的隐式转换 
import org.apache.flink.table.api.scala._ 
// 基于位置重新指定字段名称为"field1", "field2", "field3" 
val table = tStreamEnv.fromDataStream(stream, 'field1, 'field2, 'field3)
// 将DataStream转换成Table,并且将字段名称重新成别名 
val table: Table = tStreamEnv.fromDataStream(stream, 'rowtime as 'newTime, 'id as 'newId,'variable as 'newVariable)

注意:要导入隐式转换。如果使用as修改字段,需要修改表中所有字段。

查询和过滤

在 Table 对象上使用 select 操作符查询需要获取的指定字段,也可以使用 filter 或 where 方法过滤字段和检索条件,将需要的数据检索出来。

object TableAPITest { 
	def main(args: Array[String]): Unit = { 
		val streamEnv: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment 
		streamEnv.setParallelism(1) 
		//初始化Table API的上下文环境 
		val tableEvn =StreamTableEnvironment.create(streamEnv) 
		
		//导入隐式转换,建议写在这里,可以防止IDEA代码提示出错的问 
		import org.apache.flink.streaming.api.scala._ 
		import org.apache.flink.table.api.scala._ 
		val data = streamEnv.socketTextStream("hadoop101",8888) 
			.map(line=>{ 
				var arr =line.split(",") 
				new StationLog(arr(0).trim,arr(1).trim,arr(2).trim,arr(3).trim,arr(4).trim.toLong,arr(5).trim.to Long) 
				}) 
		val table: Table = tableEvn.fromDataStream(data) 
		
		//查询 
		tableEvn.toAppendStream[Row](  //toAppendStream 是把Table对象转换成为DataStream对象
			table.select('sid,'callType as 'type,'callTime,'callOut)) 
				.print() 
		//过滤查询 
		tableEvn.toAppendStream[Row]( 
			table.filter('callType==="success") //filter 
			.where('callType==="success")) //where 
			.print() 
		tableEvn.execute("sql")
		}

分组聚合

例如:

val table: Table = tableEvn.fromDataStream(data) 
tableEvn.toRetractStream[Row]( 
	table.groupBy('sid).select('sid, 'sid.count as 'logCount)) 
	.filter(_._1==true) //返回的如果是true才是Insert的数据 
	.print()

在代码中可以看出,使用 toAppendStream 和 toRetractStream 方法将 Table 转换为 DataStream[T]数据集,T 可以是 Flink 自定义的数据格式类型 Row,也可以是用户指定的数 据 格 式 类 型 。 在 使 用 toRetractStream 方 法 时 , 返 回 的 数 据 类 型 结 果 为 DataStream[(Boolean,T)],Boolean 类型代表数据更新类型,True 对应 INSERT 操作更新的 数据,False 对应 DELETE 操作更新的数据。

UDF

用户可以在 Table API 中自定义函数类,常见的抽象类和接口是:

  • ScalarFunction
  • TableFunction
  • AggregateFunction
  • TableAggregateFunction

demo如下:

package com.flink.primary.TableAndSQL

import org.apache.flink.api.common.typeinfo.TypeInformation
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.table.api.{EnvironmentSettings, Types}
import org.apache.flink.table.api.scala.StreamTableEnvironment
import org.apache.flink.table.functions.TableFunction
import org.apache.flink.types.Row

/**
  * 使用table API实现word count
  * @author xjh 2020.5.31
  */
object UDFByWordCount {
  def main(args: Array[String]): Unit = {
    //使用Flink原生的代码创建TableEnvironment
    val fsSettings = EnvironmentSettings.newInstance().useOldPlanner().inStreamingMode().build()
    val fsEnv = StreamExecutionEnvironment.getExecutionEnvironment
    val fsTableEnv = StreamTableEnvironment.create(fsEnv, fsSettings)

    import org.apache.flink.streaming.api.scala._
    import org.apache.flink.table.api.scala._
    //读取数据源
    val stream = fsEnv.socketTextStream("m1", 8888)
    val table = fsTableEnv.fromDataStream(stream,'line)

    //使用Table API切割单词,洗定义一个切割单词的函数
    val my_func = new MyFlatMapFunction
    val result=table.flatMap(my_func('line)).as('word,'word_c)
      .groupBy('word)
      .select('word,'word_c.sum as 'c)

    fsTableEnv.toRetractStream[Row](result).filter(_._1==true).print()
    fsTableEnv.execute("table_api")
  }

  /**
    * 自定义UDF
    */
  class MyFlatMapFunction extends TableFunction[Row]{
    //定义函数处理之后的返回类型,输出单词和1
    override def getResultType: TypeInformation[Row] =Types.ROW(Types.STRING(),Types.INT())

    //函数主体
    def eval(str:String):Unit={
      str.trim.split(" ").foreach(word=>{
        var row=new Row(2)
        row.setField(0,word)
        row.setField(1,1)
        collect(row)
      })
    }
  }
}

Window

Flink 支持 ProcessTime、EventTime 和 IngestionTime 三种时间概念,针对每种时间 概念,Flink Table API 中使用 Schema 中单独的字段来表示时间属性,当时间字段被指定 后,就可以在基于时间的操作算子中使用相应的时间属性。
在 Table API 中通过使用.rowtime 来定义 EventTime 字段,在 ProcessTime 时间字段 名后使用.proctime 后缀来指定 ProcessTime 时间属性

demo:统计最近 5 秒钟,每个基站的呼叫数量

object TableAPITest {
	 def main(args: Array[String]): Unit = { 
	 	val streamEnv: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment 
	 	//指定EventTime为时间语义 
	 	streamEnv.setStreamTimeCharacteristic(TimeCharacteristic.EventTime) 
	 	streamEnv.setParallelism(1) 
		
		//初始化Table API的上下文环境 
		val tableEvn =StreamTableEnvironment.create(streamEnv) 
		//导入隐式转换 
		import org.apache.flink.streaming.api.scala._ 
		import org.apache.flink.table.api.scala._ 
		
		val data = streamEnv.socketTextStream("hadoop101",8888) 
			.map(line=>{ 
				var arr =line.split(",") 
				new StationLog(arr(0).trim,arr(1).trim,arr(2).trim,arr(3).trim,arr(4).trim.toLong,arr(5).trim.to Long) 
			}) .assignTimestampsAndWatermarks( 
			//引入Watermark 
				new BoundedOutOfOrdernessTimestampExtractor[StationLog](Time.seconds(2)){//延迟2 秒 
					override def extractTimestamp(element: StationLog) = { element.callTime } 
				}) 
				
				//设置时间属性 
				val table: Table = tableEvn.fromDataStream(data,'sid,'callOut,'callIn,'callType,'callTime.rowtime) 
				//滚动Window ,第一种写法 
				val result: Table = table.window(Tumble over 5.second on 'callTime as 'window) 
				//第二种写法 
				val result: Table = table.window(Tumble.over("5.second").on("callTime").as("window"))
					 .groupBy('window, 'sid) 
					 .select('sid, 'window.start, 'window.end, 'window.rowtime, 'sid.count) 
			 //打印结果 
			 tableEvn.toRetractStream[Row](result) .filter(_._1==true) .print() 
			 tableEvn.execute("sql") 
			 }
	 }

你可能感兴趣的:(大数据,Flink)