flink(9) Table API 和Flink SQL

什么时Table API 和Flink SQL

Flink对批处理和流处理,提供了统一的上层API

Flink本身时批流同一的处理框架,所以Table API和SQL就是批流统一的上层处理API

TableApI是一套内嵌在java和scala语言中的查询API,它允许以非常直观的方式组合来自一些关系运算符的查询

Flink的SQL支持基于实现了SQL标准的Apache Calcite

两种planner(old&blink)的区别

1.批流统一:Blink将批处理作业,视为流式处理的特殊情况。所以,blink不支持表和DataSet之间的转换,批处理作业将不转换为DataSet应用程序;而是跟流处理一样,转换为dataStream程序来处理。

2.因为批流统一,Blink planner也不支持BatchTableSource,而使用有界的StreamTableSource代替。

3.Blink planner只支持全新的目录,不支持已启用的ExternalCatalog。

4.旧planner和Blink planner的FilterableTableSource实现不兼容。旧的planner会把plannerExpressions下推到filterableTableSource中,而blink planner则会把Expressions下推。

5.基于字符串的键值配置选项仅适用于Blink planner。

6.PannerConfig在两个planner中的实现不同。

7.Blinkplanner会将多个sink优化在同一个DAG中(仅在TableEnvironment上受支持,而在StreamTableEnvironment上不受支持)而旧planner的优化总是将每一个sink放在一个新的DAG中,其中所有DAG彼此独立

8.旧的palnner不支持目录统计,而Blik planner支持。

 

    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1);

    //读取数据创建DataStream
    val inputStream : DataStream[String] = env.readTextFile("/Users/oumanabuka/Desktop/cem_flink_test/src/main/resources/word.txt")
    val dataStream:DataStream[SensorReading]=inputStream
      .map(data=>{
        val dataArray = data.split(",")
        SensorReading(dataArray(0),dataArray(1).toLong,dataArray(2).toDouble)
      })

    //创建表执行环境
    val tableEnv:StreamTableEnvironment = StreamTableEnvironment.create(env)

    //基于数据流,转换成一张表,然后进行操作
    val dataTable:Table = tableEnv.fromDataStream(dataStream)

    //调用table Api 得到转换结果
    val resultTable :Table = dataTable
        .select("id, temperature")
        .filter("id == 'sensor1'")

    //或者直接写sql得到转换结果
    //因为table没有在sql注册表 可以采用字符串+dataTable的形式 进行类似注册
    val resultSqlTable :Table = tableEnv.sqlQuery(
      """
        |select
        |id ,temperature
        |from
        |""".stripMargin
    +dataTable+
        """
          |where id='sensor1'
          |""".stripMargin
    )
    //转换回数据流,打印输出
//    val resultStream: DataStream[(String,Double)] = resultTable.toAppendStream
val resultStream: DataStream[(String,Double)] = resultSqlTable.toAppendStream
    resultStream.print()
    resultTable.printSchema()
    env.execute("table example job")

基本程序结构

Table API和SQL的程序结构,与流失处理的程序结构十分类似

 //创建表执行环境
  val tableEnv:StreamTableEnvironment = StreamTableEnvironment.create(env)
  //创建一张表 用于读取数据 source
  tableEnv.connect(..).createTemporaryTable("inputTable")
  //创建一张表 用于输出数据 sink
  tableEnv.connect(..).createTemporaryTable("outputTable")

  //通过Table Api 或sql 进行计算
  val result = tableEnv.from("inputTable").select(....)
  val sqlResult= tableEnv.sqlQuery("select .....")
  
  sqlResult.insertInto("outputTable")

创建表的执行环境 需要将flink流处理的执行环境传入

 val tableEnv:StreamTableEnvironment = StreamTableEnvironment.create(env)
TableEnvironment是flink中集成Table APi 和SQL的核心概念,所有对表的操作都基于TableEnvironment

注册Catlog(类似表目录)

在Catlog中注册表

执行SQL查询

注册用户自定义函数(UDF)

 

创建各版本执行环境

 //创建老版本的流式查询环境
    val settings :EnvironmentSettings = EnvironmentSettings.newInstance()
      .useOldPlanner()
      .inStreamingMode()
      .build()

    val tableEnvironment :StreamTableEnvironment = StreamTableEnvironment.create(env,settings)

    //创建老版本的批式查询环境
    val batchEnv:ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
    val batchTableEnvironment:BatchTableEnvironment = BatchTableEnvironment.create(batchEnv)

    //创建blink版本的流式查询环境(blink版本已经批流统一了)
    val blSettings = EnvironmentSettings.newInstance()
      .useBlinkPlanner()
      .inStreamingMode()
      .build()
    val blTableEnv = StreamTableEnvironment.create(env,blSettings)

    //创建blink版本的批式查询环境(blink版本已经批流统一了)
    val bsSettings = EnvironmentSettings.newInstance()
      .useBlinkPlanner()
      .inBatchMode()
      .build()
    val bbTableEnv = TableEnvironment.create(bsSettings)

TableEnvironment可以注册目录Catalog,并可以基于Catalog注册表

表(Table)是由一个“标识符”(identifier)来指定的 由3部分组成:

Catalog名 数据库名 和对象名

表可以是常规的,也可以是虚拟的(视图View)

常规表(Table)一般可以用来描述外部数据,比如文件,数据库表或消息队列的数据,也可以直接从DataStream转换而来

视图(View)可以从现有的表中创建,通常是tableAPI或者SQL查询的一个结果集

创建表

TableEnvironment可以调用.connect()方法,连接外部系统,并调用.createTemporaryTable()(1.10才有这个方法  1.9 用的是 registerTableSource 或者registerTableSink 注册)方法,在Catalog中注册表

通过withFormat()方法 定义数据格式化 withSchema()定义表结构

 

分别连接文件系统 和 kafka

 //连接到文件系统(CSV)
    val filePath="/Users/oumanabuka/Desktop/cem_flink_test/src/main/resources/word.txt"

    val inputTable = tableEnv.connect(new FileSystem().path(filePath))
      .withFormat(new OldCsv().fieldDelimiter(",")
        .field("id", Types.STRING())
        .field("temperature", Types.LONG())
        .field("timestamp", Types.DOUBLE())) //定义读取数据之后的格式化方法
      .withSchema(new Schema()
        .field("id", Types.STRING())
        .field("temperature", Types.LONG())
        .field("timestamp", Types.DOUBLE())
      )
      .registerTableSource("test")

    //转换成流打印输出

    val sensorTable :Table = tableEnv.sqlQuery(
      """
        |select
        |*
        |from
        |test
        |""".stripMargin)

    sensorTable.toAppendStream[(String,Long,Double)].print()

    //连接到kafka
    val prop = new Properties()
    prop.setProperty("bootstrap.servers", "127.0.0.1:9092")
    prop.setProperty("group.id", "expand_group")
    //    prop.put("enable.auto.commit", "false");
    //        props.put("auto.offset.reset", "none");
    //    prop.put("auto.offset.reset", "earliest"); //当旧数据已经过期,不知道新数据从何处开始时使用
    prop.put("session.timeout.ms", "30000");
    prop.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
    prop.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer")


    tableEnv.connect(new Kafka()
        .version("0.11")
        .topic("sensor")
        .properties(prop)
    ).withFormat(new OldCsv().fieldDelimiter(",")
      .field("id", Types.STRING())
      .field("temperature", Types.LONG())
      .field("timestamp", Types.DOUBLE())) //定义读取数据之后的格式化方法
      .withSchema(new Schema()
        .field("id", Types.STRING())
        .field("temperature", Types.LONG())
        .field("timestamp", Types.DOUBLE())
      )
      .registerTableSource("test_kafka")
    val sensorKafkaTable :Table = tableEnv.sqlQuery(
      """
        |select
        |*
        |from
        |test_kafka
        |""".stripMargin)

    sensorKafkaTable.toAppendStream[(String,Long,Double)].print()

表的输出

表的输出是通过将数据 写入tablesink来实现的

tablesink是一个通用接口,可以支持不同的文件格式,存储数据库和消息队列

输出表最直接的方法,就是通过Table.Insertinto()方法将一个Table写入注册过的TableSink中

 

注意 聚合操作的结果不能输出到文件 原因是 涉及到一个叫做更新模式的概念

也就是可以简单的理解 没有触发更新操作的 才能输出到文件

 

更新模式

对于流式查询,需要声明如何在表和外部连接器之间执行转换

与外部系统交换的消息类型,由更新模式(Update Mode)指定

1.追加(Append)模式

表只做插入操作,和外部连接器只交换插入(insert)消息 

2.撤回(Retract)模式

表和外部连接器交换添加(Add)和撤回(Retract)消息 (即来了同key的需要改变 聚合值时先删除 之前的结果再查询新的结果) 

插入操作(insert)编码为Add消息;删除(Delete)编码为Retract消息 更新(Update)编码为上一条的Retract和下一条的Add消息

更新插入(Upsert)模式

更新和插入都被编码为Upsert消息;删除编码为Delete消息(来了新的数据就直接在原key上 进行更新 而不是撤回再插入)

 

使用方式 在connect之后  inUpsertMode 但是 文件 kafka 都不支持 所以说 转成datastream 更加合适一点

 

 

连接到mysql 有一点不同 就是 flink并没有 jdbc连接器 需要通过 ddl的方式创建连接

val sinkDDL :String ="""

|create table jdbcOutputTable(

id varchar(20) not null ,

|cnt bigint not null

|) with (

|'connector.type'='jdbc',

|'connector.url'=://localhost:3306/test',

|'connector.table'='sesor_count',

|'connector.driver'='com.mysql.jdbc.Drivver',

|'connector.username'='root',

|'connector.password'='123456'

|)

""".stripMargin

tableEnv.sqlUpdate(sinkDDL)

aggResultSqlTable.insertinto("jdbcOutputTable")

 

将Table转换成DataStream 

表可以转换为DataStream或DataSet,这样自定义流处理或批处理程序就可以继续在Table API

或sql查询 的结果上运行了

将表转换为DataStrem时或DataSet时,需要指定生成的数据,即要将表的每一行转换成的数据类型

表作为流式查询的结果,是动态更新的

转换有两种转换模式:追加(Appends)模式和撤回(Retract)模式

 

 查看执行计划

Table ApI提供了一种机制来解释表的罗杰和优化查询计划

查看执行计划,可以通过TableEnvironment.EXPLAIN(TABLE)方法或TableEnvironment.explain()方法完成,返回一个字符串,描述三个计划

优化的逻辑查询计划

优化后的逻辑查询计划

实际执行计划

val explainnation:String = tableEnv.explain(resultTable)

println(explaintion)

 

flink 满足 流数据提供表查询 提出的概念

动态表(Dynamic Tables)

动态表是Flink对流数据的Table Api 和SQL 支持的核心概念

与表示批处理数据的静态表不同,动态表是随事件变化的

持续查询

动态表可以向静态的批处理表一样进行查询,查询一个动态表会产生持续查询

连续查询永远不会终止,并会生成另一个动态表

 

 

流式表查询的处理过程:

1.流被转换为动态表

2.对动态表计算连续查询,生成新的动态表

3.生成的动态表被转换回流

 

为了处理带有关系查询的流,必须先将其转换为表

从概念上讲,流的每个数据记录,都被解释为对结果表的插入(Insert)修改操作

 

Table Api 时间特性(Time Attributes)

基于时间的操作(比如Table API和SQL中窗口操作) 需要定义相关的时间语义和时间数据来源的信息

Table可以提供一个逻辑上的时间字段,用于在表处理程序中,指示时间和访问相应的时间戳

时间属性,可以是每个表schema的一部分。一旦定义了时间属性,它就可以作为一个字段引用,并且可以在基于时间的操作中使用

时间属性的行为类似于常规时间戳,可以访问,并且进行计算

定义处理时间(processingtime)

在定义schema期间,使用.proctime指定字段名定义处理时间字段

这个proctime属性只能通过附加逻辑字段,来扩展物理schema

val sensorTable = tableEnv.fromDataStream(dataStream,'id,'temperature,'timestamp,'pt.proctime) 
//不使用Datastream 创建时 也可以在 wirhSchema时 指定 
.filed("pt",Types.TIMEStamp(3)).proctime
//创建表的DDL时 直接 加一个字段(Blink支持)
pt AS PROCTIME()

 

定义事件时间(Event time)

//先指定是事件事件
env.setStreamTimeCharacterstic(TimeCharacterstic.EventTime)

//再在datastream设置好watermark 并指定以哪个字段为事件时间 和之前是一样的
.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor[sesorReading](Time.seconds(1)){
override def extractTimestamp(element:SensorReading):Long = element.timestamp*1000L
})

val sensorTable = tableEnv.fromDataStream(dataStream,'id,'temperature,'timestamp.rowtime as 'ts) 



//不适用dataStream 也可以定义TableSchame时指定 并指定watermark 
.withschame(new Schema()
.field("id",Types.STRING())
.field("timestamp",Types.BIGINT())
.rowtime(
    new Rowtime()
        .timestampsFromField("timestamp")
        .watermarksPeriodicBounded(1000)
)
    .field("temperature",DataTYpes.DOUBLE)
)

ddl中使用
rt as TO_TOMESTAMP(FROm_UNIXTIME(ex) ),
watermark for rt as rt - interval '1' second

  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值