GitHub
https://github.com/SmallScorpion/flink-tutorial.git
Table API 和 Flink SQL 是什麼
- Flink 對批處理和流處理,提供了統一的上層 API
- Table API 是一套內嵌在 Java 和 Scala 語言中的查詢API,它允許以非常直觀的方式組合來自一些關係運算符的查詢
- 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
- 註冊catalog
- 在內部 catalog 中註冊表
- 執行 SQL 查詢
- 註冊用戶自定義函數
- 將 DataStream 或 DataSet 轉換爲表
- 保存對 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)
- TableEnvironment 可以註冊目錄 Catalog,並可以基於 Catalog 註冊表
- 表(Table)是由一個“標識符”(identifier)來指定的,由3部分組成:Catalog名、數據庫(database)名和對象名
- 表可以是常規的,也可以是虛擬的(視圖,View)
- 常規表(Table)一般可以用來描述外部數據,比如文件、數據庫表或消息隊列的數據,也可以直接從 DataStream轉換而來
- 視圖(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
- Flink 的 SQL 集成,基於實現 了SQL 標準的 Apache Calcite
- 在 Flink 中,用常規字符串來定義 SQL 查詢語句
- 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")
}
}