Flink de l'entrée au vrai parfum (22. La dernière partie des bases, diverses fonctions UDF)

L'API Flink Table et SQL fournissent un lot de fonctions intégrées pour la conversion de données, qui sont également le point le plus couramment utilisé et le plus important dans le processus de développement quotidien

De nombreuses fonctions sont prises en charge dans SQL. L'API Table et SQL ont été implémentées. Les fonctions les plus couramment utilisées ont été entièrement couvertes. En règle générale, vous n'avez pas besoin d'écrire des méthodes vous-même.

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

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

Le site officiel le plus complet a été répertorié, vous pouvez l'utiliser directement: https://ci.apache.org/projects/flink/flink-docs-stable/zh/dev/table/functions/systemFunctions.html

Cependant, dans certains scénarios particuliers, ces fonctions intégrées peuvent ne pas répondre aux besoins. À ce stade, nous pouvons avoir besoin de l'écrire nous-mêmes. Pour le moment, Flink fournit des fonctions personnalisées (UDF)

Fonction définie par l'utilisateur ((UDF)

Les fonctions définies par l'utilisateur (fonctions définies par l'utilisateur, udf) sont une fonctionnalité importante, qui élargit considérablement la capacité d'exprimer des requêtes
dans la plupart des cas, les fonctions définies par l'utilisateur doivent s'enregistrer avant de pouvoir utiliser dans une requête
l'utilisateur en appelant registerFunction La méthode () est enregistrée dans TableEnvironment. Lorsqu'une fonction définie par l'utilisateur est enregistrée, elle est insérée dans le catalogue de fonctions de TableEnvironment, afin que l'API Table ou l'analyseur SQL puisse la reconnaître et l'interpréter correctement.
Flink fournit trois types de fonctions intégrées

Fonctions scalaires

Passez un ou plusieurs champs et renvoyez une valeur, similaire à la fonction
scalaire définie par l'utilisateur de l' opération de carte , vous pouvez mapper 0, 1 ou plusieurs valeurs scalaires à la nouvelle valeur scalaire
pour définir la fonction scalaire, qui doit être dans org. Étendez la classe de base Scalar Function dans apache.flink.table.functions et implémentez (une ou plusieurs) méthodes d'évaluation (eval)

Implémentation Chestnut, implémentée avec tableapi et sql respectivement

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
  }
}

Structure du code et effet de l'opération

Flink de l'entrée au vrai parfum (22. La dernière partie des bases, diverses fonctions UDF)

Fonctions de table

Si la fonction scalaire doit entrer une ligne et produire une valeur, alors la fonction de table doit entrer une ligne, et la sortie obtient une table, un à plusieurs, similaire à la fonction de profil

Allez, utilisez tableapi et sql pour réaliser

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))
    )
  }
}

Structure du code et effet de l'opération:
Flink de l'entrée au vrai parfum (22. La dernière partie des bases, diverses fonctions UDF)

Fonctions d'agrégation

Les fonctions d'agrégation définies par l'utilisateur (UDAGG) peuvent agréger les données d'une table en une valeur scalaire

Par exemple, pour calculer la valeur moyenne de tous les capteurs et de chaque capteur, utilisez tableapi et sql pour y parvenir et créez un nouveau 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 de l'entrée au vrai parfum (22. La dernière partie des bases, diverses fonctions UDF)

# 表 Fonctions d'agrégation (fonctions d'agrégation de table)

Les fonctions d'agrégation de tables définies par l'utilisateur (UDTAGGs User-Defined Table Aggregate Functions) peuvent agréger les données d'une table dans une table de résultats avec plusieurs lignes et plusieurs colonnes.
Les fonctions d'agrégation de tables définies par l'utilisateur sont implémentées en
entrée en héritant de la classe abstraite TablAggregateFunction Et la sortie est une table, le scénario d'application peut être utilisé dans des scénarios tels que top10, etc., où plusieurs lignes de valeurs doivent être sorties
Flink de l'entrée au vrai parfum (22. La dernière partie des bases, diverses fonctions UDF)

Les méthodes
qu'AggregationFunction doit implémenter: ---- createAccumulator ()
---- accumlate ()
---- emitValue ()

Le principe de fonctionnement de TableAggregateFunction:

  • Premièrement, il a également besoin d'un accumulateur (Accumulator), qui est une structure de données contenant les résultats intermédiaires de l'agrégation. Un accumulateur vide peut être créé en appelant la méthode createAccumulator ().
  • Par la suite, la méthode accumullate () de la fonction est appelée pour chaque ligne d'entrée pour mettre à jour l'accumulateur.
  • Une fois toutes les lignes traitées, la méthode emitValue () de la fonction est appelée pour calculer et renvoyer le résultat final.

Par exemple, utilisez la fonction d'agrégation de table pour implémenter un scénario des n premiers pour tous les capteurs

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

Structure du code et diagramme des effets en cours d'exécution:

Flink de l'entrée au vrai parfum (22. La dernière partie des bases, diverses fonctions UDF)

Je suppose que tu aimes

Origine blog.51cto.com/mapengfei/2572888
conseillé
Classement