table和API

  • table和API基本示例
  • 引入的依赖
  • 代码基本步骤
  • 代码演示
  • 基本程序结构
  • 表的概念
  • 表的创建
  • 从文件系统中创建表
  • 从Kafka系统中创建表
  • 表的查询
  • 使用table Api
  • 使用SQL
  • DataStream转换成为表
  • 创建临时视图
  • 输出表
  • 输出到文件
  • 输出外部数据库
  • 更新模式
  • 输出到kafka
  • 输出到Mysql


table和API基本示例

引入的依赖

planner计划。计划器,这是tableAPI中最主要的部分,提供一个运行时的环境。基于运行时的环境解析流式处理程序,生成一个表的执行计划,2.12是Scala的版本。1.10是flink的版本。

<dependency>

		<groupId>org.apache.flink</groupId>
		
		<artifactId>flink-table-planner_2.12</artifactId>
		
		<version>1.10.1</version>

</dependency>

bridge桥接器,语言的转换工具。2.12是Scala的版本。1.10是flink的版本。下面是Scala语言的转换器,如果用Java语言就用Java语言的转换器。

<dependency>

		<groupId>org.apache.flink</groupId>
		
		<artifactId>flink-table-api-scala-bridge_2.12</artifactId>
		
		<version>1.10.1</version>

</dependency>

代码基本步骤

1、创建流式处理环境,获取到数据,将数据转换成一定的格式的流数据。
2、创建表的处理环境,表处理环境从数据流中获取流数据,作为一张表。
3、Table的方式: 在这张表上进行做一些方法操作,得到最终想要的表。
4、sql的方式:在环境中注册这张表,然后写一个SQL作为一个字符串,然后进行执行。

需要注意的地方: 
 1、表环境的创建是基于流环境的创建的。流环境创建的时候,直接是流执行环境.获取执行环境。而表环境创建的时候,表执行环境.创建(流环境变量)。
 2、从流中获取的数据,创建的表,并没有在表环境中注册管理,因此需要注册创建一个视图。
3、print打印的时候,表转换成流的话可以使用print这个方法。

代码演示

package com.atguigu.apitest

import org.apache.flink.streaming.api.scala._
import org.apache.flink.table.api.Table
// scala语言用这个,Java对应的改成Java
import org.apache.flink.table.api.scala._
// import org.apache.flink.table.api.java.StreamTableEnvironment

case class SensorReadingTabletest (id :String , timestamp: Long ,tempreture:Double)

object tabletest {

  def main(args: Array[String]): Unit = {
    // 定义流式处理环境。
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment


    // 从文件中读取数据。
    val inpath = "D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"

    val inputStream: DataStream[String] = env.readTextFile(inpath)

    val dataStream: DataStream[SensorReadingTabletest] = inputStream.map(data => {
      val strings = data.split(",")
      SensorReadingTabletest(strings(0), strings(1).toLong, strings(2).toDouble) //方法的返回值
    })


    //创建表的执行环境。
    // 底层是流处理,基于流处理来创建当前的表环境。
    // scala语言用这个,Java对应的改成Java,注意导包。
    //import org.apache.flink.table.api.scala._

    val tabEnv: StreamTableEnvironment = StreamTableEnvironment.create(env)

    //基于流创建一张表
    // --也就是流中的数据放入到表中,从流中拿来数据作为一张表
    // dataTable现在是一张表,想象成SQL语句中的表。
    val dataTable: Table = tabEnv.fromDataStream(dataStream)


    // -- 用table实现,这种就是类比于在 SQLyoug中进行手动选择,最后展示出来什么样的效果。
    // 调用table API ,通过sql之后形成的新的表。
    val  resultTable  =  dataTable
      .select("id,tempreture")    //相当于SQL中的select,要选那些字段。
      .filter("id ='sensor_1'")  //过滤条件,相当于SQL中的where。

    //转换成流的话,就是可以进行print输出。
    resultTable.toAppendStream[(String,Double)].print("tab")


    // 如果用SQL写的话,还是有一点点的麻烦。
    // 1、对dataTable进行注册,因为dataTable就是直接从流中直接转换过来的。
    // 2、dataTable这张表是Table数据类型,并没有在当前的表环境中有catalog,并没有管理起来。
    // 3、如果想写SQL的话,必须是在目录里面有注册的,才可以直接在SQL里面直接调用这个表名字。


    tabEnv.createTemporaryView("dataTable",dataTable) //第一个是注册的视图名字,第二个是对哪个表进行注册。

    // 然后在SQL中可以直接用这个视图的名字。
    val sql :String ="select id,tempreture from dataTable where id  = 'sensor_1'"
    //基于环境,进行执行SQL。
    val resultSqlTable = tabEnv.sqlQuery(sql)

    //转换成流的话,就是可以进行print输出。
    resultSqlTable.toAppendStream[(String, Double)].print("sql")

    env.execute()

  }
}

基本程序结构

这里的connect是连接外部的数据系统,文件,kafka…。读入,写出。

这里是直接从外部系统读取数据,就可以直接在表环境中注册出来,注册的是表,不是视图。如果是从流中读取数据,那么就是注册的是视图。

1、 tableApi要用的话,必须先得到table数据类型,那怎么得到Table数据类型呢?用from方法,把当前表环境注册好的表读出来,得到一个Table类型的表。
2、SQL的话,直接用环境注册好的那张表。

flink 打印 Sink 日志 flink print_mapreduce

注意from后面加双引号。

flink 打印 Sink 日志 flink print_flink 打印 Sink 日志_02

表的概念

flink 打印 Sink 日志 flink print_spark_03

表的创建

flink 打印 Sink 日志 flink print_mapreduce_04

从文件系统中创建表

connect和createTemporaryTable中间点击with,只有.withSchema()、.withFormat(),这两个方法。

flink 打印 Sink 日志 flink print_大数据_05

代码

package com.atguigu.tableApiTests

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

object tableApiTest {

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

   //1、创建表执行环境,前提是有流式的执行环境
   val env = StreamExecutionEnvironment.getExecutionEnvironment
    val tabEnv = StreamTableEnvironment.create(env)


   //读取文件
    tabEnv.
      connect(new FileSystem().path("D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"))
      .withFormat(new OldCsv()) //因为外部文件是逗号分割的,所以是csv格式。
      //这里用的旧版的,新版的需要添加一个依赖。
      .withSchema(new Schema()
        .field("id",DataTypes.STRING())
        .field("timestamp",DataTypes.BIGINT())
        .field("tempreture",DataTypes.DOUBLE())

      )  // 指定当前表结构每一个字段是什么。

      .createTemporaryTable("table1")

    val table2 = tabEnv.from("table1")

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


   env.execute()

  }


}

flink 打印 Sink 日志 flink print_flink 打印 Sink 日志_06

从Kafka系统中创建表
<dependency>

		<groupId>org.apache.flink</groupId>
		
		<artifactId>flink-csv</artifactId>
		
		<version>1.10.1</version>

</dependency>

代码

package com.atguigu.tableApiTests

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

object tableApiTest {

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

   //1、创建表执行环境,前提是有流式的执行环境
   val env = StreamExecutionEnvironment.getExecutionEnvironment
    val tabEnv = StreamTableEnvironment.create(env)


    tabEnv.
      connect( new Kafka()
        .version("0.11") //kafka版本
        .topic("sensor") //主题
        .property("zookeeper.connect","hadoop102:2181") // zookpeer的端口号: 2181
        .property("bootstrap.servers","hadoop102:9092") // kafka的端口号
      )
      .withFormat(new Csv()) //文本格式的文件
      .withSchema(new Schema()
        .field("id",DataTypes.STRING())
        .field("timestamp",DataTypes.BIGINT())
        .field("tempreture",DataTypes.DOUBLE())

      )  // 指定当前表结构每一个字段是什么。

      .createTemporaryTable("table1")


    val table2 = tabEnv.from("table1")

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

   env.execute()
  }
}

表的查询

使用table Api

flink 打印 Sink 日志 flink print_flink_07

使用SQL

flink 打印 Sink 日志 flink print_flink_08

DataStream转换成为表

flink 打印 Sink 日志 flink print_spark_09

基于名称的这种,可以和流中的字段的顺序不一致。

flink 打印 Sink 日志 flink print_flink 打印 Sink 日志_10

创建临时视图

flink 打印 Sink 日志 flink print_大数据_11

输出表

连接到外部系统,然后在环境中注册一张表。

flink 打印 Sink 日志 flink print_spark_12

输出到文件

flink 打印 Sink 日志 flink print_大数据_13

输出到打印台上的测试代码:

package com.atguigu.tableApiTests

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 fileOutPutTest {

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

    //1、创建表执行环境,前提是有流式的执行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)
    val tabEnv = StreamTableEnvironment.create(env)


    //2、读取数据
    //读取文件
        tabEnv.
          connect(new FileSystem().path("D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"))
          .withFormat(new OldCsv()) //因为外部文件是逗号分割的,所以是csv格式。
          .withSchema(new Schema()
            .field("id",DataTypes.STRING())
            .field("timestamp",DataTypes.BIGINT())
            .field("temperature",DataTypes.DOUBLE())

          )  // 指定当前表结构每一个字段是什么。

          .createTemporaryTable("table1")


    //3、转换操作
    val table2 = tabEnv.from("table1")


    //简单转换
    val resultTable:Table  =  table2
                .select('id,'temperature) //用半个单引号',引起来字段。
                .filter('id==="sensor_1")// 用===进行筛选。


    //聚合转换
   val aggTable = resultTable
      .groupBy('id)
      .select('id, 'id .count as  'count)


    //聚合转换的话,不能有重复数据,所以之前在表中存在的,先撤回掉,更新后的放进去。
    aggTable.toRetractStream[(String,Long)].print()

    env.execute()
  }

}

flink 打印 Sink 日志 flink print_mapreduce_14


文本中有3条sensor_1,代码运行的结果:

true表示数据进入表中,false表示数据从表中撤回。因为我们没有办法改变已经输出的数据,因此前面加一个true或者false进行判断,是不是最新的数据。

flink 打印 Sink 日志 flink print_flink_15


输出到文件的代码:

文件系统-----只能追加,不能有撤回,更改的操作,也就是不支持聚合操作。写出到文件的数据,不可能撤回掉。

package com.atguigu.tableApiTests

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 fileOutPutTest {

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

    //1、创建表执行环境,前提是有流式的执行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1)
    val tabEnv = StreamTableEnvironment.create(env)


    //2、读取数据
    //读取文件
        tabEnv.
          connect(new FileSystem().path("D:\\code_put\\flink_tuturial\\src\\main\\resources\\Sensor.txt"))
          .withFormat(new OldCsv()) //因为外部文件是逗号分割的,所以是csv格式。
          .withSchema(new Schema()
            .field("id",DataTypes.STRING())
            .field("timestamp",DataTypes.BIGINT())
            .field("temperature",DataTypes.DOUBLE())

          )  // 指定当前表结构每一个字段是什么。

          .createTemporaryTable("table1")


    //3、转换操作
    val table2 = tabEnv.from("table1")


    //简单转换
    val resultTable:Table  =  table2
                .select('id,'temperature)
                .filter('id==="sensor_1")
    
    //输出到文件
    tabEnv.
      connect(new FileSystem().path("D:\\code_put\\flink_tuturial\\src\\main\\resources\\SensorOut1.txt"))
      .withFormat(new OldCsv()) //因为外部文件是逗号分割的,所以是csv格式。
      .withSchema(new Schema()
        .field("id",DataTypes.STRING())
        .field("temperature",DataTypes.DOUBLE())
      )  // 指定当前表结构每一个字段是什么。

      .createTemporaryTable("outputTable")


      // 输出到文件
    resultTable.insertInto("outputTable")
    
    env.execute()
  }

}

输出外部数据库

更新操作的表还要写到外部系统,那么就是写入到一些数据库里面,这些数据库支持实现其他的tablesink。

flink 打印 Sink 日志 flink print_大数据_16

更新模式

1、追加模式:只追加。
2、撤回模式:增(新增一条)、删(删除一条)、改(撤回一条,发出一条)。
3、upsert模式:(key,value)这种,插入的时候,没有发现某个key,那么插入该数据。更新的时候找到key,然后更新对应的value。

flink 打印 Sink 日志 flink print_flink 打印 Sink 日志_17

输出到kafka
//输入还是输出,并不是由连接器决定,而是由转换操作决定。
tabEnv.
  connect( new Kafka()
    .version("0.11") //kafka版本
    .topic("sensor") //主题
    .property("zookeeper.connect","hadoop102:2181") // zookpeer的端口号: 2181
    .property("bootstrap.servers","hadoop102:9092") // kafka的端口号
  )
  .withFormat(new Csv()) //文本格式的文件
  .withSchema(new Schema()
    .field("id",DataTypes.STRING())
    .field("timestamp",DataTypes.BIGINT())
    .field("tempreture",DataTypes.DOUBLE())

  )  // 指定当前表结构每一个字段是什么。

  .createTemporaryTable("kafka_table")

  resultTable.insertInto("kafka_table")

代码

package com.atguigu.tableApiTests

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

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

    //1、创建表执行环境,前提是有流式的执行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    val tabEnv = StreamTableEnvironment.create(env)


    //2、从kafka中读取数据。
    tabEnv.
      connect( new Kafka()
        .version("0.11") //kafka版本
        .topic("sensor") //主题
        .property("zookeeper.connect","hadoop102:2181") // zookpeer的端口号: 2181
        .property("bootstrap.servers","hadoop102:9092") // kafka的端口号
      )
      .withFormat(new Csv()) //文本格式的文件
      .withSchema(new Schema()
        .field("id",DataTypes.STRING())
        .field("timestamp",DataTypes.BIGINT())
        .field("temperature",DataTypes.DOUBLE())

      )  // 指定当前表结构每一个字段是什么。

      .createTemporaryTable("kafka_source")



    //3、转换操作
     val sensorTable = tabEnv.from("kafka_source")

    //简单转换
    val resultTable = sensorTable
      .select('id, 'temperature)
      .filter('id === "sensor_1")

    //4、输出
    //source和sink的区别就是: topic和临时表的名字不能一样,其他的地方不用改。

    tabEnv.
      connect( new Kafka()
        .version("0.11") //kafka版本
        .topic("sensor_out") //主题
        .property("zookeeper.connect","hadoop102:2181") // zookpeer的端口号: 2181
        .property("bootstrap.servers","hadoop102:9092") // kafka的端口号
      )
      .withFormat(new Csv()) //文本格式的文件
      .withSchema(new Schema()
        .field("id",DataTypes.STRING())
        .field("temperature",DataTypes.DOUBLE())

      )  // 指定当前表结构每一个字段是什么。

      .createTemporaryTable("kafka_sink")

    //将 resultTable表 插入到外部表 kafka_sink 中。
    resultTable.insertInto("kafka_sink")

    env.execute()
  }
}

启动生产者的kafka,然后启动消费者的kafka,启动代码。在生产者中生产数据,然后在消费者中可以看到数据。

输出到Mysql
<dependency>
                <groupId>org.apache.flink</groupId>
                <artifactId>flink-jdbc_2.12</artifactId>
                <version>1.10.1</version>
    </dependency>

并不是connect的方式,而是DDL的方式。

flink 打印 Sink 日志 flink print_大数据_18

/ 输出到 Mysql

val sinkDDL: String =

"""
|create table jdbcOutputTable (

| id varchar(20) not null,

| cnt bigint not null

|) with (

| 'connector.type' = 'jdbc',

| 'connector.url' = 'jdbc:mysql://hadoop102:3306/test',

| 'connector.table' = 'sensor_count',

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

| 'connector.username' = 'root',

| 'connector.password' = '123456'

|) """.stripMargin

tableEnv.sqlUpdate(sinkDDL) aggResultSqlTable.insertInto("jdbcOutputTable")
其中:
'connector.url' = 'jdbc:mysql://hadoop102:3306/test' ,表保存在MySQL的这个库中。
'connector.table' = 'sensor_count',这张表是真正在MySQL中保存的表,也就是最后写入到MySQL中,就是这张表。
jdbcOutputTable 是在表执行环境中创建的表。