Inicio rápido de FlinkSQL

Los almacenes de datos en tiempo real están llegando con fuerza y ​​FlinkSQL se ha convertido en un nirvana. El artículo de cinco mil palabras llevará a todos a comenzar rápidamente con FlinkSQL, ocupar la ventaja de la pila de tecnología y obtener una promoción y un aumento de salario.

imagen


En segundo lugar, los antecedentes de FlinkSQL

        Flink SQL es un lenguaje de desarrollo diseñado para la computación en tiempo real de Flink para simplificar el modelo de cálculo y reducir el umbral para que los usuarios utilicen la computación en tiempo real.

        Desde 2015, Alibaba comenzó a investigar motores informáticos de flujo de código abierto y finalmente decidió construir una nueva generación de motores informáticos basados ​​en Flink, optimizar y mejorar las deficiencias de Flink y abrir el código final a principios de 2019, que se conoce como Blink. Una de las contribuciones más importantes de Blink basada en el Flink original es la implementación de Flink SQL.

        Flink SQL es una capa de API orientada al usuario. En nuestros campos tradicionales de computación en flujo, como  Storm y Spark Streaming, se proporcionan algunas API de función o Datastream. Los usuarios escriben lógica de negocios en Java o Scala. Aunque este método es flexible, tiene algunos Deficiencias. Por ejemplo, tiene un cierto umbral y es difícil de ajustar . Con la actualización continua de la versión, hay muchas incompatibilidades en la API .

imagen        En este contexto, no hay duda de que SQL se ha convertido en nuestra mejor opción. La razón por la que elegimos SQL como API central es porque tiene varias características muy importantes:

  • SQL es un lenguaje prescriptivo, los usuarios solo necesitan expresar sus necesidades con claridad, sin conocer los métodos específicos ;

  • SQL se puede optimizar, con una variedad de optimizadores de consultas integrados, que pueden traducir el plan de ejecución óptimo para SQL ;

  • SQL es fácil de entender, las personas de diferentes industrias y campos lo comprenden y el costo de aprendizaje es bajo ;

  • SQL es muy estable En la historia de la base de datos de más de 30 años, SQL en sí mismo ha cambiado poco ;

  • La unificación de flujo y lote, el Runtime subyacente de Flink en sí mismo es un motor unificado de flujo y lote, mientras que SQL puede lograr la unificación de flujo y lote en la capa API .

Tres, introducción general

3.1 ¿Qué son Table API y Flink SQL?

        Flink en sí es un marco de procesamiento unificado de flujo por lotes, por lo que Table API y SQL son las API de procesamiento de nivel superior unificadas para el flujo por lotes . La función actual aún no está completa y se encuentra en una etapa de desarrollo activo.

        Table API es un conjunto de API de consultas integradas en los lenguajes Java y Scala que nos permite combinar consultas de algunos operadores relacionales (como seleccionar, filtrar y unir) de una manera muy intuitiva. Para Flink SQL, puede escribir SQL directamente en el código para implementar algunas operaciones de consulta . El soporte SQL de Flink se basa en Apache Calcite (herramienta de análisis de SQL de código abierto de Apache) que implementa el estándar SQL.

        Independientemente de si la entrada es una entrada por lotes o una entrada de transmisión, en los dos conjuntos de API, las consultas especificadas tienen la misma semántica y obtienen los mismos resultados.

3.2 Dependencias que deben introducirse

        La API de tabla y SQL deben introducir dos dependencias: planner  y 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>

        entre ellos:

        flink-table-planner : el planificador, la parte más importante de la API de la tabla, proporciona un entorno de ejecución y un planificador para generar planes de ejecución de programas ;

        flink-table-api-scala-bridge : puente puente, principalmente responsable del soporte de conexión de API de tabla y API de DataStream / DataSet, dividido en java y scala según el idioma ;

        Las dos dependencias aquí deben agregarse cuando se ejecuta en el entorno IDE; si es un entorno de producción, el planificador ya existe en el directorio lib de forma predeterminada, y solo se requiere el puente.

        Por supuesto, si desea utilizar funciones definidas por el usuario o conectarse a Kafka, necesita un cliente SQL, que está incluido  flink-table-common .        

3.3 La diferencia entre los dos planificadores (antiguo y parpadeante)

        1. Unificación de flujo por lotes: Blink considera los trabajos de procesamiento por lotes como un caso especial de procesamiento por secuencias . Por lo tanto, blink no admite la conversión entre tablas y DataSets, los trabajos de procesamiento por lotes no se convertirán en aplicaciones DataSet, sino que, al igual que el procesamiento de secuencias, se convertirán en programas DataStream para su procesamiento.

        2. Debido a la unificación de los flujos por lotes, el planificador Blink no admite BatchTableSource y, en su lugar, utiliza un StreamTableSource limitado .

        3. El planificador Blink solo admite catálogos nuevos, no el ExternalCatalog obsoleto.

        4. El planificador anterior y la implementación de FilterableTableSource del planificador Blink no son compatibles. El antiguo planificador empujará PlannerExpressions hacia abajo a filterableTableSource, mientras que el planificador parpadeante empujará Expressions hacia abajo.

        5. Las opciones de configuración de clave-valor basadas en cadenas solo se aplican al planificador Blink.

        6. La implementación de PlannerConfig en los dos planificadores es diferente.

        7. El planificador Blink optimizará varios receptores en un DAG (solo se admite en TableEnvironment, pero no en StreamTableEnvironment). La optimización del planificador anterior siempre coloca cada receptor en un nuevo DAG, donde todos los DAG son independientes entre sí.

        8. El planificador antiguo no admite estadísticas de catálogo, pero el planificador Blink sí.

Cuatro, llamada a la API

4.1 Estructura básica del programa

        La estructura del programa de Table API y SQL es similar a la estructura del programa del procesamiento de flujo ; también se puede considerar que tiene unos pocos pasos: primero cree un entorno de ejecución y luego defina el origen, la transformación y el sumidero.

        El proceso de operación específico es el siguiente:

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 Crear un entorno de mesa

        La forma más sencilla de crear un entorno de tabla es crear un entorno de tabla directamente basado en el entorno de ejecución de procesamiento de flujo ajustando el método de creación:

val tableEnv = StreamTableEnvironment.create(env)

        Table Environment ( TableEnvironment ) es el concepto central de la integración de Table API y SQL en flink . Es responsable de:

  • Registro de catálogo

  • Registrarse en el catálogo interno

  • Ejecutar consulta SQL

  • Funciones definidas por el usuario registradas

  • Convertir un DataStream o DataSet en una tabla

  • Guardar una referencia a ExecutionEnvironment o StreamExecutionEnvironment

        Al crear un TableEnv, puede pasar un parámetro EnvironmentSettings o TableConfig adicional, que se puede utilizar para configurar algunas características del TableEnvironment.

        Por ejemplo, configure la versión anterior de la consulta de transmisión (Flink-Streaming-Query):

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

        Entorno de procesamiento por lotes basado en la versión anterior (Flink-Batch-Query):

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

        Entorno de transmisión basado en la versión blink (Blink-Streaming-Query):

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

        Entorno de procesamiento por lotes basado en la versión de parpadeo (Blink-Batch-Query):

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

4.3 Registrarse en el catálogo

4.3.1 El concepto de tabla

        TableEnvironment puede registrar el catálogo del catálogo y puede basarse en el formulario de registro del catálogo. Mantendrá un mapa entre las tablas de tabla de catálogo.

        La tabla (Tabla) se especifica mediante un "identificador" y consta de tres partes: nombre del catálogo, nombre de la base de datos y nombre del objeto (nombre de la tabla ). Si no se especifica ningún directorio o base de datos, se utiliza el valor predeterminado actual.

        Las tablas pueden ser regulares (tabla, tabla) o virtuales (ver, ver). Una tabla normal (Table) generalmente se puede usar para describir datos externos, como datos en archivos, tablas de bases de datos o colas de mensajes, o se puede convertir directamente desde DataStream. Se puede crear una vista a partir de una tabla existente, generalmente como resultado de una API de tabla o una consulta SQL .

4.3.2 Conectarse al sistema de archivos (formato Csv)

        Para conectarse al sistema externo en el registro en el Catálogo,  puede llamar directamente a  tableEnv.connect () , y los parámetros deben pasarse en un ConnectorDescriptor, que es el descriptor del conector. Para el conector del sistema de archivos, flink lo ha proporcionado internamente, que se llama FileSystem () .

        el código se muestra a continuación:

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")    // 创建临时表

        Esta es una versión antigua del descriptor de formato csv. Dado que no es estándar, no es universal para el acoplamiento con sistemas externos, por lo que se abandonará y será reemplazado por un nuevo descriptor de formato que se ajuste al estándar RFC-4180 en el futuro. El nuevo descriptor se llama Csv (), pero flink no lo proporciona directamente y necesita introducir una dependencia en flink-csv :

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

        El código es muy similar, simplemente cambie OldCsv en withFormat a Csv.

4.3.3 Conectarse a Kafka

        Entre los conectores kafka, flink-kafka-connector, la versión 1.10 ha proporcionado compatibilidad con Table API. Podemos pasar directamente una clase llamada Kafka en el método de conexión, que es el ConnectorDescriptor del conector 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")

        Por supuesto, también se puede conectar a sistemas externos como ElasticSearch, MySql, HBase, Hive, etc. La implementación es básicamente similar. Los amigos interesados ​​pueden investigar por su cuenta, por lo que no entraré en detalles aquí.

4.4 Consulta de tabla

        Mediante el aprendizaje anterior, hemos utilizado el conector del sistema externo, podemos leer y escribir datos, y registrarnos en el catálogo del entorno. Luego, puede realizar la conversión de consultas en la tabla .

        Flink nos proporciona dos métodos de consulta: Table API y SQL.

4.4.1 API de tabla de llamadas

        Table API es una API de consulta integrada en los lenguajes Scala y Java. A diferencia de SQL, las consultas de Table API no están representadas por cadenas, sino que se llaman paso a paso en el lenguaje host.

        La API Table se basa en la clase Table que representa una "tabla" y proporciona un conjunto de API para el procesamiento de operaciones . Estos métodos devuelven un nuevo objeto Table, que representa el resultado de aplicar una operación de transformación a la tabla de entrada. Algunas operaciones de conversión relacional pueden estar compuestas por múltiples llamadas a métodos para formar una estructura de llamadas en cadena. Por ejemplo table.select(…).filter(…), donde seleccionar (...) representa el campo especificado en la tabla de selección y filtro (...) representa la condición del filtro.

        La implementación en el código es la siguiente:

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

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

4.4.2 consulta SQL

        La integración SQL de Flink se basa en ApacheCalcite, que implementa el estándar SQL . En Flink, las cadenas regulares se utilizan para definir declaraciones de consulta SQL. El resultado de la consulta SQL es una nueva tabla.

        El código se implementa de la siguiente manera:

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

        o:

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

        Por supuesto, también se pueden agregar operaciones de agregación. Por ejemplo, contamos el número de ocurrencias de datos de temperatura para cada sensor y hacemos un recuento de estadísticas:

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

        Implementación de SQL:

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

        El campo especificado en la API de tabla aquí está precedido por una comilla simple. Esta es la escritura del tipo de expresión definido en la API de tabla, que puede representar fácilmente un campo en una tabla.

        Los campos se pueden escribir directamente entre comillas dobles o se pueden utilizar medias comillas simples + nombres de campo . En el siguiente código, generalmente se usa la última forma.

4.5 Convertir DataStream en tabla

        Flink nos permite convertir Table y DataStream: basándonos en un DataStream, podemos leer la fuente de datos en una secuencia, luego mapearla en una clase de muestra y luego convertirla en una Tabla. Los campos de columna de la Tabla son los campos de la clase de muestra, por lo que no es necesario molestarse en definir el esquema.

4.5.1 Expresión de código

        La implementación en el código es muy simple, solo use tableEnv.fromDataStream () directamente. El esquema de la tabla después de la conversión predeterminada corresponde a las definiciones de campo en DataStream, o se puede especificar por separado.

        Esto nos permite cambiar el orden de los campos, renombrarlos o seleccionar solo ciertos campos, lo que equivale a hacer una operación de mapa (o la operación de selección de la API de tabla).

        El código es el siguiente:

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 Correspondencia entre tipo de datos y esquema de tabla

        En el ejemplo de la sección anterior, la relación correspondiente entre el tipo de datos en DataStream y el esquema de la tabla se basa en los nombres de campo en la clase de muestra (asignación basada en nombres), por lo que también puede usar as para cambiar el nombre.

        Otro método correspondiente es corresponder directamente a la posición del campo (mapeo basado en la posición) Durante el proceso correspondiente, puede especificar directamente el nuevo nombre del campo.

        Correspondencia basada en nombres:

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

        Correspondencia basada en la ubicación:

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

        La API DataStream y DataSet de Flink admite varios tipos.

        Los tipos combinados, como tuplas (tuplas integradas de Scala y Java), POJO, clases de casos de Scala y el tipo de fila de Flink, etc., permiten estructuras de datos anidadas con múltiples campos, a los que se puede acceder en expresiones de tabla. Otros tipos se consideran tipos atómicos.

        Tipo de tupla y tipo de átomo, generalmente es mejor usar correspondencia de posición; si tiene que usar correspondencia de nombre, también es posible: tipo de tupla, el nombre predeterminado es "_1", "_2"; y tipo atómico, el nombre predeterminado es "f0".

4.6 Crear vista temporal (vista temporal)

        La primera forma de crear una vista temporal es convertirla directamente desde DataStream. Del mismo modo, puede corresponder directamente a la conversión del campo; también puede especificar el campo correspondiente al convertir.

        el código se muestra a continuación:

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

        Además, por supuesto, también puede crear una vista basada en Tabla:

tableEnv.createTemporaryView("sensorView", sensorTable)

        El esquema de vista y la tabla son exactamente iguales. De hecho, en Table API, View y Table pueden considerarse equivalentes.

4.7 Tabla de resultados

        La salida de la tabla se realiza escribiendo datos en TableSink. TableSink es una interfaz universal que puede admitir diferentes formatos de archivo, bases de datos de almacenamiento y colas de mensajes .

        Para una implementación específica, la forma más directa de generar una tabla es escribir una Tabla en el TableSink registrado a través del método Table.insertInto () .

4.7.1 Salida a archivo

        el código se muestra a continuación:

// 注册输出表
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 Modo de actualización

        En el proceso de procesamiento de flujo, el procesamiento de la tabla no es tan simple como la definición tradicional.

        Para las consultas de transmisión, debe declarar cómo realizar la conversión entre la tabla (dinámica) y el conector externo . El tipo de mensajes intercambiados con el sistema externo lo especifica el modo de actualización .

        Hay tres modos de actualización en la API de Flink Table:

  • Modo anexar

        En el modo de adición, la tabla (tabla dinámica) y el conector externo solo intercambian mensajes de inserción.

  • Modo de retracción

        En el modo de retiro, la mesa y el conector externo intercambian: agregar (Agregar) y retirar (Retirar) mensajes.

        entre ellos:

  • Insertar (Insertar) se codificará como agregar un mensaje;

  • Eliminar (Eliminar) se codifica como un mensaje de retiro;

  • Actualizar (Actualizar) se codificará como un mensaje de retiro de la fila actualizada (fila anterior) y un mensaje agregado de la fila actualizada (fila nueva).

        En este modo, la clave no se puede definir, que es completamente diferente del modo upsert.

  • Modo Upsert (actualización de inserción)

        En el modo Upsert, la tabla dinámica y el conector externo intercambian mensajes Upsert y Delete.

        Este modo requiere una clave única a través de la cual se pueden transmitir los mensajes de actualización. Para aplicar el mensaje correctamente, el conector externo necesita conocer los atributos de esta clave única.

  • Tanto Insertar como Actualizar están codificados como mensajes Upsert;

  • Eliminar (eliminar) el código como información de eliminación

        La principal diferencia entre este modo y el modo de retracción es que la operación de actualización está codificada con un solo mensaje, por lo que la eficiencia será mayor.

4.7.3 Exportar a Kafka

        Además de la salida a un archivo, también puede enviarse a Kafka. Podemos combinar el Kafka anterior como datos de entrada para construir una canalización de datos, kafka in y kafka out.

        el código se muestra a continuación:

// 输出到 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 Exportar a ElasticSearch

        El conector de ElasticSearch puede operar en modo upsert (actualizar + insertar), de modo que puede usar la clave definida por Query para intercambiar mensajes UPSERT / DELETE con sistemas externos.

        Además, para las consultas de "solo adición", el conector también puede funcionar en modo de adición, de modo que solo los mensajes de inserción se pueden intercambiar con sistemas externos.

        El formato de datos actualmente soportado por es es solo Json, y flink en sí no tiene el soporte correspondiente, por lo que es necesario introducir dependencias:

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

        El código se implementa de la siguiente manera:

// 输出到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 Exportar a MySql

        Flink proporciona el conector flink-jdbc para la conexión jdbc de la API Table. Primero, debemos introducir la dependencia:

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

        La implementación del código de la conexión jdbc es bastante especial, porque no hay una implementación de clase java / scala correspondiente  ConnectorDescriptor, por lo que no puede ser directamente  tableEnv.connect(). Sin embargo, Flink SQL deja una interfaz para ejecutar DDL:tableEnv.sqlUpdate()

        Para la operación de creación de tablas de jdbc, es inherentemente adecuado escribir DDL directamente, por lo que nuestro código se puede escribir así:

// 输出到 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 tabla a DataStream

        La tabla se puede convertir a DataStream o DataSet . De esta forma, el programa de procesamiento por lotes o de flujo personalizado puede continuar ejecutándose en los resultados de la API de tabla o la consulta SQL.

        Al convertir una tabla a DataStream o DataSet, debe especificar el tipo de datos generado , es decir, el tipo de datos que se convertirán en cada fila de la tabla. Normalmente, el tipo de conversión más conveniente es Fila . Por supuesto, debido a que todos los tipos de campo del resultado son claros, a menudo usamos tipos de tupla para expresar.

        La tabla se actualiza dinámicamente como resultado de la consulta de transmisión . Por lo tanto, para convertir esta consulta dinámica en un flujo de datos, la operación de actualización de la tabla también debe codificarse, y luego existen diferentes modos de conversión.

        Hay dos modos de tabla a DataStream en Table API:

  • Modo anexar

        Se usa en escenarios donde la tabla solo será cambiada por la operación Insertar

  • Modo de retracción

        Utilizado en cualquier escena. Algunos son similares al modo Retract en el modo de actualización, solo tiene dos tipos de operaciones: Insertar y Eliminar.

        Los datos obtenidos agregarán una bandera booleana (el primer campo devuelto), que se utiliza para indicar si se trata de datos recién agregados (Insertar) o datos eliminados (datos antiguos, Eliminar).

        El código se implementa de la siguiente manera:

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

        Por lo tanto, puede usar directamente toAppendStream para convertir sin operaciones de agregación como groupby; y si hay una operación de actualización después de la agregación, generalmente debe usar toRetractDstream.

4.7.7 Interpretación y ejecución de la consulta

        La API de tabla proporciona un mecanismo para explicar la lógica de la tabla de cálculo y optimizar el plan de consulta . Esto se hace mediante el método TableEnvironment.explain (table) o el método TableEnvironment.explain ().

        El método de explicación devolverá una cadena que describe los tres planes:

  • Plan de consulta lógica no optimizado

  • Plan de consultas lógicas optimizado

  • Plan de ejecución real

        Podemos ver el plan de ejecución en el código:

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

        El proceso de interpretación y ejecución de Query, el planificador anterior y el planificador de parpadeo son generalmente los mismos, pero son diferentes. En términos generales, la consulta se expresará como un plan de consulta lógica y luego se explicará en dos pasos:

  1. Optimizar el plan de consultas

  2. Interpretado como un programa DataStream o DataSet

        La versión Blink está unificada de flujo por lotes, por lo que todas las consultas solo se interpretarán como programas DataStream; además, en el entorno de procesamiento por lotes TableEnvironment, la versión Blink  no se iniciará hasta que se  llame a tableEnv.execute () .  

Supongo que te gusta

Origin blog.csdn.net/hzp666/article/details/113739441
Recomendado
Clasificación