Démarrage rapide de FlinkSQL

Les entrepôts de données en temps réel se multiplient et FlinkSQL est devenu un nirvana. Cet article de cinq mille mots amènera tout le monde à se lancer rapidement avec FlinkSQL, à profiter de la pile technologique et à obtenir une promotion et une augmentation de salaire!

image


Deuxièmement, l'arrière-plan de FlinkSQL

        Flink SQL est un langage de développement conçu pour le calcul en temps réel Flink afin de simplifier le modèle de calcul et d'abaisser le seuil d'utilisation du calcul en temps réel par les utilisateurs.

        Depuis 2015, Alibaba a commencé à enquêter sur les moteurs de calcul de flux open source et a finalement décidé de construire une nouvelle génération de moteurs de calcul basés sur Flink, d'optimiser et d'améliorer les lacunes de Flink et d'ouvrir le code final au début de 2019, connu sous le nom de Blink. L'une des contributions les plus importantes de Blink basée sur le Flink original est l'implémentation de Flink SQL.

        Flink SQL est une couche d'API orientée utilisateur. Dans nos domaines de calcul de flux traditionnels, tels que  Storm et Spark Streaming, certaines API Function ou Datastream sont fournies. Les utilisateurs écrivent la logique métier en Java ou Scala. Bien que cette méthode soit flexible, elle en comporte par exemple, il a un certain seuil et est difficile à régler.Avec la mise à jour continue de la version, il existe de nombreuses incompatibilités dans l' API .

image        Dans ce contexte, il ne fait aucun doute que SQL est devenu notre meilleur choix. La raison pour laquelle nous avons choisi SQL comme API principale est qu'elle possède plusieurs fonctionnalités très importantes:

  • SQL est un langage prescriptif, les utilisateurs n'ont qu'à exprimer clairement leurs besoins, sans connaître les méthodes spécifiques ;

  • SQL peut être optimisé, avec une variété d'optimiseurs de requêtes intégrés, qui peuvent traduire le plan d'exécution optimal pour SQL ;

  • SQL est facile à comprendre, les gens de différents secteurs et domaines le comprennent et le coût d'apprentissage est faible ;

  • SQL est très stable: dans l'histoire de la base de données de plus de 30 ans, SQL lui-même a peu changé ;

  • L'unification du flux et du lot, le Runtime sous-jacent de Flink lui-même est un moteur unifié de flux et de lot, tandis que SQL peut réaliser l'unification du flux et du lot au niveau de la couche API .

Trois, introduction générale

3.1 Que sont l'API Table et Flink SQL?

        Flink lui-même est un cadre de traitement de flux par lots unifié, donc l' API Table et SQL sont les API de traitement unifiées de niveau supérieur pour le flux par lots . La fonction actuelle n'est pas encore terminée et est en phase de développement actif.

        L'API de table est un ensemble d'API de requête intégrées aux langages Java et Scala . Elle nous permet de combiner les requêtes de certains opérateurs relationnels (tels que la sélection, le filtrage et la jointure) de manière très intuitive. Pour Flink SQL, vous pouvez écrire du SQL directement dans le code pour implémenter certaines opérations de requête . Le support SQL de Flink est basé sur Apache Calcite (outil d'analyse SQL open source Apache) qui implémente le standard SQL.

        Indépendamment du fait que l'entrée soit une entrée par lots ou une entrée de flux, dans les deux ensembles d'API, les requêtes spécifiées ont la même sémantique et obtiennent les mêmes résultats.

3.2 Dépendances à introduire

        L'API de table et SQL doivent introduire deux dépendances: planner  et bridge

<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-api-scala-bridge_2.11</artifactId>
    <version>1.10.0</version>
</dependency>

        parmi eux:

        flink-table-planner : Le planificateur de planification, la partie la plus importante de l'API de table, fournit un environnement d'exécution et un planificateur pour générer des plans d'exécution de programme ;

        flink-table-api-scala-bridge : bridge bridge, principalement responsable du support de connexion de l'API table et de l'API DataStream / DataSet, divisé en java et scala selon le langage ;

        Les deux dépendances ici doivent être ajoutées lors de l'exécution dans l'environnement IDE; s'il s'agit d'un environnement de production, le planificateur existe déjà dans le répertoire lib par défaut, et seul le pont est requis.

        Bien sûr, si vous souhaitez utiliser des fonctions définies par l'utilisateur ou vous connecter à Kafka, vous avez besoin d'un client SQL, qui est inclus  flink-table-common .        

3.3 La différence entre les deux planificateurs (ancien et clignotant)

        1. Unification de flux par lots: Blink considère les travaux de traitement par lots comme un cas particulier de traitement en continu . Par conséquent, blink ne prend pas en charge la conversion entre les tables et les DataSets, les travaux de traitement par lots ne seront pas convertis en applications DataSet, mais tout comme le traitement de flux, convertis en programmes DataStream pour traitement.

        2. En raison de l'unification des flux de lots, Blink planner ne prend pas en charge BatchTableSource et utilise à la place un StreamTableSource borné .

        3. Blink planner ne prend en charge que les nouveaux catalogues, pas le catalogue externe obsolète.

        4. L'ancien planificateur et l'implémentation FilterableTableSource du planificateur Blink ne sont pas compatibles. L'ancien planificateur poussera PlannerExpressions vers le filterableTableSource, tandis que le planificateur clignotant poussera Expressions vers le bas.

        5. Les options de configuration de valeur-clé basées sur une chaîne ne s'appliquent qu'au planificateur Blink.

        6. L'implémentation de PlannerConfig dans les deux planificateurs est différente.

        7. Blink planner optimisera plusieurs récepteurs dans un DAG (uniquement pris en charge sur TableEnvironment, mais non pris en charge sur StreamTableEnvironment). L'optimisation de l'ancien planificateur place toujours chaque puits dans un nouveau DAG, où tous les DAG sont indépendants les uns des autres.

        8. L'ancien planificateur ne prend pas en charge les statistiques de catalogue, contrairement au planificateur Blink.

Quatre, appel API

4.1 Structure de base du programme

        La structure de programme de Table API et SQL est similaire à la structure de programme de traitement de flux ; elle peut également être considérée à peu près comme comportant quelques étapes: créez d'abord un environnement d'exécution, puis définissez la source, la transformation et le récepteur.

        Le processus de fonctionnement spécifique est le suivant:

val tableEnv = ...     // 创建表的执行环境

// 创建一张表,用于读取数据
tableEnv.connect(...).createTemporaryTable("inputTable")
// 注册一张表,用于把计算结果输出
tableEnv.connect(...).createTemporaryTable("outputTable")

// 通过 Table API 查询算子,得到一张结果表
val result = tableEnv.from("inputTable").select(...)
// 通过 SQL查询语句,得到一张结果表
val sqlResult  = tableEnv.sqlQuery("SELECT ... FROM inputTable ...")

// 将结果表写入输出表中
result.insertInto("outputTable")

4.2 Créer un environnement de table

        Le moyen le plus simple de créer un environnement de table consiste à créer un environnement de table directement basé sur l'environnement d'exécution du traitement de flux en ajustant la méthode create:

val tableEnv = StreamTableEnvironment.create(env)

        L'environnement de table ( TableEnvironment ) est le concept de base de l'intégration de l'API Table et de SQL dans flink . Il est responsable de:

  • Enregistrer le catalogue

  • Inscrivez-vous dans le catalogue interne

  • Exécuter une requête SQL

  • Fonctions définies par l'utilisateur enregistrées

  • Convertir un DataStream ou un DataSet en table

  • Enregistrer une référence à ExecutionEnvironment ou StreamExecutionEnvironment

        Lors de la création d'un TableEnv, vous pouvez transmettre un paramètre EnvironmentSettings ou TableConfig supplémentaire, qui peut être utilisé pour configurer certaines caractéristiques de TableEnvironment.

        Par exemple, configurez l'ancienne version de la requête de streaming (Flink-Streaming-Query):

val settings = EnvironmentSettings.newInstance()
  .useOldPlanner()      // 使用老版本planner
  .inStreamingMode()    // 流处理模式
  .build()
val tableEnv = StreamTableEnvironment.create(env, settings)

        Environnement de traitement par lots basé sur l'ancienne version (Flink-Batch-Query):

val batchEnv = ExecutionEnvironment.getExecutionEnvironment
val batchTableEnv = BatchTableEnvironment.create(batchEnv)

        Environnement de streaming basé sur la version blink (Blink-Streaming-Query):

val bsSettings = EnvironmentSettings.newInstance()
.useBlinkPlanner()
.inStreamingMode().build()
val bsTableEnv = StreamTableEnvironment.create(env, bsSettings)

        Environnement de traitement par lots basé sur la version clignotante (Blink-Batch-Query):

val bbSettings = EnvironmentSettings.newInstance()
.useBlinkPlanner()
.inBatchMode().build()
val bbTableEnv = TableEnvironment.create(bbSettings)

4.3 Inscription dans le catalogue

4.3.1 Le concept de table

        TableEnvironment peut enregistrer le catalogue de catalogue et peut être basé sur le formulaire d'enregistrement de catalogue. Il maintiendra une carte entre les tables Catalog-Table.

        La table (Table) est spécifiée par un "identificateur" et se compose de trois parties: le nom du catalogue, le nom de la base de données et le nom de l'objet (nom de la table ). Si aucun répertoire ou base de données n'est spécifié, la valeur par défaut actuelle est utilisée.

        Les tables peuvent être régulières (Table, table) ou virtuelles (View, view). Une table ordinaire (Table) peut généralement être utilisée pour décrire des données externes, telles que des données dans des fichiers, des tables de base de données ou des files d'attente de messages, ou elle peut être directement convertie à partir de DataStream. Une vue peut être créée à partir d'une table existante, généralement le résultat d'une API de table ou d'une requête SQL .

4.3.2 Se connecter au système de fichiers (format Csv)

        Pour vous connecter au système externe dans le registre dans le catalogue,  vous pouvez appeler directement  tableEnv.connect () , et les paramètres doivent être passés dans un ConnectorDescriptor, qui est le descripteur de connecteur. Pour le connecteur du système de fichiers, flink l'a fourni en interne, ce qui s'appelle FileSystem () .

        code montrer comme ci-dessous:

tableEnv
.connect( new FileSystem().path("sensor.txt"))  // 定义表数据来源,外部连接
  .withFormat(new OldCsv())    // 定义从外部系统读取数据之后的格式化方法
  .withSchema( new Schema()
    .field("id", DataTypes.STRING())
    .field("timestamp", DataTypes.BIGINT())
    .field("temperature", DataTypes.DOUBLE())
  )    // 定义表结构
  .createTemporaryTable("inputTable")    // 创建临时表

        Il s'agit d'une ancienne version du descripteur de format csv. Comme il n'est pas standard, il n'est pas universel pour l'amarrage avec des systèmes externes, il sera donc abandonné et sera remplacé par un nouveau descripteur de format conforme à la norme RFC-4180 à l'avenir. Le nouveau descripteur s'appelle Csv (), mais flink ne le fournit pas directement, et il doit introduire une dépendance sur flink-csv :

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

        Le code est très similaire, il suffit de changer OldCsv dans withFormat en Csv.

4.3.3 Se connecter à Kafka

        Parmi les connecteurs kafka, flink-kafka-connector, la version 1.10 a fourni le support de l'API Table. Nous pouvons passer directement une classe appelée Kafka dans la méthode connect, qui est le ConnectorDescriptor du connecteur Kafka.

tableEnv.connect(
  new Kafka()
    .version("0.11") // 定义kafka的版本
    .topic("sensor") // 定义主题
    .property("zookeeper.connect", "localhost:2181") 
    .property("bootstrap.servers", "localhost:9092")
)
  .withFormat(new Csv())
  .withSchema(new Schema()
  .field("id", DataTypes.STRING())
  .field("timestamp", DataTypes.BIGINT())
  .field("temperature", DataTypes.DOUBLE())
)
  .createTemporaryTable("kafkaInputTable")

        Bien sûr, il peut également être connecté à des systèmes externes tels qu'ElasticSearch, MySql, HBase, Hive, etc. La mise en œuvre est fondamentalement similaire. Les amis intéressés peuvent effectuer leurs propres recherches, je n'entrerai donc pas dans les détails ici.

4.4 Requête de table

        Grâce à l'apprentissage ci-dessus, nous avons utilisé le connecteur du système externe, nous pouvons lire et écrire des données, et nous inscrire dans le catalogue de l'environnement. Ensuite, vous pouvez effectuer une conversion de requête sur la table .

        Flink nous fournit deux méthodes de requête: Table API et SQL.

4.4.1 Appel de l'API de table

        L'API de table est une API de requête intégrée aux langages Scala et Java. Contrairement à SQL, les requêtes de l'API Table ne sont pas représentées par des chaînes, mais sont appelées étape par étape dans le langage hôte.

        L'API Table est basée sur la classe Table représentant une «table» et fournit un ensemble d'API pour le traitement des opérations . Ces méthodes renvoient un nouvel objet Table, qui représente le résultat de l'application d'une opération de transformation à la table d'entrée. Certaines opérations de conversion relationnelle peuvent être composées de plusieurs appels de méthode pour former une structure d'appel en chaîne. Par exemple table.select(…).filter(…), où select (...) représente le champ spécifié dans la table de sélection et filter (...) représente la condition de filtre.

        L'implémentation dans le code est la suivante:

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

val resultTable: Table = senorTable
.select("id, temperature")
.filter("id ='sensor_1'")

4.4.2 Requête SQL

        L'intégration SQL de Flink est basée sur ApacheCalcite, qui implémente le standard SQL . Dans Flink, des chaînes régulières sont utilisées pour définir des instructions de requête SQL. Le résultat de la requête SQL est une nouvelle table.

        Le code est implémenté comme suit:

val resultSqlTable: Table = tableEnv.sqlQuery("select id, temperature from inputTable where id ='sensor_1'")

        ou:

val resultSqlTable: Table = tableEnv.sqlQuery(
  """
    |select id, temperature
    |from inputTable
    |where id = 'sensor_1'
  """.stripMargin)

        Bien entendu, des opérations d'agrégation peuvent également être ajoutées. Par exemple, nous comptons le nombre d'occurrences de données de température pour chaque capteur et faisons un comptage statistique:

val aggResultTable = sensorTable
.groupBy('id)
.select('id, 'id.count as 'count)

        Implémentation SQL:

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

        Le champ spécifié dans l'API de table ici est précédé d'un guillemet simple. Il s'agit de l'écriture du type d'expression défini dans l'API de table, qui peut facilement représenter un champ dans une table.

        Les champs peuvent être placés directement entre guillemets doubles, ou des demi-guillemets simples + noms de champs peuvent être utilisés . Dans le code suivant, cette dernière forme est généralement utilisée.

4.5 Convertir DataStream en table

        Flink nous permet de convertir Table et DataStream: sur la base d'un DataStream, nous pouvons lire la source de données dans un flux, puis la mapper dans un exemple de classe, puis la convertir en Table. Les champs de colonne de la table sont les champs de la classe d'exemple, il n'est donc pas nécessaire de se soucier de définir le schéma.

4.5.1 Expression de code

        L'implémentation dans le code est très simple, il suffit d'utiliser directement tableEnv.fromDataStream (). Le schéma de table après la conversion par défaut correspond aux définitions de champ dans DataStream, ou il peut être spécifié séparément.

        Cela nous permet de changer l'ordre des champs, de les renommer ou de ne sélectionner que certains champs, ce qui équivaut à faire une opération de carte (ou l'opération de sélection de l'API Table).

        Le code est comme suit:

val inputStream: DataStream[String] = env.readTextFile("sensor.txt")
val dataStream: DataStream[SensorReading] = inputStream
  .map(data => {
    val dataArray = data.split(",")
    SensorReading(dataArray(0), dataArray(1).toLong, dataArray(2).toDouble)
  })

val sensorTable: Table = tableEnv.fromDataStreama(datStream)

val sensorTable2 = tableEnv.fromDataStream(dataStream, 'id, 'timestamp as 'ts)

4.5.2 Correspondance entre le type de données et le schéma de table

        Dans l'exemple de la section précédente, la relation correspondante entre le type de données dans DataStream et le schéma de la table est basée sur les noms de champ dans l'exemple de classe (mappage basé sur le nom), vous pouvez donc également utiliser as pour renommer.

        Une autre méthode correspondante consiste à correspondre directement à la position du champ (cartographie basée sur la position). Au cours du processus correspondant, vous pouvez directement spécifier le nouveau nom du champ.

        Correspondance basée sur le nom:

val sensorTable = tableEnv.fromDataStream(dataStream, 'timestamp as 'ts, 'id as 'myId, 'temperature)

        Correspondance basée sur l'emplacement:

val sensorTable = tableEnv.fromDataStream(dataStream, 'myId, 'ts)

        Les API DataStream et DataSet de Flink prennent en charge plusieurs types.

        Les types combinés, tels que les tuples (tuples Scala et Java intégrés), les POJO, les classes de cas Scala et le type Flink's Row, etc., permettent des structures de données imbriquées avec plusieurs champs, accessibles dans les expressions de table. Les autres types sont considérés comme des types atomiques.

        Type de tuple et type d'atome, il est généralement préférable d'utiliser la correspondance de position; si vous devez utiliser la correspondance de nom, c'est également possible: type de tuple, le nom par défaut est "_1", "_2"; et type atomique, le nom par défaut est "f0".

4.6 Créer une vue temporaire (vue temporaire)

        La première façon de créer une vue temporaire est de la convertir directement à partir du DataStream. De même, vous pouvez directement correspondre à la conversion du champ; vous pouvez également spécifier le champ correspondant lors de la conversion.

        code montrer comme ci-dessous:

tableEnv.createTemporaryView("sensorView", dataStream)
tableEnv.createTemporaryView("sensorView", dataStream, 'id, 'temperature, 'timestamp as 'ts)

        En outre, bien sûr, vous pouvez également créer une vue basée sur Table:

tableEnv.createTemporaryView("sensorView", sensorTable)

        Le schéma de vue et de table sont exactement les mêmes. En fait, dans l'API Table, View et Table peuvent être considérés comme équivalents.

4.7 Tableau de sortie

        La sortie de la table est réalisée en écrivant des données dans TableSink. TableSink est une interface universelle qui peut prendre en charge différents formats de fichiers, bases de données de stockage et files d'attente de messages .

        Pour une implémentation spécifique, le moyen le plus direct de générer une table est d'écrire un Table dans le TableSink enregistré via la méthode Table.insertInto () .

4.7.1 Sortie dans un fichier

        code montrer comme ci-dessous:

// 注册输出表
tableEnv.connect(
  new FileSystem().path("…\\resources\\out.txt")
) // 定义到文件系统的连接
  .withFormat(new Csv()) // 定义格式化方法,Csv格式
  .withSchema(new Schema()
  .field("id", DataTypes.STRING())
  .field("temp", DataTypes.DOUBLE())
) // 定义表结构
  .createTemporaryTable("outputTable") // 创建临时表

resultSqlTable.insertInto("outputTable")

4.7.2 Mode de mise à jour

        Dans le processus de traitement de flux, le traitement de la table n'est pas aussi simple que la définition traditionnelle.

        Pour les requêtes en streaming, vous devez déclarer comment effectuer la conversion entre la table (dynamique) et le connecteur externe . Le type de messages échangés avec le système externe est spécifié par le mode de mise à jour .

        Il existe trois modes de mise à jour dans l'API Flink Table:

  • Ajouter le mode

        En mode ajout, la table (table dynamique) et le connecteur externe échangent uniquement des messages d'insertion.

  • Mode de rétraction

        En mode retrait, la table et le connecteur externe s'échangent: ajoutez (Ajouter) et retirez (Retract) des messages.

        parmi eux:

  • Insérer (Insérer) sera codé comme l'ajout d'un message;

  • Supprimer (Supprimer) est codé comme un message de retrait;

  • Update (Update) sera codé comme un message de retrait de la ligne mise à jour (ligne précédente), et un message ajouté de la ligne mise à jour (nouvelle ligne).

        Dans ce mode, la clé ne peut pas être définie, ce qui est complètement différent du mode upsert.

  • Mode Upsert (insertion de mise à jour)

        En mode Upsert, la table dynamique et le connecteur externe échangent des messages Upsert et Delete.

        Ce mode nécessite une clé unique à travers laquelle les messages de mise à jour peuvent être transmis. Pour appliquer correctement le message, le connecteur externe doit connaître les attributs de cette clé unique.

  • L'insertion et la mise à jour sont codées en tant que messages Upsert;

  • Supprimer (supprimer) le code comme informations de suppression

        La principale différence entre ce mode et le mode Retract est que l'opération de mise à jour est codée avec un seul message, de sorte que l'efficacité sera plus élevée.

4.7.3 Exporter vers Kafka

        En plus de la sortie dans un fichier, il peut également être sorti vers Kafka. Nous pouvons combiner le Kafka précédent en tant que données d'entrée pour créer un pipeline de données, kafka in et kafka out.

        code montrer comme ci-dessous:

// 输出到 kafka
tableEnv.connect(
  new Kafka()
    .version("0.11")
    .topic("sinkTest")
    .property("zookeeper.connect", "localhost:2181")
    .property("bootstrap.servers", "localhost:9092")
)
  .withFormat( new Csv() )
  .withSchema( new Schema()
    .field("id", DataTypes.STRING())
    .field("temp", DataTypes.DOUBLE())
  )
  .createTemporaryTable("kafkaOutputTable")

resultTable.insertInto("kafkaOutputTable")

4.7.4 Exporter vers ElasticSearch

        Le connecteur d'ElasticSearch peut fonctionner en mode upsert (mise à jour + insertion), de sorte que vous pouvez utiliser la clé définie par Query pour échanger des messages UPSERT / DELETE avec des systèmes externes.

        En outre, pour les requêtes "Ajout uniquement", le connecteur peut également fonctionner en mode Ajout, de sorte que seuls les messages d'insertion peuvent être échangés avec des systèmes externes.

        Le format de données actuellement pris en charge par es est uniquement Json, et flink lui-même n'a pas de support correspondant, il est donc nécessaire d'introduire des dépendances:

<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-json</artifactId>
    <version>1.10.0</version>
</dependency>

        Le code est implémenté comme suit:

// 输出到es
tableEnv.connect(
  new Elasticsearch()
    .version("6")
    .host("localhost", 9200, "http")
    .index("sensor")
    .documentType("temp")
)
  .inUpsertMode()           // 指定是 Upsert 模式
  .withFormat(new Json())
  .withSchema( new Schema()
    .field("id", DataTypes.STRING())
    .field("count", DataTypes.BIGINT())
  )
  .createTemporaryTable("esOutputTable")

aggResultTable.insertInto("esOutputTable")

4.7.5 Exporter vers MySql

        Flink fournit le connecteur flink-jdbc pour la connexion jdbc de l'API Table. Nous devons d'abord introduire la dépendance:

<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-jdbc_2.11</artifactId>
    <version>1.10.0</version>
</dependency>

        L'implémentation du code de la connexion jdbc est assez spéciale, car il n'y a pas d'implémentation de classe java / scala correspondante  ConnectorDescriptor, donc elle ne peut pas être directement  tableEnv.connect(). Cependant, Flink SQL laisse une interface pour exécuter DDL:tableEnv.sqlUpdate()

        Pour l'opération de création de table de jdbc, il est intrinsèquement approprié d'écrire DDL directement, donc notre code peut être écrit comme ceci:

// 输出到 Mysql
val sinkDDL: String =
  """
    |create table jdbcOutputTable (
    |  id varchar(20) not null,
    |  cnt bigint not null
    |) with (
    |  'connector.type' = 'jdbc',
    |  'connector.url' = 'jdbc:mysql://localhost: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")

4.7.6 Convertir la table en DataStream

        La table peut être convertie en DataStream ou DataSet . De cette manière, le programme de traitement de flux personnalisé ou de traitement par lots peut continuer à s'exécuter sur les résultats de l'API de table ou de la requête SQL.

        Lors de la conversion d'une table en DataStream ou DataSet, vous devez spécifier le type de données généré , c'est-à-dire le type de données à convertir dans chaque ligne de la table. En général, le type de conversion le plus pratique est Row . Bien sûr, comme tous les types de champs du résultat sont clairs, nous utilisons souvent des types de tuple pour exprimer.

        La table est mise à jour dynamiquement à la suite de la requête de diffusion en continu . Par conséquent, pour convertir cette requête dynamique en un flux de données, l'opération de mise à jour de la table doit également être codée, puis il existe différents modes de conversion.

        Il existe deux modes de table à DataStream dans l'API Table:

  • Ajouter le mode

        Utilisé dans les scénarios où la table ne sera modifiée que par l'opération d'insertion

  • Mode de rétraction

        Utilisé dans n'importe quelle scène. Certains sont similaires au mode Retract en mode de mise à jour. Il ne comporte que deux types d'opérations: Insérer et Supprimer.

        Les données obtenues ajouteront un indicateur booléen (le premier champ renvoyé), qui est utilisé pour indiquer s'il s'agit de données nouvellement ajoutées (Insérer) ou de données supprimées (anciennes données, Supprimer).

        Le code est implémenté comme suit:

val resultStream: DataStream[Row] = tableEnv.toAppendStream[Row](resultTable)

val aggResultStream: DataStream[(Boolean, (String, Long))] = 
tableEnv.toRetractStream[(String, Long)](aggResultTable)

resultStream.print("result")
aggResultStream.print("aggResult")

        Par conséquent, vous pouvez utiliser directement toAppendStream pour convertir sans opérations d'agrégation telles que groupby; et s'il y a une opération de mise à jour après l'agrégation, vous devez généralement utiliser toRetractDstream.

4.7.7 Interprétation et exécution de la requête

        L'API Table fournit un mécanisme pour expliquer la logique de la table de calcul et optimiser le plan de requête . Cela se fait via la méthode TableEnvironment.explain (table) ou la méthode TableEnvironment.explain ().

        La méthode expliquer renverra une chaîne décrivant les trois plans:

  • Plan de requête logique non optimisé

  • Plan de requête logique optimisé

  • Plan d'exécution réel

        Nous pouvons voir le plan d'exécution dans le code:

val explaination: String = tableEnv.explain(resultTable)
println(explaination)

        Le processus d'interprétation et d'exécution de Query, l'ancien planificateur et le planificateur clignotant sont généralement les mêmes, mais ils sont différents. De manière générale, la requête sera exprimée sous la forme d'un plan de requête logique, puis expliquée en deux étapes:

  1. Optimiser le plan de requête

  2. Interprété comme un programme DataStream ou DataSet

        La version Blink est unifiée à flux par lots, donc toutes les requêtes ne seront interprétées que comme des programmes DataStream; De plus, dans l'environnement de traitement par lots TableEnvironment, la version Blink  ne démarrera pas tant que la tableEnv.execute () ne sera  pas appelée.  

Je suppose que tu aimes

Origine blog.csdn.net/hzp666/article/details/113739441
conseillé
Classement