Todos los productos
Search
Centro de documentos

Realtime Compute for Apache Flink:UDSF de Java

Última actualización: Sep 21, 2026

Una función escalar definida por el usuario (UDSF) asigna cero, uno o más valores escalares a un nuevo valor escalar. Esto genera una relación de uno a uno entre las filas de entrada y los valores de salida. Utilice una UDSF cuando las funciones integradas de Flink SQL no puedan expresar su lógica personalizada.

Para consultar la descripción general de todos los tipos de funciones definidas por el usuario (UDF), consulte Funciones definidas por el usuario.

Funcionamiento

Una UDSF extiende la clase ScalarFunction de Apache Flink e implementa uno o más métodos eval(). Flink llama a eval() una vez por cada fila de entrada y usa el valor devuelto como el escalar de salida.

public class SubstringFunction extends ScalarFunction {
    public String eval(String s, Integer begin, Integer end) {
        return s.substring(begin, end);
    }
}

Requisitos del método eval():

  • Debe declararse como public

  • Admite sobrecarga de métodos: defina varias firmas de eval() para distintos tipos de entrada

  • Admite argumentos variables (por ejemplo, eval(Integer...))

  • Use tipos primitivos envueltos (por ejemplo, Integer en lugar de int) para gestionar correctamente las entradas NULL

Requisitos previos

Antes de empezar, asegúrese de contar con lo siguiente:

  • IntelliJ IDEA instalado

  • Apache Maven instalado

  • Acceso a un espacio de trabajo de Realtime Compute for Apache Flink

Funciones escalares asíncronas (AsyncScalarFunction)

A diferencia de una función escalar común, una función escalar asíncrona personalizada debe extender AsyncScalarFunction y devolver los resultados mediante un CompletableFuture.

Utilice una función escalar asíncrona cuando esta ejecute operaciones que impliquen esperas de E/S, como consultar una base de datos externa o llamar a una interfaz HTTP. La función procesa varios registros de forma concurrente, lo cual reduce el impacto de las esperas en el rendimiento del trabajo. Para operaciones que no dependen de servicios externos, como el recorte de cadenas o los cálculos numéricos, suele ser suficiente una función escalar síncrona.

Parámetros de configuración

A continuación se muestran los parámetros que controlan el comportamiento en tiempo de ejecución de las funciones escalares asíncronas. Establézcalos antes de enviar una consulta o generar un plan de ejecución.

Parámetro

Valor predeterminado

Descripción

table.exec.async-scalar.max-concurrent-operations

10

Número máximo de operaciones asíncronas simultáneas permitidas para cada instancia de operador asíncrono. Un valor mayor incrementa la presión de concurrencia sobre el servicio externo.

table.exec.async-scalar.timeout

3 min

Tiempo de espera para procesar de forma asíncrona un solo registro, incluidas las reintentos.

table.exec.async-scalar.retry-strategy

FIXED_DELAY

FIXED_DELAY reintenta a intervalos fijos tras un error. NO_RETRY deshabilita los reintentos.

table.exec.async-scalar.retry-delay

100 ms

Tiempo de espera entre los reintentos de intervalo fijo. Este parámetro solo surte efecto cuando la estrategia de reintento es FIXED_DELAY.

table.exec.async-scalar.max-attempts

3

Número máximo de reintentos. Este parámetro solo surte efecto cuando la estrategia de reintento es FIXED_DELAY.

table.exec.async-scalar.output-mode

ORDERED

ORDERED conserva el orden de entrada. ALLOW_UNORDERED permite salidas sin orden específico cuando la entrada es un flujo de solo anexión. Este parámetro es compatible con VVR 11.8.0 y versiones posteriores.

Configurar la salida sin orden específico

De manera predeterminada, las funciones asíncronas se ejecutan de forma concurrente. Sin embargo, el resultado completado de un registro posterior solo se emite después de finalizar los registros anteriores. Cuando las latencias de las solicitudes externas varían significativamente y su lógica de negocio tolera resultados sin orden, habilite la salida sin orden específico para reducir las esperas provocadas por solicitudes lentas.

Nota

La invocación asíncrona y la salida sin orden específico son dos conceptos independientes. De forma predeterminada, una función escalar asíncrona se invoca de manera concurrente y emite los resultados en el orden de entrada. Una vez que se permite la salida sin orden específico, el sistema decide si la habilita según el tipo de cambio de los datos de entrada.

Agregue la siguiente configuración antes de ejecutar la consulta:

SET 'table.exec.async-scalar.output-mode' = 'ALLOW_UNORDERED';

Ejemplo de función escalar asíncrona personalizada

El siguiente ejemplo consulta nombres de productos mediante la interfaz HTTP de un servicio de productos. La función crea y reutiliza un HttpClient en open, llama a sendAsync() dentro de eval para enviar la solicitud y devuelve el resultado mediante la devolución de llamada de finalización. sendAsync() devuelve un CompletableFuture, lo cual resulta adecuado para vincular llamadas HTTP con funciones escalares asíncronas. Para obtener más información, consulte la documentación de JDK HttpClient.

El ejemplo utiliza un tiempo de espera de conexión de 3 segundos y un tiempo de espera de solicitud de 5 segundos. Estos valores son apropiados para consultas de nombres de productos con cuerpos de respuesta pequeños. Ajústelos según el tiempo de respuesta real de su interfaz.

package com.example.udf;

import org.apache.flink.table.functions.AsyncScalarFunction;
import org.apache.flink.table.functions.FunctionContext;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;

/**
 * Queries a product name through an asynchronous HTTP interface.
 *
 * <p>Interface contract: 200 returns a UTF-8 product name, and 404 indicates that the product does not exist.
 * This example suits read-only queries that return short text. Other status codes are treated as invocation failures and handed over to the engine.
 */
public class AsyncProductName extends AsyncScalarFunction {

    private transient HttpClient client;
    private transient URI serviceBaseUri;

    @Override
    public void open(FunctionContext context) {
        String baseUrl = context.getJobParameter("product.service.base-url", null);
        if (baseUrl == null || baseUrl.isBlank()) {
            throw new IllegalArgumentException("Configure product.service.base-url.");
        }
        serviceBaseUri = URI.create(baseUrl);
        if (!("https".equalsIgnoreCase(serviceBaseUri.getScheme())
                        || "http".equalsIgnoreCase(serviceBaseUri.getScheme()))
                || serviceBaseUri.getHost() == null
                || serviceBaseUri.getRawQuery() != null
                || serviceBaseUri.getRawFragment() != null
                || !baseUrl.endsWith("/")) {
            throw new IllegalArgumentException("The service URL must be an HTTP(S) base URL that ends with a forward slash (/).");
        }
        client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(3)).build();
    }

    @Override
    public boolean isDeterministic() {
        // Product information may change. The same ID may return different results at different times.
        return false;
    }

    public void eval(CompletableFuture<String> result, Long productId) {
        if (productId == null) {
            result.complete(null);
            return;
        }

        try {
            HttpRequest request =
                    HttpRequest.newBuilder(
                                    serviceBaseUri.resolve("products/" + productId + "/name"))
                            .timeout(Duration.ofSeconds(5))
                            .header("Accept", "text/plain")
                            .GET()
                            .build();

            client.sendAsync(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8))
                    .thenApply(
                            response -> {
                                if (response.statusCode() == 200) {
                                    return response.body();
                                }
                                if (response.statusCode() == 404) {
                                    return null;
                                }
                                throw new CompletionException(
                                        new IOException(
                                                "Failed to query the product. HTTP status code: " + response.statusCode()));
                            })
                    .whenComplete(
                            (name, error) -> {
                                if (error != null) {
                                    result.completeExceptionally(error);
                                } else {
                                    result.complete(name);
                                }
                            });
        } catch (RuntimeException e) {
            result.completeExceptionally(e);
        }
    }
}

Tenga en cuenta los siguientes puntos durante el desarrollo:

  • Llame a result.complete(value) para devolver un resultado. Si necesita devolver un valor NULL de SQL, llame a result.complete(null).

  • Cuando una llamada falle, invoque result.completeExceptionally(exception) para devolver la excepción. El motor la gestionará según la estrategia de reintento configurada.

  • eval retorna inmediatamente después de enviar la solicitud. Pase los resultados mediante la devolución de llamada de finalización de sendAsync(). No llame a los métodos bloqueantes send(), get() ni join(). Tampoco ejecute cálculos que consuman mucho tiempo dentro de eval o en la devolución de llamada.

  • Cada instancia de la función inicializa y reutiliza el cliente en open para evitar crear uno nuevo por cada registro. El HttpClient de JDK 11 y JDK 17 no tiene un método close() público, ya que el propio JDK gestiona sus recursos internos. Si utiliza otro cliente asíncrono que proporcione un método de cierre, libere el cliente en el método close de la función.

  • La información de los productos cambia con el tiempo, por lo que el ejemplo establece isDeterministic() en false. Cuando intervienen flujos de actualización, gestione los resultados no deterministas según la semántica de la consulta.

Desarrollar una UDSF

Flink proporciona ejemplos de UDF con el entorno de desarrollo preconfigurado. Estos ejemplos abarcan UDSF, funciones de agregación definidas por el usuario (UDAF) y funciones con valores de tabla definidas por el usuario (UDTF).
  1. Descargue y descomprima el ejemplo ASI_UDX_Demo en su computadora local. Tras la descompresión, se creará la carpeta ASI_UDX-main con la siguiente estructura:

    • pom.xml: archivo de configuración del proyecto de Maven. Define las coordenadas, dependencias, reglas de compilación y metadatos relacionados del proyecto.

    • \ASI_UDX-main\src\main\java\ASI_UDF\ASI_UDF.java: implementación de muestra en Java de la UDSF.

    ASI_UDX_Demo está alojado en un sitio web de terceros. Es posible que experimente errores o retrasos al acceder a él.
  2. En IntelliJ IDEA, haga clic en File > Open y seleccione la carpeta ASI_UDX-main.

  3. Abra \ASI_UDX-main\src\main\java\ASI_UDF\ASI_UDF.java y actualice el método eval() con su lógica personalizada. La implementación de muestra extrae caracteres desde la posición begin hasta la posición end de cada cadena de entrada:

    package ASI_UDF;
    
    import org.apache.flink.table.functions.ScalarFunction;
    
    public class ASI_UDF extends ScalarFunction {
        public String eval(String s, Integer begin, Integer end) {
            return s.substring(begin, end);
        }
    }
  4. Abra \ASI_UDX-main\pom.xml y configure las dependencias de Maven correspondientes a su versión de Flink. El siguiente ejemplo muestra las dependencias principales del paquete JAR para Flink 1.11. Si su UDSF no depende de paquetes JAR adicionales, omita este paso.

    <dependencies>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-java_2.12</artifactId>
            <version>1.11.0</version>
            <!--<scope>provided</scope>-->
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-table</artifactId>
            <version>1.11.0</version>
            <type>pom</type>
            <!--<scope>provided</scope>-->
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-core</artifactId>
            <version>1.11.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-table-common</artifactId>
            <version>1.11.0</version>
        </dependency>
    </dependencies>
  5. En el directorio que contiene el archivo pom.xml, ejecute el siguiente comando para compilar el proyecto:

    mvn package -Dcheckstyle.skip

    Una vez finalizada la compilación, se generará el archivo ASI_UDX-1.0-SNAPSHOT.jar en el directorio \ASI_UDX-main\target\.

Registrar una UDSF

Para registrar el paquete JAR como una UDSF, consulte Manage user-defined functions (UDFs).

Usar una UDSF

Después de registrarla, llame a la UDSF en un trabajo de Flink SQL.

  1. Cree un trabajo de Flink SQL. Para obtener instrucciones, consulte Job development map. El siguiente código SQL de ejemplo extrae los caracteres de la segunda a la cuarta posición de la cadena en el campo a de ASI_UDSF_Source y escribe los resultados en ASI_UDSF_Sink:

    CREATE TEMPORARY TABLE ASI_UDSF_Source (
      a VARCHAR,
      b INT,
      c INT
    ) WITH (
      'connector' = 'datagen'
    );
    
    CREATE TEMPORARY TABLE ASI_UDSF_Sink (
      a VARCHAR
    ) WITH (
      'connector' = 'blackhole'
    );
    
    INSERT INTO ASI_UDSF_Sink
    SELECT ASI_UDSF(a, 2, 4)
    FROM ASI_UDSF_Source;
  2. En la página Operation Center > Job O&M, busque su trabajo y haga clic en Start dentro de la columna Actions. Una vez iniciado el trabajo, los caracteres de la segunda a la cuarta posición del campo a de cada fila en ASI_UDSF_Source se insertarán en ASI_UDSF_Sink.