입문에서 실제 향기로 Flink (22. 기본의 마지막 부분, 다양한 UDF 기능)

Flink Table API 및 SQL은 데이터 변환을위한 내장 함수를 일괄 제공하며, 이는 일상적인 개발 프로세스에서 가장 일반적으로 사용되는 가장 중요한 지점이기도합니다.

SQL은 많은 기능을 지원하고, 테이블 API 및 SQL이 구현되었으며, 일반적으로 사용되는 기본 사항을 완전히 다루었습니다. 일반적으로 메서드를 직접 작성할 필요가 없습니다.

像sql里面比较用的: =,   <>,  >,  >=, <=,is,is not,BETWEEN,EXISTS,IN等等这种操作符基本都覆盖

逻辑类的: or,and,is FALSE
计算类的: +,-,*,/,POWER,ABS,
字符类的: || ,upper,lower,LTRIM
聚合类的: count(*),count(1),avg,sum,max,min,rank

가장 완전한 공식 웹 사이트가 나열되어 있으므로 직접 사용할 수 있습니다 : https://ci.apache.org/projects/flink/flink-docs-stable/zh/dev/table/functions/systemFunctions.html

그러나 일부 특수한 시나리오에서는 이러한 내장 함수가 요구 사항을 충족하지 못할 수 있습니다. 현재로서는 직접 작성해야 할 수도 있습니다. 이때 Flink는 사용자 정의 함수 (UDF)를 제공합니다.

사용자 정의 함수 ((UDF)

사용자 정의 함수 (사용자 정의 함수, udf)는
대부분의 경우 쿼리 표현 기능을 크게 확장하는 중요한 기능입니다 . 사용자 정의 함수는 registerFunction
을 호출하여 사용자가 쿼리에 사용하기 전에 등록해야합니다. () 메소드는 TableEnvironment에 등록되어 있습니다. 사용자 정의 함수가 등록되면 TableEnvironment의 함수 카탈로그에 삽입되어 Table API 또는 SQL 파서가 올바르게 인식하고 해석 할 수 있도록합니다.
Flink는 3 가지 내장 함수를 제공 합니다.

스칼라 함수

하나 이상의 필드를 전달하고 값을 반환합니다. 매핑 작업
사용자 정의 스칼라 함수 와 유사하게 0, 1 개 이상의 스칼라 값을 새 스칼라 값
매핑 하여 조직에 있어야하는 스칼라 함수를 정의 할 수 있습니다. apache.flink.table.functions에서 기본 클래스 Scalar 함수를 확장하고 하나 이상의 평가 (eval) 메서드를 구현합니다.

각각 tableapi 및 sql로 구현 된 Chestnut 구현

package com.mafei.udftest

import com.mafei.sinktest.SensorReadingTest5
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.scala._
import org.apache.flink.table.api.EnvironmentSettings
import org.apache.flink.table.api.scala._
import org.apache.flink.table.functions.ScalarFunction
import org.apache.flink.types.Row

object ScalarFunctionTest {

  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1) //设置1个并发

    //设置处理时间为流处理的时间
    env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)

    //    val inputStream = env.readTextFile("/opt/java2020_study/maven/flink1/src/main/resources/sensor.txt")
    val inputStream = env.readTextFile("D:\\java2020_study\\maven\\flink1\\src\\main\\resources\\sensor.txt")
    //先转换成样例类类型
    val dataStream = inputStream
      .map(data => {
        val arr = data.split(",") //按照,分割数据,获取结果
        SensorReadingTest5(arr(0), arr(1).toLong, arr(2).toDouble) //生成一个传感器类的数据,参数中传toLong和toDouble是因为默认分割后是字符串类别
      })

    //设置环境信息(可以不用)
    val settings = EnvironmentSettings.newInstance()
      .useBlinkPlanner() // Flink 10的时候默认是用的useOldPlanner 11就改为了BlinkPlanner
      .inStreamingMode()
      .build()

    // 设置flink table运行环境
    val tableEnv = StreamTableEnvironment.create(env, settings)

    //流转换成表
    val sensorTables = tableEnv.fromDataStream(dataStream, 'id,'timestamp, 'temperature, 'tp.proctime as 'ts)

    //如果要看效果,可以直接打印出来
//    sensorTables.toAppendStream[Row].print("sensorTables: ")

    //调用自定义UDF函数,对id进行hash运算
    //1. table api实现
    // 首先需要new一个实例
    val hashCode = new HashCode(1)

    val resultTable = sensorTables
      .select('id,'ts,hashCode('id))

//    resultTable.toAppendStream[Row].print("resultTable: ")
    /**输出效果:
     * resultTable: > sensor1,2020-12-13T13:53:57.630,1980364880
        resultTable: > sensor2,2020-12-13T13:53:57.632,1980364881
        resultTable: > sensor3,2020-12-13T13:53:57.632,1980364882
        resultTable: > sensor4,2020-12-13T13:53:57.632,1980364883
        resultTable: > sensor4,2020-12-13T13:53:57.632,1980364883
        resultTable: > sensor4,2020-12-13T13:53:57.633,1980364883
     */

    //2. 用sql来实现,需要先在环境中注册好udf函数

    tableEnv.createTemporaryView("sensor",sensorTables)
    tableEnv.registerFunction("hashCode", hashCode)
    val sqlResultTable = tableEnv.sqlQuery("select id, ts, hashCode(id) from sensor")

    sqlResultTable.toRetractStream[Row].print("sqlResultTable")

    env.execute()

  }

}

//自定义一个标量函数
class HashCode(factor: Int) extends  ScalarFunction{
  def eval(s :String): Int={
    s.hashCode * factor - 11111
  }
}

코드 구조 및 운영 효과

입문에서 실제 향기로 Flink (22. 기본의 마지막 부분, 다양한 UDF 기능)

테이블 기능

스칼라 함수가 한 줄을 입력하고 한 값을 출력하는 경우 테이블 함수는 한 줄을 입력하고 출력은 프로필 함수와 유사한 일대 다 테이블을 가져옵니다.

자, tableapi와 sql을 사용하여

package com.mafei.udftest

import com.mafei.sinktest.SensorReadingTest5
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.scala._
import org.apache.flink.table.api.EnvironmentSettings
import org.apache.flink.table.api.scala._
import org.apache.flink.table.functions.{ScalarFunction, TableFunction}
import org.apache.flink.types.Row

object TableFunctionTest {

  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1) //设置1个并发

    //设置处理时间为流处理的时间
    env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)

    //    val inputStream = env.readTextFile("/opt/java2020_study/maven/flink1/src/main/resources/sensor.txt")
    val inputStream = env.readTextFile("D:\\java2020_study\\maven\\flink1\\src\\main\\resources\\sensor.txt")
    //先转换成样例类类型
    val dataStream = inputStream
      .map(data => {
        val arr = data.split(",") //按照,分割数据,获取结果
        SensorReadingTest5(arr(0), arr(1).toLong, arr(2).toDouble) //生成一个传感器类的数据,参数中传toLong和toDouble是因为默认分割后是字符串类别
      })

    //设置环境信息(可以不用)
    val settings = EnvironmentSettings.newInstance()
      .useBlinkPlanner() // Flink 10的时候默认是用的useOldPlanner 11就改为了BlinkPlanner
      .inStreamingMode()
      .build()

    // 设置flink table运行环境
    val tableEnv = StreamTableEnvironment.create(env, settings)

    //流转换成表
    val sensorTables = tableEnv.fromDataStream(dataStream, 'id,'timestamp, 'temperature, 'tp.proctime as 'ts)

    //如果要看效果,可以直接打印出来
//    sensorTables.toAppendStream[Row].print("sensorTables: ")

    //调用自定义UDF函数,先实例化,定义以_为分隔符
    val split = new Split("_")
    val resultTable = sensorTables
      .joinLateral(split('id) as ('word, 'length)) //做个关联,以id作为key,拿到1个元组,定义为world和length名字
      .select('id,'ts,'word,'length)

//    resultTable.toRetractStream[Row].print("resultTable")
    /**  输出效果:
     * resultTable> (true,sensor1,2020-12-13T14:43:01.121,sensor1,7)
        resultTable> (true,sensor2,2020-12-13T14:43:01.124,sensor2,7)
        resultTable> (true,sensor3,2020-12-13T14:43:01.125,sensor3,7)
        resultTable> (true,sensor4,2020-12-13T14:43:01.125,sensor4,7)
        resultTable> (true,sensor4,2020-12-13T14:43:01.125,sensor4,7)
        resultTable> (true,sensor4,2020-12-13T14:43:01.126,sensor4,7)

     */

    //2. 用sql实现
    tableEnv.createTemporaryView("sensor", sensorTables)
    tableEnv.registerFunction("split", split)
    val sqlResultTables = tableEnv.sqlQuery(
      """
        |select
        |id,ts,word,length
        |from sensor,lateral table( split(id)) as splitid(word,length)
        |""".stripMargin)

    sqlResultTables.toRetractStream[Row].print("sqlResultTables")

    env.execute()

  }

}

//自定义一个UDF函数
//定义以传入的字符串作为分隔符,定义输出一个元祖,String和Int
class Split(separator: String) extends TableFunction[(String,Int)]{

  def eval(str:String):Unit={
    str.split(separator).foreach(
      wold => collect((wold, wold.length))
    )
  }
}

코드 구조 및 작동 효과 :
입문에서 실제 향기로 Flink (22. 기본의 마지막 부분, 다양한 UDF 기능)

집계 함수

사용자 정의 집계 함수 (UDAGG)는 테이블의 데이터를 스칼라 값으로 집계 할 수 있습니다.

예를 들어 모든 센서와 각 센서의 평균 값을 계산하려면 tableapi 및 sql을 사용하여이를 달성하고 새 AggregateFunctionTest.scala를 만듭니다.

package com.mafei.udftest

import com.mafei.sinktest.SensorReadingTest5
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.scala._
import org.apache.flink.table.api.EnvironmentSettings
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) //设置1个并发

    //设置处理时间为流处理的时间
    env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)

    //    val inputStream = env.readTextFile("/opt/java2020_study/maven/flink1/src/main/resources/sensor.txt")
    val inputStream = env.readTextFile("D:\\java2020_study\\maven\\flink1\\src\\main\\resources\\sensor.txt")
    //先转换成样例类类型
    val dataStream = inputStream
      .map(data => {
        val arr = data.split(",") //按照,分割数据,获取结果
        SensorReadingTest5(arr(0), arr(1).toLong, arr(2).toDouble) //生成一个传感器类的数据,参数中传toLong和toDouble是因为默认分割后是字符串类别
      })

    //设置环境信息(可以不用)
    val settings = EnvironmentSettings.newInstance()
      .useBlinkPlanner() // Flink 10的时候默认是用的useOldPlanner 11就改为了BlinkPlanner
      .inStreamingMode()
      .build()

    // 设置flink table运行环境
    val tableEnv = StreamTableEnvironment.create(env, settings)

    //流转换成表
    val sensorTables = tableEnv.fromDataStream(dataStream, 'id,'timestamp, 'temperature, 'tp.proctime as 'ts)

    //table api实现:
    val avgTemp = new AggTemp()
    val resultTable = sensorTables
      .groupBy('id)
      .aggregate(avgTemp('temperature) as 'tempAvg)
      .select('id,'tempAvg)

    resultTable.toRetractStream[Row].print("resultTable")

    //sql实现

    //注册表
    tableEnv.createTemporaryView("sensor", sensorTables)

    //注册函数
    tableEnv.registerFunction("avgTemp", avgTemp)
    val sqlResult = tableEnv.sqlQuery(
      """
        |select id,avgTemp(temperature) as tempAvg
        |from sensor
        |group by id
        |""".stripMargin
    )

    sqlResult.toRetractStream[Row].print("sqlResult")

    env.execute()
  }

}

//定义一个类,存储聚合状态,如果不设置,在AggregateFunction 传入的第二个值就是(Double, Int)   温度的总数和温度的数量
class AggTempAcc{
  var sum: Double = 0.0
  var count: Int = 0
}
//自定义一个聚合函数,求每个传感器的平均温度值,保存状态(tempSum,tempCount)
//传入的第一个Double是最终的返回值,这里求的是平均值,所以是Double
//第二个传入的是中间状态存储的值,需要求平均值,那就需要保存所有温度加起来的总温度和温度的数量(多少个),那就是(Double,Int)
// 如果不传AggTempAcc ,那就传入(Double,Int)一样的效果
class AggTemp extends AggregateFunction[Double,AggTempAcc]{
  override def getValue(acc: AggTempAcc): Double = acc.sum / acc.count

//  override def createAccumulator(): (Double, Int) = (0.0,0)
  override def createAccumulator(): AggTempAcc = new AggTempAcc

  //还要实现一个具体的处理计算函数, accumulate(父方法),具体计算的逻辑,
  def accumulate(acc:AggTempAcc, temp:Double): Unit={
    acc.sum += temp
    acc.count += 1
  }

}

입문에서 실제 향기로 Flink (22. 기본의 마지막 부분, 다양한 UDF 기능)

# 表 집계 함수 (테이블 집계 함수)

사용자 정의 테이블 집계 함수 (사용자 정의 테이블 집계 함수 UDTAGG)는 테이블의 데이터를 여러 행과 여러 열이있는 결과 테이블로 집계 할 수 있습니다.
사용자 정의 테이블 집계 함수는 TablAggregateFunction 추상 클래스를 상속하여 구현 된
입력입니다. 그리고 출력은 테이블이며, 애플리케이션 시나리오는 여러 행의 값이 출력되는 top10 등과 같은 시나리오에서 사용할 수 있습니다.
입문에서 실제 향기로 Flink (22. 기본의 마지막 부분, 다양한 UDF 기능)

AggregationFunction이 구현해야하는 메서드 :
---- createAccumulator
()
---- accumlate () ---- emitValue ()

TableAggregateFunction의 작동 원리 :

  • 첫째, 집계의 중간 결과를 보유하는 데이터 구조 인 누산기 (Accumulator)도 필요합니다. createAccumulator () 메서드를 호출하여 빈 누산기를 만들 수 있습니다.
  • 그 후, 누산기를 업데이트하기 위해 각 입력 행에 대해 함수의 accumlate () 메서드가 호출됩니다.
  • 모든 행을 처리 한 후 함수의 emitValue () 메서드를 호출하여 최종 결과를 계산하고 반환합니다.

예를 들어 테이블 집계 함수를 사용하여 모든 센서에 대한 상위 n 개 시나리오를 구현합니다.

package com.mafei.udftest

import com.mafei.sinktest.SensorReadingTest5
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.scala._
import org.apache.flink.table.api.EnvironmentSettings
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 TableAggregateFunctionTest {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    env.setParallelism(1) //设置1个并发

    //设置处理时间为流处理的时间
    env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)

        val inputStream = env.readTextFile("/opt/java2020_study/maven/flink1/src/main/resources/sensor.txt")
//    val inputStream = env.readTextFile("D:\\java2020_study\\maven\\flink1\\src\\main\\resources\\sensor.txt")
    //先转换成样例类类型
    val dataStream = inputStream
      .map(data => {
        val arr = data.split(",") //按照,分割数据,获取结果
        SensorReadingTest5(arr(0), arr(1).toLong, arr(2).toDouble) //生成一个传感器类的数据,参数中传toLong和toDouble是因为默认分割后是字符串类别
      })

    //设置环境信息(可以不用)
    val settings = EnvironmentSettings.newInstance()
      .useBlinkPlanner() // Flink 10的时候默认是用的useOldPlanner 11就改为了BlinkPlanner
      .inStreamingMode()
      .build()

    // 设置flink table运行环境
    val tableEnv = StreamTableEnvironment.create(env, settings)

    //流转换成表
    val sensorTables = tableEnv.fromDataStream(dataStream, 'id,'timestamp, 'temperature, 'tp.proctime as 'ts)

    //1、使用table api方式实现
    val top2Temp = new Top2Temp()
    val resultTable = sensorTables
      .groupBy('id)
      .flatAggregate(top2Temp('temperature) as ('temp, 'rank))
      .select('id,'temp,'rank)

//    resultTable.toAppendStream[Row].print()   //表聚合中间有更改,所以不能直接用toAppendStream

    resultTable.toRetractStream[Row].print("table aggregate")
    /**
     * 输出效果:
     * (true,sensor1,1.0,1)
        (true,sensor1,-1.7976931348623157E308,2)
        (true,sensor2,42.0,1)
        (true,sensor2,-1.7976931348623157E308,2)
        (true,sensor3,43.0,1)
        (true,sensor3,-1.7976931348623157E308,2)
        (true,sensor4,40.1,1)
        (true,sensor4,-1.7976931348623157E308,2)
        (false,sensor4,40.1,1)
        (false,sensor4,-1.7976931348623157E308,2)
        (true,sensor4,40.1,1)
        (true,sensor4,20.0,2)
        (false,sensor4,40.1,1)
        (false,sensor4,20.0,2)
        (true,sensor4,40.2,1)
        (true,sensor4,40.1,2)
     */

    env.execute("表聚合函数-取每个传感器top2")

  }

}

//定义要输出的结构
class Top2TempAcc{
  var highestTemp: Double = Double.MinValue
  var secondHighestTemp: Double = Double.MinValue
}

// 自定义表聚合函数,提取所有温度值中最高的两个温度,输出(temp,rank)
class Top2Temp extends TableAggregateFunction[(Double,Int),Top2TempAcc]{
  override def createAccumulator(): Top2TempAcc = new Top2TempAcc()

  //实现计算聚合结果的函数 accumulate
  // 第一个参数是 accumulate,第二个是当前做聚合传入的参数是什么,这里只需要把温度传入就可以(Double)
  def accumulate(acc: Top2TempAcc, temp : Double): Unit={
    // 要判断当前温度值,是否比状态中保存的温度值大
    //第一步先判断温度是不是比最大的都大
    if(temp > acc.highestTemp){
      //如果比最高温度还高,那排在第一,原来的第一高移动到第二高
      acc.secondHighestTemp = acc.highestTemp
      acc.highestTemp = temp
    }
    else if(temp > acc.secondHighestTemp){
      //这种是比最高的小,比第二高的大,那就直接把第二高换成当前温度值
      acc.secondHighestTemp = temp
    }

  }

  //再实现一个输出结果的方法,最终处理完表中所有数据时调用
  def emitValue(acc: Top2TempAcc,out: Collector[(Double, Int)]): Unit ={
    out.collect((acc.highestTemp,1))
    out.collect((acc.secondHighestTemp,2))
  }
}

sensor.txt内容:
sensor1,1603766281,1
sensor2,1603766282,42
sensor3,1603766283,43
sensor4,1603766240,40.1
sensor4,1603766284,20
sensor4,1603766249,40.2

코드 구조 및 실행 효과 다이어그램 :

입문에서 실제 향기로 Flink (22. 기본의 마지막 부분, 다양한 UDF 기능)

추천

출처blog.51cto.com/mapengfei/2572888