Flink 作为一款强大的流批一体数据处理引擎,其灵活性和扩展性在很大程度上依赖于用户自定义函数(User-Defined Functions, UDF)。UDF 允许开发者根据业务需求扩展 Flink 的核心功能,实现复杂的数据转换、聚合或分析。本文将系统性地讲解 Flink UDF 的类型、实现方式、优化技巧及实际应用场景,涵盖从基础到高级的完整知识体系。
1.1 什么是 UDF?
UDF 是用户根据业务逻辑自定义的函数,用于在数据处理过程中执行特定的操作。Flink 支持多种类型的 UDF,包括标量函数(ScalarFunction)、表函数(TableFunction)、聚合函数(AggregateFunction)等,覆盖了从单行数据转换到多行数据生成、分组聚合等多种场景。
1.2 为什么需要 UDF?
定义:一对一转换,输入一行数据返回单个值。
典型场景:字符串处理、数值计算、类型转换。
实现步骤:
org.apache.flink.table.functions.ScalarFunction
。eval()
方法,支持重载多个参数类型。示例(Java):
public class ToUpperCase extends ScalarFunction {
public String eval(String input) {
return input.toUpperCase();
}
}
// 注册并使用
tableEnv.createTemporaryFunction("to_upper", ToUpperCase.class);
tableEnv.sqlQuery("SELECT to_upper(name) FROM Users");
Scala 实现:
class ToUpperCase extends ScalarFunction {
def eval(input: String): String = input.toUpperCase
}
定义:一对多转换,输入一行数据返回多行结果(类似 SQL 的 LATERAL TABLE)。
典型场景:解析 JSON 数组、字符串拆分、行转列。
实现步骤:
org.apache.flink.table.functions.TableFunction
。eval()
方法,通过 collect(T)
输出多行。CROSS JOIN LATERAL TABLE
或 LEFT JOIN LATERAL TABLE
调用。示例(拆分字符串):
public class SplitString extends TableFunction<String> {
public void eval(String input, String delimiter) {
for (String s : input.split(delimiter)) {
collect(s);
}
}
}
// SQL 调用
tableEnv.sqlQuery(
"SELECT name, word FROM Users, LATERAL TABLE(split_string(description, ' '))"
);
注意事项:
getResultType()
(或在 Scala 中使用注解 @DataTypeHint
)。定义:多对一转换,基于一组数据计算聚合结果(如 SUM、COUNT)。
核心概念:
示例(自定义平均值):
public class CustomAvg extends AggregateFunction<Double, Tuple2<Long, Double>> {
public Tuple2<Long, Double> createAccumulator() {
return Tuple2.of(0L, 0.0);
}
public void accumulate(Tuple2<Long, Double> acc, Double value) {
acc.f0 += 1;
acc.f1 += value;
}
public Double getValue(Tuple2<Long, Double> acc) {
return acc.f1 / acc.f0;
}
}
// 注册并使用
tableEnv.createTemporaryFunction("custom_avg", CustomAvg.class);
tableEnv.sqlQuery("SELECT custom_avg(salary) FROM Employees");
高级用法:
retract()
(回撤数据)和 merge()
(会话窗口合并)。定义:多对多聚合,输出多行结果(如 Top N)。
实现方法:
TableAggregateFunction
。emitValue()
或 emitUpdateWithRetract()
输出结果。示例(求 Top 2 分数):
public class Top2 extends TableAggregateFunction<Tuple2<Double, Integer>, Tuple2<Double, Double>> {
public void accumulate(Tuple2<Double, Double> acc, Double value) {
if (value > acc.f0) {
acc.f1 = acc.f0;
acc.f0 = value;
} else if (value > acc.f1) {
acc.f1 = value;
}
}
public void emitValue(Tuple2<Double, Double> acc, Collector<Tuple2<Double, Integer>> out) {
out.collect(Tuple2.of(acc.f0, 1));
out.collect(Tuple2.of(acc.f1, 2));
}
}
调用方式:需使用 flatAggregate
(Table API)或自定义 SQL 解析。
应用场景:调用高延迟外部服务(如 HTTP API、数据库)时避免阻塞。
实现方法:
AsyncFunction
,使用 asyncInvoke()
结合 CompletableFuture
。示例(异步查询外部服务):
public class AsyncLookup extends AsyncFunction<String, String> {
@Override
public void asyncInvoke(String key, ResultFuture<String> resultFuture) {
CompletableFuture.supplyAsync(() -> externalService.query(key))
.thenAccept(result -> resultFuture.complete(Collections.singleton(result)));
}
}
优化建议:
背景:在批处理中提升 CPU 缓存利用率,减少虚函数调用。
实现方法:
VectorizedScalarFunction
,处理 ColumnVector
对象。TableConfigOptions#SQL_EXEC_VECTORIZED_ENABLED
。适用场景:高吞吐批处理作业。
4.1 序列化优化
Types.POJO
)替代 Java 原生序列化。4.2 类型推导优化
@DataTypeHint
或 @FunctionHint
显式指定输入/输出类型,避免反射开销。4.3 资源管理
open()
方法中初始化资源(如数据库连接),在 close()
中释放。MemorySegment
)减少 GC 压力。4.4 代码生成
5.1 数据清洗
public class SanitizeInput extends ScalarFunction {
public String eval(String input) {
return input.replaceAll("[^a-zA-Z0-9]", "");
}
}
5.2 实时统计分析
5.3 复杂事件处理(CEP)
5.4 机器学习集成
6.1 状态管理
ValueState
)。6.2 线程安全
6.3 异常处理
Collector
输出错误信息,避免作业失败。6.4 测试策略