You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Java编写继承AbstractGenericUDAFResolver的Hive UDAF函数parsed_user_agent

没问题,我来帮你把这个Hive UDAF的实现逻辑理清楚——你已经掌握了User-Agent的解析方法,那核心就是搞定UDAF的输入输出定义、聚合流程这几块了。下面一步步来:

首先明确输入输出的类型定义

你的需求是按city分组,对每组的User_Agent字符串进行解析,最终返回包含操作系统、浏览器、设备信息的聚合结果。这里我们可以把输出定义为一个Hive StructType,里面包含三个Map(用来统计每组中各类型的出现次数,比如每个操作系统被多少用户使用):

  • 输入:单个String类型的User_Agent字段
  • 输出:struct<os_stats:map<string,int>, browser_stats:map<string,int>, device_stats:map<string,int>>

第一步:实现继承AbstractGenericUDAFResolver的Resolver类

这个类的作用是向Hive声明UDAF支持的输入输出类型,核心是重写getEvaluator方法,返回我们自定义的聚合逻辑实现类。

import org.apache.hadoop.hive.ql.exec.UDAFArgumentException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.AbstractGenericUDAFResolver;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator;
import org.apache.hadoop.hive.serde2.objectinspector.*;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.IntObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.StringObjectInspector;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class ParsedUserAgentUDAF extends AbstractGenericUDAFResolver {

    @Override
    public GenericUDAFEvaluator getEvaluator(ObjectInspector[] parameters) throws UDAFArgumentException {
        // 校验输入参数:只能是一个String类型的字段
        if (parameters.length != 1 || !(parameters[0] instanceof StringObjectInspector)) {
            throw new UDAFArgumentException("UDAF parsed_user_agent only accepts a single String parameter");
        }
        return new ParsedUserAgentEvaluator();
    }

    // 内部类实现具体的聚合逻辑
    public static class ParsedUserAgentEvaluator extends GenericUDAFEvaluator {
        // 定义输入、中间状态、输出的ObjectInspector
        private StringObjectInspector inputUaInspector;
        private StructObjectInspector intermediateInspector;
        private StructObjectInspector finalOutputInspector;

        // 自定义的聚合状态类:用来存三个统计Map
        public static class AggregationState {
            public Map<String, Integer> osMap = new HashMap<>();
            public Map<String, Integer> browserMap = new HashMap<>();
            public Map<String, Integer> deviceMap = new HashMap<>();
        }

        @Override
        public ObjectInspector init(Mode mode, ObjectInspector[] parameters) throws HiveException {
            super.init(mode, parameters);
            switch (mode) {
                case PARTIAL1:
                case PARTIAL2:
                    // 初始化中间状态的ObjectInspector
                    intermediateInspector = ObjectInspectorFactory.getStandardStructObjectInspector(
                        Arrays.asList("os_map", "browser_map", "device_map"),
                        Arrays.asList(
                            ObjectInspectorFactory.getStandardMapObjectInspector(
                                PrimitiveObjectInspectorFactory.javaStringObjectInspector,
                                PrimitiveObjectInspectorFactory.javaIntObjectInspector
                            ),
                            ObjectInspectorFactory.getStandardMapObjectInspector(
                                PrimitiveObjectInspectorFactory.javaStringObjectInspector,
                                PrimitiveObjectInspectorFactory.javaIntObjectInspector
                            ),
                            ObjectInspectorFactory.getStandardMapObjectInspector(
                                PrimitiveObjectInspectorFactory.javaStringObjectInspector,
                                PrimitiveObjectInspectorFactory.javaIntObjectInspector
                            )
                        )
                    );
                    return intermediateInspector;
                case FINAL:
                    // 初始化最终输出的ObjectInspector
                    finalOutputInspector = ObjectInspectorFactory.getStandardStructObjectInspector(
                        Arrays.asList("os_stats", "browser_stats", "device_stats"),
                        Arrays.asList(
                            ObjectInspectorFactory.getStandardMapObjectInspector(
                                PrimitiveObjectInspectorFactory.javaStringObjectInspector,
                                PrimitiveObjectInspectorFactory.javaIntObjectInspector
                            ),
                            ObjectInspectorFactory.getStandardMapObjectInspector(
                                PrimitiveObjectInspectorFactory.javaStringObjectInspector,
                                PrimitiveObjectInspectorFactory.javaIntObjectInspector
                            ),
                            ObjectInspectorFactory.getStandardMapObjectInspector(
                                PrimitiveObjectInspectorFactory.javaStringObjectInspector,
                                PrimitiveObjectInspectorFactory.javaIntObjectInspector
                            )
                        )
                    );
                    return finalOutputInspector;
                case COMPLETE:
                    inputUaInspector = (StringObjectInspector) parameters[0];
                    return finalOutputInspector;
                default:
                    throw new IllegalArgumentException("Unsupported mode: " + mode);
            }
        }

        @Override
        public AggregationState getNewAggregationBuffer() throws HiveException {
            return new AggregationState();
        }

        @Override
        public void reset(AggregationBuffer agg) throws HiveException {
            AggregationState state = (AggregationState) agg;
            state.osMap.clear();
            state.browserMap.clear();
            state.deviceMap.clear();
        }

        @Override
        public void iterate(AggregationBuffer agg, Object[] parameters) throws HiveException {
            if (parameters[0] == null) {
                return;
            }
            AggregationState state = (AggregationState) agg;
            String userAgent = inputUaInspector.getPrimitiveJavaObject(parameters[0]);
            
            // 这里调用你已经掌握的User-Agent解析方法,得到os、browser、device
            String os = parseOs(userAgent);
            String browser = parseBrowser(userAgent);
            String device = parseDevice(userAgent);

            // 更新统计Map
            updateMap(state.osMap, os);
            updateMap(state.browserMap, browser);
            updateMap(state.deviceMap, device);
        }

        @Override
        public Object terminatePartial(AggregationBuffer agg) throws HiveException {
            AggregationState state = (AggregationState) agg;
            return new Object[]{state.osMap, state.browserMap, state.deviceMap};
        }

        @Override
        public void merge(AggregationBuffer agg, Object partial) throws HiveException {
            if (partial == null) {
                return;
            }
            AggregationState state = (AggregationState) agg;
            // 从partial中取出各Map
            Map<String, Integer> partialOsMap = (Map<String, Integer>) intermediateInspector.getStructFieldData(partial, intermediateInspector.getStructFieldRef("os_map"));
            Map<String, Integer> partialBrowserMap = (Map<String, Integer>) intermediateInspector.getStructFieldData(partial, intermediateInspector.getStructFieldRef("browser_map"));
            Map<String, Integer> partialDeviceMap = (Map<String, Integer>) intermediateInspector.getStructFieldData(partial, intermediateInspector.getStructFieldRef("device_map"));

            // 合并到全局状态
            mergeMaps(state.osMap, partialOsMap);
            mergeMaps(state.browserMap, partialBrowserMap);
            mergeMaps(state.deviceMap, partialDeviceMap);
        }

        @Override
        public Object terminate(AggregationBuffer agg) throws HiveException {
            AggregationState state = (AggregationState) agg;
            return new Object[]{state.osMap, state.browserMap, state.deviceMap};
        }

        // 工具方法:更新单个Map的计数
        private void updateMap(Map<String, Integer> map, String key) {
            if (key == null || key.isEmpty()) {
                key = "Unknown";
            }
            map.put(key, map.getOrDefault(key, 0) + 1);
        }

        // 工具方法:合并两个统计Map
        private void mergeMaps(Map<String, Integer> target, Map<String, Integer> source) {
            for (Map.Entry<String, Integer> entry : source.entrySet()) {
                target.put(entry.getKey(), target.getOrDefault(entry.getKey(), 0) + entry.getValue());
            }
        }

        // 这里替换成你自己的User-Agent解析实现
        private String parseOs(String userAgent) {
            // 示例逻辑:根据User-Agent字符串提取操作系统
            if (userAgent.contains("Windows")) {
                return "Windows";
            } else if (userAgent.contains("Mac OS")) {
                return "Mac OS";
            } else if (userAgent.contains("Linux")) {
                return "Linux";
            } else {
                return "Unknown";
            }
        }

        // 替换成你的浏览器解析逻辑
        private String parseBrowser(String userAgent) {
            if (userAgent.contains("Chrome")) {
                return "Chrome";
            } else if (userAgent.contains("Firefox")) {
                return "Firefox";
            } else if (userAgent.contains("Googlebot")) {
                return "Googlebot";
            } else {
                return "Unknown";
            }
        }

        // 替换成你的设备解析逻辑
        private String parseDevice(String userAgent) {
            if (userAgent.contains("Mobile")) {
                return "Mobile";
            } else if (userAgent.contains("Tablet")) {
                return "Tablet";
            } else {
                return "Desktop";
            }
        }
    }
}

第二步:编译注册UDAF

  1. 把上面的代码编译成JAR包(需要依赖Hive的相关核心包,比如hive-exec、hive-serde等)
  2. 在Hive中注册函数:
ADD JAR /path/to/your/udaf.jar;
CREATE FUNCTION parsed_user_agent AS 'com.your.package.ParsedUserAgentUDAF';

第三步:使用UDAF查询

现在就可以执行你想要的分组查询了:

SELECT 
    city,
    parsed_user_agent(User_Agent) AS ua_stats
FROM log_data
GROUP BY city;

如果需要单独提取结构体中的字段,可以用点语法访问:

SELECT 
    city,
    ua_stats.os_stats AS os_distribution,
    ua_stats.browser_stats AS browser_distribution,
    ua_stats.device_stats AS device_distribution
FROM (
    SELECT 
        city,
        parsed_user_agent(User_Agent) AS ua_stats
    FROM log_data
    GROUP BY city
) t;

内容的提问来源于stack exchange,提问作者Pavel Orlov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:22:19