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,
Integeralih-alihint) untuk menangani inputNULLdengan 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 |
|
|
|
Jumlah maksimum operasi asinkron konkuren yang diizinkan untuk setiap instans operator asinkron. Nilai yang lebih besar meningkatkan tekanan konkurensi pada layanan eksternal. |
|
|
|
Periode timeout untuk memproses satu catatan secara asinkron, termasuk percobaan ulang. |
|
|
|
|
|
|
|
Waktu tunggu antar percobaan ulang dengan interval tetap. Parameter ini hanya berlaku ketika strategi percobaan ulang adalah |
|
|
|
Jumlah maksimum percobaan ulang. Parameter ini hanya berlaku ketika strategi percobaan ulang adalah |
|
|
|
|
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.
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 mengembalikanNULLSQL, panggilresult.complete(null). -
Saat pemanggilan gagal, panggil
result.completeExceptionally(exception)untuk mengembalikan pengecualian. Mesin kemudian akan menanganinya berdasarkan strategi percobaan ulang. -
evallangsung mengembalikan setelah permintaan dikirim. Kirim hasil melalui callback penyelesaiansendAsync(). Jangan memanggil metode blocking sepertisend(),get(), ataujoin(), atau lakukan komputasi yang memakan waktu dalamevalatau dalam callback. -
Setiap instans fungsi menginisialisasi dan menggunakan kembali klien dalam metode
openuntuk menghindari pembuatan klien untuk setiap catatan.HttpClientdi JDK 11 dan JDK 17 tidak memiliki metodeclose()publik, dan sumber daya internalnya dikelola oleh JDK. Jika Anda menggunakan klien asinkron lain yang menyediakan metode close, lepaskan klien tersebut dalam metodeclosefungsi. -
Informasi produk berubah seiring waktu, sehingga contoh ini mengatur
isDeterministic()menjadifalse. 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).
-
Unduh dan ekstrak contoh ASI_UDX_Demo ke mesin lokal Anda. Setelah diekstrak, folder
ASI_UDX-maindibuat 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.
-
-
Di IntelliJ IDEA, klik File > Open dan pilih folder
ASI_UDX-main. -
Buka
\ASI_UDX-main\src\main\java\ASI_UDF\ASI_UDF.javadan perbarui metodeeval()dengan logika kustom Anda. Implementasi contoh mengekstraksi karakter dari posisibeginhingga posisienddari 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); } } -
Buka
\ASI_UDX-main\pom.xmldan 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> -
Di direktori yang berisi file
pom.xml, jalankan perintah berikut untuk mengemas proyek:mvn package -Dcheckstyle.skipSetelah build selesai,
ASI_UDX-1.0-SNAPSHOT.jardibuat 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.
-
Buat pekerjaan Flink SQL. Untuk panduan, lihat Job development map. SQL contoh berikut mengekstraksi karakter dari posisi kedua hingga keempat dari string di bidang
apadaASI_UDSF_Sourcedan menulis hasilnya keASI_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; -
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
apada setiap barisASI_UDSF_Sourcedimasukkan keASI_UDSF_Sink.