Flink-Table API 和 Flink SQL簡介 | 新老版本Flink批流處理對比 | 讀取文件和Kafka消費數據 | API 和 SQL查詢表

GitHub

https://github.com/SmallScorpion/flink-tutorial.git

Table API 和 Flink SQL 是什麼

  1. Flink 對批處理和流處理,提供了統一的上層 API
  2. Table API 是一套內嵌在 Java 和 Scala 語言中的查詢API,它允許以非常直觀的方式組合來自一些關係運算符的查詢
  3. Flink 的 SQL 支持基於實現了 SQL 標準的 Apache Calcite

在這裏插入圖片描述

基本程序結構

Table API 和 SQL 的程序結構,與流式處理的程序結構十分類似

在這裏插入圖片描述

POM

<dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-table-planner_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-csv</artifactId>
            <version>1.10.0</version>
        </dependency>

簡單實例

import com.atguigu.bean.SensorReading
// 隱式轉換
import org.apache.flink.streaming.api.scala._
import org.apache.flink.table.api.Table
// 隱式轉換
import org.apache.flink.table.api.scala._

object Example {
  def main(args: Array[String]): Unit = {

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

    val inputDStream: DataStream[String] = env.readTextFile("D:\\MyWork\\WorkSpaceIDEA\\flink-tutorial\\src\\main\\resources\\SensorReading.txt")

    val dataDstream: DataStream[SensorReading] = inputDStream.map(
      data => {
        val dataArray: Array[String] = data.split(",")
        SensorReading(dataArray(0), dataArray(1).toLong, dataArray(2).toDouble)
      })

    // 1. 基於env創建表環境
    val tableEnv: StreamTableEnvironment = StreamTableEnvironment.create(env)

    // 2. 基於tableEnv 將流轉換成表
    val dataTable: Table = tableEnv.fromDataStream(dataDstream)

    // 3. 只輸出id爲sensor_1的id和溫度值
    // 3.1 調用table api,做轉換操作
    val resultTable: Table = dataTable
      .select("id, temperature")
      .filter("id == 'sensor_1'")

    // 3.2 直接調用SQL - 寫sql實現轉換
    tableEnv.registerTable("dataTable", dataTable) // 註冊表
    val resultSqlTable: Table = tableEnv.sqlQuery(
      """
        |select
        |   id, temperature
        |from
        |   dataTable
        |where
        |   id = 'sensor_1'
        |""".stripMargin
    )

    // 4. 將錶轉換成流操作
    resultTable.toAppendStream[ (String, Double) ].print("table")
    resultSqlTable.toAppendStream[ (String, Double) ].print( " sql " )

    env.execute("table test job")

  }
}

在這裏插入圖片描述

新老版本Flink批流處理對比

TableEnvironment 是 flink 中集成 Table API 和 SQL 的核心概念,所有對錶的操作都基於 TableEnvironment

  1. 註冊catalog
  2. 在內部 catalog 中註冊表
  3. 執行 SQL 查詢
  4. 註冊用戶自定義函數
  5. 將 DataStream 或 DataSet 轉換爲表
  6. 保存對 ExecutionEnvironment 或 StreamExecutionEnvironment 的引用
import com.atguigu.bean.SensorReading
import org.apache.flink.api.scala.ExecutionEnvironment
import org.apache.flink.streaming.api.scala._
import org.apache.flink.table.api.{EnvironmentSettings, TableEnvironment}
import org.apache.flink.table.api.scala._

object OldAndNewInStreamComparisonBatchTest {
  def main(args: Array[String]): Unit = {

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

    // 創建表執行環境
    // val TableEnv: StreamTableEnvironment = StreamTableEnvironment.create(env)


    // --------------    Old -> 老版本批流處理得方式    ---------------


    // 老版本planner的流式查詢
    val oldStreamSettings: EnvironmentSettings = EnvironmentSettings
      .newInstance() // 創建實例  -> return new Builder()
      .useOldPlanner() // 用老版本得Planner
      .inStreamingMode() //流處理模式
      .build()
    // 創建老版本planner的表執行環境
    val oldStreamTableEnv: StreamTableEnvironment = StreamTableEnvironment.create(env, oldStreamSettings)

    // 老版本得批處理查詢
    val oldBatchEnv: ExecutionEnvironment = ExecutionEnvironment.getExecutionEnvironment
    val oldBatchTableEnv: BatchTableEnvironment = BatchTableEnvironment.create(oldBatchEnv)



    // -------------------------------    new -> 新版本批流處理得方式    -------------------------------



    // 新版本planner的流式查詢
    val newStreamSettings: EnvironmentSettings = EnvironmentSettings
      .newInstance()
      .useBlinkPlanner() // 用新版本得Blink得Planner(添加pom依賴)
      .inStreamingMode() // 流處理
      .build()

    val newStreamTableEnv: StreamTableEnvironment = StreamTableEnvironment.create(env, newStreamSettings)


    // 新版本得批處理查詢
    val newBatchSettings: EnvironmentSettings = EnvironmentSettings
      .newInstance()
      .useBlinkPlanner()
      .inBatchMode() // 批處理
      .build()

    val newBatchTableEnv: TableEnvironment = TableEnvironment.create( newBatchSettings )



    // 數據讀入並轉換
    val inputDStream: DataStream[String] = env.readTextFile("D:\\MyWork\\WorkSpaceIDEA\\flink-tutorial\\src\\main\\resources\\SensorReading.txt")
    val dataDStream: DataStream[SensorReading] = inputDStream.map(
      data => {
        val dataArray: Array[String] = data.split(",")
        SensorReading(dataArray(0), dataArray(1).toLong, dataArray(2).toDouble)
      }
    )

    
    env.execute(" table test jobs " )
  }
}

在這裏插入圖片描述

運行報錯

Static methods in interface require -target:jvm-1.8
在這裏插入圖片描述

表(Table)

  1. TableEnvironment 可以註冊目錄 Catalog,並可以基於 Catalog 註冊表
  2. 表(Table)是由一個“標識符”(identifier)來指定的,由3部分組成:Catalog名、數據庫(database)名和對象名
  3. 表可以是常規的,也可以是虛擬的(視圖,View)
  4. 常規表(Table)一般可以用來描述外部數據,比如文件、數據庫表或消息隊列的數據,也可以直接從 DataStream轉換而來
  5. 視圖(View)可以從現有的表中創建,通常是 table API 或者 SQL 查詢的一個結果集

連接外部系統

官網:https://ci.apache.org/projects/flink/flink-docs-release-1.10/dev/table/connect.html

連接文件數據

連接外部系統在Catalog中註冊表,直接調用tableEnv.connect()就可以,裏面參數要傳入一個ConnectorDescriptor,也就是connector描述器。對於文件系統的connector而言,flink內部已經提供了,就叫做FileSystem()

定義表得數據來源,和外部建立連接
在這裏插入圖片描述

這是舊版本的csv格式描述器。由於它是非標的,跟外部系統對接並不通用,所以將被棄用,以後會被一個符合RFC-4180標準的新format描述器取代。新的描述器就叫Csv(),但flink沒有直接提供,需要引入依賴flink-csv

定義從外部文件讀取數據之後的格式化方法
在這裏插入圖片描述

定義表結構

在這裏插入圖片描述

import org.apache.flink.streaming.api.scala._
import org.apache.flink.table.api.{DataTypes, Table}
import org.apache.flink.table.api.scala._
import org.apache.flink.table.descriptors.{FileSystem, OldCsv, Schema}

object CreateTableTest {
  def main(args: Array[String]): Unit = {

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

    // 創建表環境
    val tableEnv: StreamTableEnvironment = StreamTableEnvironment.create(env)

    // --------------------- 讀取文件數據 ---------------------------
    val filePath = "D:\\MyWork\\WorkSpaceIDEA\\flink-tutorial\\src\\main\\resources\\SensorReading.txt"

    tableEnv.connect( new FileSystem().path(filePath) ) // 定義表的數據來源,和外部系統建立連接
      .withFormat( new OldCsv() ) // 定義從外部文件讀取數據之後的格式化方法
      .withSchema( new Schema() // 定義表結構
          .field("id", DataTypes.STRING())
          .field("timestamp", DataTypes.BIGINT())
          .field("temperature", DataTypes.DOUBLE())
      )
      .createTemporaryTable( "inputTable" ) // 在表環境中註冊一張表(創建)


    // 測試輸出
    val sensorTable: Table = tableEnv.from( "inputTable" )
    val resultTable: Table = sensorTable
        .select('id, 'temperature) // 查詢id和temperature字段
        .filter('id === "sensor_1") // 輸出sensor_1得數據
    resultTable.toAppendStream[ (String, Double) ].print( "FileSystem" )


    env.execute(" table connect fileSystem test job")
  }
}

在這裏插入圖片描述

連接Kafka 消費Kafka得數據

kafka的連接器flink-kafka-connector中,1.10版本的已經提供了Table API的支持。我們可以在 connect方法中直接傳入一個叫做Kafka的類,這就是kafka連接器的描述器ConnectorDescriptor。

import org.apache.flink.streaming.api.scala._
import org.apache.flink.table.api.{DataTypes, Table}
import org.apache.flink.table.api.scala._
import org.apache.flink.table.descriptors.{Csv, FileSystem, Kafka, OldCsv, Schema}

object CreateTableFromKafkaTest {
  def main(args: Array[String]): Unit = {

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

    // 創建表環境
    val tableEnv: StreamTableEnvironment = StreamTableEnvironment.create(env)

    // --------------------- 消費Kafka數據 ---------------------------

    tableEnv.connect( new Kafka()
        .version( "0.11" ) // 版本
        .topic( "sensor" ) // 主題
        .property("zookeeper.connect", "hadoop102:2181")
        .property("bootstrap.servers", "hadoop102:9092")
    )
      .withFormat( new Csv() ) // 新版本得Csv
      .withSchema( new Schema()
        .field("id", DataTypes.STRING())
        .field("timestamp", DataTypes.BIGINT())
        .field("temperature", DataTypes.DOUBLE())
      )


    // 測試輸出
    val sensorTable: Table = tableEnv.from( "inputTable" )
    val resultTable: Table = sensorTable
      .select('id, 'temperature) // 查詢id和temperature字段
      .filter('id === "sensor_1") // 輸出sensor_1得數據
    resultTable.toAppendStream[ (String, Double) ].print( "FileSystem" )


    env.execute(" table connect fileSystem test job")
  }
}

在這裏插入圖片描述

表的查詢

利用外部系統的連接器connector,我們可以讀寫數據,並在環境的Catalog中註冊表。接下來就可以對錶做查詢轉換了。
Flink給我們提供了兩種查詢方式:Table API和 SQL。

表的查詢 – Table API

這裏Table API裏指定的字段,前面加了一個單引號’,這是Table API中定義的Expression類型的寫法,可以很方便地表示一個表中的字段。
字段可以直接全部用雙引號引起來,也可以用半邊單引號+字段名的方式。以後的代碼中,一般都用後一種形式。

import org.apache.flink.streaming.api.scala._
import org.apache.flink.table.api.{DataTypes, Table}
import org.apache.flink.table.api.scala._
import org.apache.flink.table.descriptors.{FileSystem, OldCsv, Schema}

object QueryTableAPITest {
  def main(args: Array[String]): Unit = {

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

    // 創建表環境
    val tableEnv: StreamTableEnvironment = StreamTableEnvironment.create(env)

    // --------------------- 讀取文件數據 ---------------------------
    val filePath = "D:\\MyWork\\WorkSpaceIDEA\\flink-tutorial\\src\\main\\resources\\SensorReading.txt"

    tableEnv.connect( new FileSystem().path(filePath) ) // 定義表的數據來源,和外部系統建立連接
      .withFormat( new OldCsv() ) // 定義從外部文件讀取數據之後的格式化方法
      .withSchema( new Schema() // 定義表結構
        .field("id", DataTypes.STRING())
        .field("timestamp", DataTypes.BIGINT())
        .field("temperature", DataTypes.DOUBLE())
      )
      .createTemporaryTable( "inputTable" ) // 在表環境中註冊一張表(創建)


    // ----------------------- 表得查詢 ---------------------

    val sensorTable: Table = tableEnv.from( "inputTable" )

    // 1. Table API的調用

    // 簡單查詢
    val resultTable: Table = sensorTable
      .select('id, 'temperature) // 查詢id和temperature字段
      .filter('id === "sensor_1") // 輸出sensor_1得數據

    // 聚合查詢
    val aggResultTable: Table = sensorTable
        .groupBy('id)
        .select('id, 'id.count as 'count)


    // 測試輸出
    resultTable.toAppendStream[ (String, Double) ].print( "easy" )
    aggResultTable.toAppendStream[ (String, Int) ].print( "agg" )

    env.execute(" tableAPI query test job")
  }
}

在這裏插入圖片描述

這裏在測試得時候發現會報錯一個並不是簡單得插入追加操作,這裏指得是,在讀取數據得時候,如果做聚合操作,相當於把前面得數據做了更改或者覆蓋操作,Flink不允許聚合查詢出現這種。如果是簡單查詢,來一條數據根據條件就會輸出一條,但是聚合查詢會如首先出現sensor_1得數據,當第二條seneor_1得數據來時做聚合操作,將要更改原有得數據,不然如果後面得數據在累加,前面得數據還存在,聚合操作得意義不大
這裏得更改意見是 修改成toRetractStream()

在這裏插入圖片描述

表的查詢 – SQL

  1. Flink 的 SQL 集成,基於實現 了SQL 標準的 Apache Calcite
  2. 在 Flink 中,用常規字符串來定義 SQL 查詢語句
  3. SQL 查詢的結果,也是一個新的 Table
import org.apache.flink.streaming.api.scala._
import org.apache.flink.table.api.scala._
import org.apache.flink.table.api.{DataTypes, Table}
import org.apache.flink.table.descriptors.{Csv, FileSystem, OldCsv, Schema}

object QueryTableSQLTest {
  def main(args: Array[String]): Unit = {

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

    // 創建表環境
    val tableEnv: StreamTableEnvironment = StreamTableEnvironment.create(env)

    // --------------------- 讀取文件數據 ---------------------------
    val filePath = "D:\\MyWork\\WorkSpaceIDEA\\flink-tutorial\\src\\main\\resources\\SensorReading.txt"

    tableEnv.connect( new FileSystem().path(filePath) ) // 定義表的數據來源,和外部系統建立連接
      .withFormat( new Csv() ) // 定義從外部文件讀取數據之後的格式化方法
      .withSchema( new Schema() // 定義表結構
        .field("id", DataTypes.STRING())
        .field("timestamp", DataTypes.BIGINT())
        .field("temperature", DataTypes.DOUBLE())
      )
      .createTemporaryTable( "inputTable" ) // 在表環境中註冊一張表(創建)


    // ----------------------- 表得查詢 ---------------------

    val sensorTable: Table = tableEnv.from( "inputTable" )

    tableEnv.registerTable("sensorTable", sensorTable) // 註冊表
    val resultSqlTable: Table = tableEnv.sqlQuery(
      """
        |select
        |   id, temperature
        |from
        |   sensorTable
        |where
        |   id = 'sensor_1'
        |""".stripMargin
    )

    val aggResultSqlTable: Table = tableEnv
      .sqlQuery("select id, count(id) as cnt from sensorTable group by id")


    // 測試輸出
    resultSqlTable.toAppendStream[ (String, Double) ].print( "easy " )
    aggResultSqlTable.toRetractStream[ (String, Long) ].print( "agg" )

    env.execute(" tableSQL query test job")
  }
}

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章