Flink自定义函数

一、UDF 核心原理

Flink 自定义函数(UDF)是扩展 Table API/SQL 能力的核心机制,允许将自定义逻辑嵌入查询。其设计遵循以下原则:

1. 函数类型体系

类型 输入输出关系 核心用途
标量函数(ScalarFunction) 0~N 个标量 → 1 个标量 字段转换、值计算
表值函数(TableFunction) 0~N 个标量 → 多行多列 数据拆分、关联外部数据
聚合函数(AggregateFunction) 多行标量 → 1 个标量 自定义聚合(如加权平均)
表值聚合函数(TableAggregateFunction) 多行标量 → 多行多列 分组TopN、分桶统计等
异步表值函数 异步查询外部系统 → 多行多列 高效关联外部数据库/API

2. 类型系统

  • 标量/表值函数使用新数据类型系统 (基于DataTypes
  • 聚合函数仍使用旧类型系统 (基于TypeInformation
  • 类型推导:默认通过反射获取,复杂场景可通过@DataTypeHint@FunctionHint注解显式指定

3. 执行逻辑

  • 核心是求值方法 (如eval()accumulate()),定义数据处理逻辑
  • 生命周期:open()初始化 → 求值方法调用 → close()资源清理
  • 确定性:通过isDeterministic()声明是否返回确定结果(影响优化策略)

二、快速上手实战

1. 标量函数(ScalarFunction)

作用:对输入标量做转换计算(如字符串处理、格式转换)

实现步骤

  1. 继承ScalarFunction,实现eval()方法

    java 复制代码
    public class HashFunction extends ScalarFunction {
        // 输入任意类型,返回哈希值
        public int eval(@DataTypeHint(inputGroup = InputGroup.ANY) Object o) {
            return o.hashCode();
        }
    }
  2. 注册与调用

    java 复制代码
    // 注册
    tableEnv.createTemporarySystemFunction("HashFunc", HashFunction.class);
    // Table API 调用
    table.select(call("HashFunc", $("field")));
    // SQL 调用
    tableEnv.sqlQuery("SELECT HashFunc(field) FROM t");

2. 表值函数(TableFunction)

作用:将单行输入拆分为多行输出(如字符串按分隔符拆分)

实现步骤

  1. 继承TableFunction<T>,通过collect()输出结果

    java 复制代码
    @FunctionHint(output = @DataTypeHint("ROW<word STRING, len INT>"))
    public class SplitFunction extends TableFunction<Row> {
        public void eval(String str) {
            for (String s : str.split(" ")) {
                collect(Row.of(s, s.length())); // 输出每行数据
            }
        }
    }
  2. 注册与调用

    java 复制代码
    tableEnv.createTemporarySystemFunction("SplitFunc", SplitFunction.class);
    // 关联查询(LATERAL JOIN)
    tableEnv.sqlQuery("""
        SELECT t.id, s.word, s.len 
        FROM t, LATERAL TABLE(SplitFunc(t.content)) AS s(word, len)
    """);

3. 聚合函数(AggregateFunction)

作用:多行数据聚合为单个值(如自定义平均值、求和逻辑)

实现步骤

  1. 定义累加器(存储中间结果)

    java 复制代码
    public class WeightedAvgAccum {
        public long sum = 0;   // 加权和
        public int count = 0; // 权重总和
    }
  2. 继承AggregateFunction,实现核心方法

    java 复制代码
    public class WeightedAvg extends AggregateFunction<Long, WeightedAvgAccum> {
        @Override
        public WeightedAvgAccum createAccumulator() { return new WeightedAvgAccum(); }
        
        // 累加逻辑
        public void accumulate(WeightedAvgAccum acc, long value, int weight) {
            acc.sum += value * weight;
            acc.count += weight;
        }
        
        // 最终结果计算
        @Override
        public Long getValue(WeightedAvgAccum acc) {
            return acc.count == 0 ? null : acc.sum / acc.count;
        }
    }
  3. 注册与调用

    java 复制代码
    tableEnv.createTemporarySystemFunction("WeightedAvg", WeightedAvg.class);
    tableEnv.sqlQuery("""
        SELECT user, WeightedAvg(score, weight) 
        FROM scores 
        GROUP BY user
    """);

4. 表值聚合函数(TableAggregateFunction)

作用:多行数据聚合为多行结果(如分组取TopN)

实现步骤

  1. 定义累加器(存储中间状态)

    java 复制代码
    public class Top2Accum {
        public int first;  // 第一名
        public int second; // 第二名
    }
  2. 继承TableAggregateFunction,实现核心方法

    java 复制代码
    public class Top2 extends TableAggregateFunction<Tuple2<Integer, Integer>, Top2Accum> {
        @Override
        public Top2Accum createAccumulator() {
            Top2Accum acc = new Top2Accum();
            acc.first = Integer.MIN_VALUE;
            acc.second = Integer.MIN_VALUE;
            return acc;
        }
        
        // 累加逻辑
        public void accumulate(Top2Accum acc, int value) {
            if (value > acc.first) {
                acc.second = acc.first;
                acc.first = value;
            } else if (value > acc.second) {
                acc.second = value;
            }
        }
        
        // 输出结果
        public void emitValue(Top2Accum acc, Collector<Tuple2<Integer, Integer>> out) {
            out.collect(Tuple2.of(acc.first, 1));
            out.collect(Tuple2.of(acc.second, 2));
        }
    }
  3. 注册与调用

    java 复制代码
    tableEnv.createTemporarySystemFunction("Top2", Top2.class);
    // Table API 调用(SQL暂不支持)
    table.groupBy($("group"))
         .flatAggregate(call(Top2.class, $("value")).as("val", "rank"))
         .select($("group"), $("val"), $("rank"));

三、关键技巧

  1. 类型注解 :复杂类型用@DataTypeHint指定,例如:

    java 复制代码
    @DataTypeHint("DECIMAL(12, 3)") // 声明 decimal 精度
    public BigDecimal eval(double a) { ... }
  2. 命名参数 :通过@ArgumentHint指定参数名,支持 SQL 中按名传参:

    java 复制代码
    public String eval(
        @ArgumentHint(name = "content") String s,
        @ArgumentHint(name = "begin") int b
    ) { ... }
    // SQL 调用:SELECT func(content => 'abc', begin => 1)
  3. 确定性声明:非确定性函数(如随机数、当前时间)需重写:

    java 复制代码
    @Override
    public boolean isDeterministic() { return false; }

四、常见问题

  • 注册方式 :临时注册(createTemporarySystemFunction)仅当前会话有效,永久注册需结合 Catalog
  • 权限控制:UDF 可访问外部资源(如数据库连接),需确保执行环境有对应权限
  • 性能优化 :聚合函数尽量实现merge()方法,支持两阶段聚合优化

通过上述步骤,可快速实现各类自定义逻辑,扩展 Flink 处理能力。核心是理解不同函数的输入输出关系,以及累加器(聚合函数)的设计逻辑。

相关推荐
武子康4 小时前
大数据-239 离线数仓 - 广告业务实战:Flume 导入日志到 HDFS,并完成 Hive ODS/DWD 分层加载
大数据·后端·apache hive
字节跳动数据平台1 天前
代码量减少 70%、GPU 利用率达 95%:火山引擎多模态数据湖如何释放模思智能的算法生产力
大数据
得物技术1 天前
深入剖析Spark UI界面:参数与界面详解|得物技术
大数据·后端·spark
大大大大晴天1 天前
Flink生产问题排障-HBase NotServingRegionException
flink·hbase
武子康1 天前
大数据-238 离线数仓 - 广告业务 Hive分析实战:ADS 点击率、购买率与 Top100 排名避坑
大数据·后端·apache hive
武子康2 天前
大数据-237 离线数仓 - Hive 广告业务实战:ODS→DWD 事件解析、广告明细与转化分析落地
大数据·后端·apache hive
大大大大晴天2 天前
Flink生产问题排障-Kryo serializer scala extensions are not available
大数据·flink
武子康4 天前
大数据-236 离线数仓 - 会员指标验证、DataX 导出与广告业务 ODS/DWD/ADS 全流程
大数据·后端·apache hive
武子康5 天前
大数据-235 离线数仓 - 实战:Flume+HDFS+Hive 搭建 ODS/DWD/DWS/ADS 会员分析链路
大数据·后端·apache hive
DianSan_ERP6 天前
电商API接口全链路监控:构建坚不可摧的线上运维防线
大数据·运维·网络·人工智能·git·servlet