All Products
Search
Document Center

Realtime Compute for Apache Flink:Java UDSFs

Last Updated:Sep 22, 2026

Fungsi skalar yang didefinisikan pengguna (user-defined scalar function/UDSF) memetakan nol, satu, atau beberapa nilai skalar ke nilai skalar baru, menghasilkan hubungan satu-ke-satu antara baris input dan nilai output. Gunakan UDSF ketika fungsi bawaan Flink SQL tidak dapat merepresentasikan logika kustom Anda.

Untuk ikhtisar semua jenis fungsi yang didefinisikan pengguna (user-defined function/UDF), lihat User-defined functions.

Cara kerja

Sebuah UDSF memperluas kelas ScalarFunction Apache Flink dan mengimplementasikan satu atau beberapa metode eval(). Flink memanggil eval() sekali per baris input dan menggunakan nilai kembali sebagai skalar output.

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

Persyaratan metode eval():

  • Harus dideklarasikan sebagai public

  • Mendukung method overloading—definisikan beberapa tanda tangan eval() untuk tipe input yang berbeda

  • Mendukung varargs (misalnya, eval(Integer...))

  • Gunakan tipe primitif terbungkus (boxed primitives) (misalnya, Integer alih-alih int) untuk menangani input NULL dengan benar

Prasyarat

Sebelum memulai, pastikan Anda telah memiliki:

  • IntelliJ IDEA terinstal

  • Apache Maven terinstal

  • Akses ke ruang kerja Realtime Compute for Apache Flink

Fungsi skalar asinkron (AsyncScalarFunction)

Berbeda dengan fungsi skalar biasa, fungsi skalar asinkron kustom harus memperluas AsyncScalarFunction dan mengembalikan hasil melalui CompletableFuture.

Gunakan fungsi skalar asinkron ketika fungsi tersebut melakukan operasi yang melibatkan penundaan I/O, seperti mengkueri database eksternal atau memanggil antarmuka HTTP. Fungsi ini memproses beberapa catatan secara konkuren, sehingga mengurangi dampak penundaan terhadap throughput pekerjaan. Untuk operasi yang tidak menunggu layanan eksternal, seperti pemotongan string dan komputasi numerik, fungsi skalar sinkron biasanya sudah cukup.

Parameter konfigurasi

Parameter berikut mengontrol perilaku waktu proses fungsi skalar asinkron. Atur parameter ini sebelum Anda mengirimkan kueri atau menghasilkan rencana eksekusi.

Parameter

Nilai default

Deskripsi

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

10

Jumlah maksimum operasi asinkron konkuren yang diizinkan untuk setiap instans operator asinkron. Nilai yang lebih besar meningkatkan tekanan konkurensi pada layanan eksternal.

table.exec.async-scalar.timeout

3 min

Periode timeout untuk memproses satu catatan secara asinkron, termasuk percobaan ulang.

table.exec.async-scalar.retry-strategy

FIXED_DELAY

FIXED_DELAY melakukan percobaan ulang dengan interval tetap setelah kegagalan. NO_RETRY menonaktifkan percobaan ulang.

table.exec.async-scalar.retry-delay

100 ms

Waktu tunggu antar percobaan ulang dengan interval tetap. Parameter ini hanya berlaku ketika strategi percobaan ulang adalah FIXED_DELAY.

table.exec.async-scalar.max-attempts

3

Jumlah maksimum percobaan ulang. Parameter ini hanya berlaku ketika strategi percobaan ulang adalah FIXED_DELAY.

table.exec.async-scalar.output-mode

ORDERED

ORDERED mempertahankan urutan input. ALLOW_UNORDERED mengizinkan output tidak terurut ketika input merupakan aliran append-only. Parameter ini didukung mulai VVR 11.8.0 dan versi selanjutnya.

Konfigurasi output tidak terurut

Secara default, fungsi asinkron dijalankan secara konkuren, tetapi hasil yang telah selesai untuk catatan berikutnya tetap dipancarkan hanya setelah catatan sebelumnya selesai. Ketika latensi permintaan eksternal sangat bervariasi dan bisnis Anda dapat menerima hasil yang tidak terurut, aktifkan output tidak terurut untuk mengurangi waktu tunggu akibat permintaan lambat.

Catatan

Pemanggilan asinkron dan output tidak terurut adalah dua konsep independen. Secara default, fungsi skalar asinkron dipanggil secara konkuren dan memancarkan hasil sesuai urutan input. Setelah Anda mengizinkan output tidak terurut, sistem akan memutuskan apakah akan mengaktifkannya berdasarkan jenis perubahan data input.

Tambahkan konfigurasi berikut sebelum menjalankan kueri:

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

Contoh fungsi skalar asinkron kustom

Contoh berikut mengkueri nama produk melalui antarmuka HTTP layanan produk. Fungsi ini membuat dan menggunakan kembali HttpClient dalam open, memanggil sendAsync() dalam eval untuk mengirim permintaan, dan mengembalikan hasil melalui callback penyelesaian. sendAsync() mengembalikan CompletableFuture, yang cocok untuk menghubungkan panggilan HTTP ke fungsi skalar asinkron. Untuk informasi lebih lanjut, lihat JDK HttpClient documentation.

Contoh ini menggunakan timeout koneksi 3 detik dan timeout permintaan 5 detik, yang sesuai untuk kueri nama produk dengan respons berukuran kecil. Sesuaikan nilai-nilai tersebut berdasarkan waktu respons aktual antarmuka Anda.

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;

/**
 * Mengkueri nama produk melalui antarmuka HTTP asinkron.
 *
 * <p>Kontrak antarmuka: kode status 200 mengembalikan nama produk dalam UTF-8, dan 404 menunjukkan bahwa produk tidak ada.
 * Contoh ini cocok untuk kueri read-only yang mengembalikan teks pendek. Kode status lain dianggap sebagai kegagalan pemanggilan dan diserahkan ke mesin.
 */
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("Konfigurasikan 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("URL layanan harus berupa URL dasar HTTP(S) yang diakhiri dengan garis miring (/).");
        }
        client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(3)).build();
    }

    @Override
    public boolean isDeterministic() {
        // Informasi produk dapat berubah. ID yang sama mungkin mengembalikan hasil berbeda pada waktu berbeda.
        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(
                                                "Gagal mengkueri produk. Kode status HTTP: " + response.statusCode()));
                            })
                    .whenComplete(
                            (name, error) -> {
                                if (error != null) {
                                    result.completeExceptionally(error);
                                } else {
                                    result.complete(name);
                                }
                            });
        } catch (RuntimeException e) {
            result.completeExceptionally(e);
        }
    }
}

Perhatikan hal-hal berikut selama pengembangan:

  • Panggil result.complete(value) untuk mengembalikan hasil. Untuk mengembalikan NULL SQL, panggil result.complete(null).

  • Saat pemanggilan gagal, panggil result.completeExceptionally(exception) untuk mengembalikan pengecualian. Mesin kemudian akan menanganinya berdasarkan strategi percobaan ulang.

  • eval langsung mengembalikan setelah permintaan dikirim. Kirim hasil melalui callback penyelesaian sendAsync(). Jangan memanggil metode blocking seperti send(), get(), atau join(), atau lakukan komputasi yang memakan waktu dalam eval atau dalam callback.

  • Setiap instans fungsi menginisialisasi dan menggunakan kembali klien dalam metode open untuk menghindari pembuatan klien untuk setiap catatan. HttpClient di JDK 11 dan JDK 17 tidak memiliki metode close() publik, dan sumber daya internalnya dikelola oleh JDK. Jika Anda menggunakan klien asinkron lain yang menyediakan metode close, lepaskan klien tersebut dalam metode close fungsi.

  • Informasi produk berubah seiring waktu, sehingga contoh ini mengatur isDeterministic() menjadi false. Saat aliran pembaruan terlibat, tangani hasil non-deterministik berdasarkan semantik kueri.

Kembangkan UDSF

Flink menyediakan contoh UDF yang telah dikonfigurasi lingkungan pengembangannya. Contoh tersebut mencakup UDSF, fungsi agregat yang didefinisikan pengguna (UDAF), dan fungsi bernilai tabel yang didefinisikan pengguna (UDTF).
  1. Unduh dan ekstrak contoh ASI_UDX_Demo ke mesin lokal Anda. Setelah diekstrak, folder ASI_UDX-main dibuat dengan struktur berikut:

    • pom.xml: File konfigurasi proyek Maven. File ini mendefinisikan koordinat proyek, dependensi, aturan build, dan metadata terkait.

    • \ASI_UDX-main\src\main\java\ASI_UDF\ASI_UDF.java: Implementasi Java contoh dari UDSF.

    ASI_UDX_Demo dihosting di website pihak ketiga. Anda mungkin mengalami kegagalan akses atau penundaan.
  2. Di IntelliJ IDEA, klik File > Open dan pilih folder ASI_UDX-main.

  3. Buka \ASI_UDX-main\src\main\java\ASI_UDF\ASI_UDF.java dan perbarui metode eval() dengan logika kustom Anda. Implementasi contoh mengekstraksi karakter dari posisi begin hingga posisi end dari setiap string input:

    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. Buka \ASI_UDX-main\pom.xml dan konfigurasikan dependensi Maven untuk versi Flink Anda. Contoh berikut menunjukkan dependensi paket JAR utama untuk Flink 1.11. Jika UDSF Anda tidak bergantung pada paket JAR tambahan, lewati langkah ini.

    <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. Di direktori yang berisi file pom.xml, jalankan perintah berikut untuk mengemas proyek:

    mvn package -Dcheckstyle.skip

    Setelah build selesai, ASI_UDX-1.0-SNAPSHOT.jar dibuat di direktori \ASI_UDX-main\target\.

Daftarkan UDSF

Untuk mendaftarkan paket JAR sebagai UDSF, lihat Manage user-defined functions (UDFs).

Gunakan UDSF

Setelah didaftarkan, panggil UDSF dalam pekerjaan Flink SQL.

  1. Buat pekerjaan Flink SQL. Untuk panduan, lihat Job development map. SQL contoh berikut mengekstraksi karakter dari posisi kedua hingga keempat dari string di bidang a pada ASI_UDSF_Source dan menulis hasilnya ke 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. Di halaman Operation Center > Job O&M, temukan pekerjaan Anda dan klik Start di kolom Actions. Setelah pekerjaan dimulai, karakter dari posisi kedua hingga keempat dari bidang a pada setiap baris ASI_UDSF_Source dimasukkan ke ASI_UDSF_Sink.