让人
- 动态表
- 动态表和持续查询
- 时间特性
- 处理时间
- 事件时间
- 窗口
- Group Windows
- 滚动窗口
- 滑动窗口
- 会话窗口
- 代码中的应用
- Group Windows细节
- GroupwindowTable
- Over Windoweds
- 函数
- 内置函数
- 自定义udf函数
- 标量函数
- 表函数
- 聚合函数
- 表聚合函数
把table转换成为datastream的时候,做了哪些操作?
可以吧datastream API和tableAPI集合起来一起用。甚至可以一段代码的处理程序里面,前面做datastream的转换,转换成某个datastream的时候,然后再转换成表,然后调用table API做一些操作。哪种方式更容易实现需求就用哪种方式。
需要注意的是,表转换成流的时候,没有更新模式。流输出了,不可能在输出的结果基础上再修改。流之前的数据已经输出到了下游,已经溜过去了。所以upsert模式在流里面没哟办法直接做转换。
追加模式:流中数据一条一条的插入到表中。
撤回模式,追加了一个撤回的信息,并不是真正的撤回流中数据,流就像水,流出去就是收不回。
动态表
动态表是随着时间变化的。随着时间的到来而更新变化。 相当于在处理快照,不停的对快照进行做查询。
动态表和持续查询
持续查询,state储存的是之前流数据中的聚合值,而不是之前大量的数据。后来的流中来了之后,再与之进行聚合。
外部系统支持这个模式,我们才可以指定这个模式。
时间特性
处理时间
流转换成表的时候,定义时间特性,这种用的最广泛。
主要转换代码
//创建流执行环境。
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
//默认是处理时间,但是习惯性的写一下,事件时间的话就是需要设置。 env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)
//创建流表环境
val tableEnv = StreamTableEnvironment.create(env)
// 从文件中读取数据。
val inpath = "D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"
val inputStream: DataStream[String] = env.readTextFile(inpath)
val dataStream: DataStream[SensorReading_timeAndWindowTest] = inputStream.map(data => {
val strings = data.split(",")
SensorReading_timeAndWindowTest(strings(0), strings(1).toLong, strings(2).toDouble) //方法的返回值
})
val SensorTable = tableEnv.fromDataStream(dataStream,'id,'temperature,'timestamp,'pt.proctime)
这种就是得用blinplanner,引入的计算列的概念,计算出流中没有的列,并且添加进去。
事件时间
窗口
Group Windows
滚动窗口
参数是Tumble这个类,因为里面只有over这个方法,所以点省略了。同理后面的点也省略了。
滑动窗口
参数是Slide这个类,因为里面只有over这个方法,所以点省略了。同理后面的点也省略了。
会话窗口
代码中的应用
Group Windows细节
GroupwindowTable
点进去
Window方法有两个,先点进去看GroupWindowtTable的这个类。
点进去发现,里面只有一个同名的方法,也就是只能调用这个方法。
点进去这个类中。
点击ctrl +F12,就可以发现做很多操作,如下的方法。
关于window里面的参数
直接传这个类,这个类本身是一个final class
里面只有一个方法。over方法,类型是TumbleWithSize。
点进去,然后看见as方法。
继续点进去。这次终于继承了Groupwindow
代码
package com.atguigu.tableApiTests
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.{DataStream, _}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.table.api.Tumble
import org.apache.flink.table.api.scala._
import org.apache.flink.table.sources.wmstrategies.BoundedOutOfOrderTimestamps
import org.apache.flink.types.Row
case class SensorReading_timeAndWindowTest (id :String, timestamp: Long, temperature:Double)
object timeAndWindowTest {
def main(args: Array[String]): Unit = {
//创建流执行环境。
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
//创建流表环境
val tableEnv = StreamTableEnvironment.create(env)
// 从文件中读取数据。
val inpath = "D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"
val inputStream: DataStream[String] = env.readTextFile(inpath)
val dataStream: DataStream[SensorReading_timeAndWindowTest] = inputStream.map(data => {
val strings = data.split(",")
SensorReading_timeAndWindowTest(strings(0), strings(1).toLong, strings(2).toDouble) //方法的返回值
}).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[SensorReading_timeAndWindowTest](Time.seconds(1)) {
override def extractTimestamp(element: SensorReading_timeAndWindowTest): Long = element.timestamp *1000
} )
//将流中数据转换为---流表
val SensorTable = tableEnv.fromDataStream(dataStream,'id,'temperature,'timestamp.rowtime as 'ts) //起个别名,SQL中timestamp是关键字,防止冲突。
// 1、group window
// 1-1 table api
val resultTable = SensorTable
//每10秒统计一次滚动时间窗口
.window(Tumble.over(10.seconds).on('ts) as 'tw )
.groupBy('id, 'tw) //id为key,还有分组的窗口,作为分组条件。
.select('id, 'id.count, 'temperature.avg,'tw.end) //前面是常规的聚合操作,最后一个是调用当前窗口的结束时间,也就是关窗时间,也就是赋予了一些窗口的信息。
// 1-2 SQL实现 用SQL的话,需要在环境中注册一下这个流表
tableEnv.createTemporaryView("sensor",SensorTable)
val resultSqlTable = tableEnv.sqlQuery(
"""
| select
| id,
| count(id),
| avg(temperature),
| tumble_end(ts,interval '10' second)
| from
| sensor
| group by
| id,
| tumble(ts,interval '10' second)
|
|
|""".stripMargin
) //窗口的分组,因为是两个函数,tumble、tumble_end,没有办法起别名。
//转换成流打印输出
//如果没有处理迟到数据的话,那么就是不会撤回,只有追加操作,只输出一次,也就是不会撤回,用追加流和撤回流都是可以的。
resultSqlTable.toAppendStream[Row].print("sql")
resultTable.toAppendStream[Row].print("table")
env.execute("12345")
}
}
Over Windoweds
和hive中的开窗函数是差不多的。
Groupwindow: 开了一个窗口,属于当前窗口的所有数据都放进去。都在里面做统计,最后每个窗口输出的是一个结果。
OverWindowedTable:每一行数据都要计算和他相邻的行去做聚合,然后得到一个结果。
tableAPI中的用法
SQL中的用法
代码
package com.atguigu.tableApiTests
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.{DataStream, _}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.table.api.{Over, Tumble}
import org.apache.flink.table.api.scala._
import org.apache.flink.table.sources.wmstrategies.BoundedOutOfOrderTimestamps
import org.apache.flink.types.Row
case class SensorReading_timeAndWindowTest (id :String, timestamp: Long, temperature:Double)
object timeAndWindowTest {
def main(args: Array[String]): Unit = {
//创建流执行环境。
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
//创建流表环境
val tableEnv = StreamTableEnvironment.create(env)
// 从文件中读取数据。
val inpath = "D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"
val inputStream: DataStream[String] = env.readTextFile(inpath)
val dataStream: DataStream[SensorReading_timeAndWindowTest] = inputStream.map(data => {
val strings = data.split(",")
SensorReading_timeAndWindowTest(strings(0), strings(1).toLong, strings(2).toDouble) //方法的返回值
}).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[SensorReading_timeAndWindowTest](Time.seconds(1)) {
override def extractTimestamp(element: SensorReading_timeAndWindowTest): Long = element.timestamp *1000
} )
//将流中数据转换为---流表
val SensorTable = tableEnv.fromDataStream(dataStream,'id,'temperature,'timestamp.rowtime as 'ts) //起个别名,SQL中timestamp是关键字,防止冲突。
// 1、group window
// 1-1 table api
val resultTable = SensorTable
//每10秒统计一次滚动时间窗口
.window(Tumble.over(10.seconds).on('ts) as 'tw )
.groupBy('id, 'tw) //id为key,还有分组的窗口,作为分组条件。
.select('id, 'id.count, 'temperature.avg,'tw.end) //前面是常规的聚合操作,最后一个是调用当前窗口的结束时间,也就是关窗时间,也就是赋予了一些窗口的信息。
// 1-2 SQL实现 用SQL的话,需要在环境中注册一下这个流表
tableEnv.createTemporaryView("sensor",SensorTable)
val resultSqlTable = tableEnv.sqlQuery(
"""
| select
| id,
| count(id),
| avg(temperature),
| tumble_end(ts,interval '10' second)
| from
| sensor
| group by
| id,
| tumble(ts,interval '10' second)
|
|
|""".stripMargin
) //窗口的分组,因为是两个函数,tumble、tumble_end,没有办法起别名。
//转换成流打印输出
//如果没有处理迟到数据的话,那么就是不会撤回,只有追加操作,只输出一次,也就是不会撤回,用追加流和撤回流都是可以的。
resultSqlTable.toAppendStream[Row].print("sql")
resultTable.toAppendStream[Row].print("table")
// 2、over window 需求统计: 统计每个sensor每条数据,与之前的两行的平均温度。
// 2-1 table api
val overResultTable = SensorTable
.window(Over.partitionBy('id).orderBy('ts).preceding(2.rows) as 'ow )
.select('id, 'ts, 'id.count.over('ow), 'temperature.avg.over('ow) )
// 2-2 SQL实现
//上面环境中注册过了,直接用注册好的sensor
val overResultSqlTable = tableEnv.sqlQuery(
"""
|select
| id,
| ts,
| count(id) over ow,
| avg(temperature) over ow
|from
| sensor
|window ow as(
| partition by id
| order by ts
| rows between 2 preceding and current row
| )
|""".stripMargin)
overResultTable.toAppendStream[Row].print("overTable")
overResultSqlTable.toAppendStream[Row].print("overSql")
// //看整体的结构
// SensorTable.printSchema()
//
// SensorTable.toAppendStream[Row].print()
env.execute("12345")
}
}
函数
内置函数
函数在不断的更新中,有的话直接用。
sum0,是对于那种,没值的话,给一个默认的返回值0。
自定义udf函数
标量函数
简单的可以认为是map操作。
里面的方法必须叫eval,必须是公开的。不能是private。
代码
package com.atguigu.tableApiTests
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.table.api.scala._
import org.apache.flink.table.functions.ScalarFunction
import org.apache.flink.types.Row
case class SensorReading (id :String, timestamp: Long, temperature:Double)
object udfTest {
def main(args: Array[String]): Unit = {
//创建流执行环境。
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
//创建流表环境
val tableEnv = StreamTableEnvironment.create(env)
// 从文件中读取数据。
val inpath = "D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"
val inputStream: DataStream[String] = env.readTextFile(inpath)
val dataStream: DataStream[SensorReading] = inputStream.map(data => {
val strings = data.split(",")
SensorReading(strings(0), strings(1).toLong, strings(2).toDouble) //方法的返回值
}).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.seconds(1)) {
override def extractTimestamp(element: SensorReading): Long = element.timestamp *1000
} )
//将流中数据转换为---流表
val SensorTable = tableEnv.fromDataStream(dataStream,'id,'temperature,'timestamp.rowtime as 'ts) //起个别名,SQL中timestamp是关键字,防止冲突。
//调用自定义hash函数,对id进行hash运算。
// 1、tableAPI
//首先new一个udf的实例
val hashCode = new HashCode(23) // hashCode是new的对象,API和SQL都需要。
val resultTable = SensorTable.select('id, 'ts, hashCode('id) )
//2、SQL
//需要在表环境中注册udf
tableEnv.createTemporaryView("sensor",SensorTable)
tableEnv.registerFunction("hashCode",hashCode) // hashCode是new的对象,API和SQL都需要。
val resultSqlTable = tableEnv.sqlQuery(
"""
|select
| id,
| ts,
| hashCode(id)
| from
| sensor
|""".stripMargin)
resultTable.toAppendStream[Row].print("overTable")
resultSqlTable.toAppendStream[Row].print("overSql")
env.execute("12345")
}
}
//自定义的hash函数
class HashCode(factor: Int) extends ScalarFunction{
def eval(s:String) :Int = {
s.hashCode * factor - 10000
}
}
表函数
输出是多个行,可以理解为一张表。类似于侧写函数。
方法名字为eval,返回值为unit类型。
输出的类型在继承的类TableFunction后面中写。
package com.atguigu.tableApiTests
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.{DataStream, _}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.table.api.scala._
import org.apache.flink.table.functions.TableFunction
import org.apache.flink.types.Row
case class SensorReading (id :String, timestamp: Long, temperature:Double)
object tableFunctionTest {
def main(args: Array[String]): Unit = {
//创建流执行环境。
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
//创建流表环境
val tableEnv = StreamTableEnvironment.create(env)
// 从文件中读取数据。
val inpath = "D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"
val inputStream: DataStream[String] = env.readTextFile(inpath)
val dataStream: DataStream[SensorReading] = inputStream.map(data => {
val strings = data.split(",")
SensorReading(strings(0), strings(1).toLong, strings(2).toDouble) //方法的返回值
}).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.seconds(1)) {
override def extractTimestamp(element: SensorReading): Long = element.timestamp *1000
} )
//将流中数据转换为---流表
val SensorTable = tableEnv.fromDataStream(dataStream,'id,'temperature,'timestamp.rowtime as 'ts) //起个别名,SQL中timestamp是关键字,防止冲突。
val split = new Split("_") // new一个udf实例,传入下划线作分隔符。
//1、tableAPI
//原始的table和扩展的table,做一个侧向的连接
//当前表中的每一行数据都要与 split的结果 进行一个连接操作,相当于自己和自己进行连接操作,相当于是一个笛卡尔积。
val ResultTable = SensorTable
.joinLateral(split('id) as ('word,'length))
.select('id,'ts,'word,'length)
//2、sql
tableEnv.createTemporaryView("sensor",SensorTable)
tableEnv.registerFunction("split",split)
val ResultSqlTable = tableEnv.sqlQuery(
"""
|select
| id,
| ts,
| word,
| length
|from
| sensor,lateral table( split(id) ) as splitid(word,length)
|""".stripMargin)
ResultTable.toAppendStream[Row].print("Table")
ResultSqlTable.toAppendStream[Row].print("Sql")
env.execute("12345")
}
}
// TableFunction[(String,Int)],里面的泛型是定义输出的类型
class Split(sep:String) extends TableFunction[(String,Int)]{
def eval(str:String) ={
str.split(sep).foreach(
word => collect((word,word.length))
)
}
}
聚合函数
package com.atguigu.tableApiTests
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.{DataStream, _}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.table.api.scala._
import org.apache.flink.table.functions.AggregateFunction
import org.apache.flink.types.Row
object aggregateFunctionTest {
def main(args: Array[String]): Unit = {
//创建流执行环境。
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
//创建流表环境
val tableEnv = StreamTableEnvironment.create(env)
// 从文件中读取数据。
val inpath = "D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"
val inputStream: DataStream[String] = env.readTextFile(inpath)
val dataStream: DataStream[SensorReading] = inputStream.map(data => {
val strings = data.split(",")
SensorReading(strings(0), strings(1).toLong, strings(2).toDouble) //方法的返回值
}).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.seconds(1)) {
override def extractTimestamp(element: SensorReading): Long = element.timestamp *1000
} )
//将流中数据转换为---流表
val SensorTable = tableEnv.fromDataStream(dataStream,'id,'temperature,'timestamp.rowtime as 'ts) //起个别名,SQL中timestamp是关键字,防止冲突。
//table api
val avgTemp = new avgTemperature()
val resultTable = SensorTable
.groupBy('id)
.aggregate(avgTemp('temperature) as 'avgtemp)
.select('id, 'avgtemp)
//SQL实现
tableEnv.createTemporaryView("sensor",SensorTable)
tableEnv.registerFunction("avgTemp",avgTemp)
val sqltable = tableEnv.sqlQuery(
"""
| select
| id ,avgTemp(temperature)
|from
| sensor
|group by id
|""".stripMargin)
resultTable.toRetractStream[Row].print("Table")
sqltable.toRetractStream[Row].print("Sql")
env.execute()
}
}
//定义一个类,专门表示聚合的状态。
class AvgTempAcc{
//这些变量是可变的,所以是用的是var而不是val。
var sum:Double =0.0
var count:Int =0
}
//自定义聚合函数,求每个传感器的平均温度值,保存状态(tempsum,tempcount),返回值为double型。
class avgTemperature extends AggregateFunction[Double,AvgTempAcc]{
override def getValue(accumulator: AvgTempAcc): Double = accumulator.sum /accumulator.count
//保存结构。
override def createAccumulator(): AvgTempAcc = new AvgTempAcc
//还要具体实现一个处理计算函数,accumulate,这个必须叫这个名字。
//聚合结果的变量也是定义好的,必须叫accumulator。第二个位置是聚合的值,也就是创建对象的时候,需要输入的参数位置。
//只是更新,不需要输出,所以返回值类型unit即可。
def accumulate(accumulator :AvgTempAcc, temp:Double)={
accumulator.sum += temp
accumulator.count+=1
}
}
表聚合函数
package com.atguigu.tableApiTests
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
import org.apache.flink.streaming.api.scala.{DataStream, _}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.table.api.scala._
import org.apache.flink.table.functions.TableAggregateFunction
import org.apache.flink.types.Row
import org.apache.flink.util.Collector
object tableAggregationFunctionTest {
def main(args: Array[String]): Unit = {
//创建流执行环境。
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(1)
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
//创建流表环境
val tableEnv = StreamTableEnvironment.create(env)
// 从文件中读取数据。
val inpath = "D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"
val inputStream: DataStream[String] = env.readTextFile(inpath)
val dataStream: DataStream[SensorReading] = inputStream.map(data => {
val strings = data.split(",")
SensorReading(strings(0), strings(1).toLong, strings(2).toDouble) //方法的返回值
}).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[SensorReading](Time.seconds(1)) {
override def extractTimestamp(element: SensorReading): Long = element.timestamp *1000
} )
//将流中数据转换为---流表
val SensorTable = tableEnv.fromDataStream(dataStream,'id,'temperature,'timestamp.rowtime as 'ts) //起个别名,SQL中timestamp是关键字,防止冲突。
//new 自定义的聚合函数
val top2temping = new top2Temps()
// table api
val resulttable = SensorTable
.groupBy('id)
.flatAggregate( top2temping('temperature) as ('temp,'rank) ) //聚合函数输出的结果起个别名
.select('id,'temp,'rank) //这里就用到上面的别名
resulttable.toRetractStream[Row].print("Table")
env.execute()
}
}
class top2Temp{
var highestTemp :Double = Double.MinValue
var secondHighTemp :Double = Double.MinValue
}
//自定义聚合函数,实现top n,这里提取所有 温度值的最高的两个温度。输出(temp,rank)
class top2Temps extends TableAggregateFunction[(Double,Int),top2Temp]{
override def createAccumulator(): top2Temp = new top2Temp()
//实现计算聚合结果的函数 accumulate
def accumulate(accumulator: top2Temp,temp:Double) ={
if(temp>accumulator.highestTemp) {
accumulator.secondHighTemp = accumulator.highestTemp
accumulator.highestTemp = temp
}else if(temp>accumulator.secondHighTemp){
//大于第二温度小于第一温度,直接替换第二温度。
accumulator.secondHighTemp = temp
}
}
//实现一个输出结果的方法,最终处理完表中所有数据的时候进行调用。
//collect输出,不需要返回值。
def emitValue(acc:top2Temp,out:Collector[(Double,Int)]) ={
out.collect((acc.highestTemp,1))
out.collect((acc.secondHighTemp,2))
}
}