• hive自定义函数学习


    1介绍

    Hive自定义函数包括三种UDFUDAFUDTF

    UDF(User-Defined-Function) 一进一出

    UDAF(User- Defined Aggregation Funcation) 聚集函数,多进一出。Count/max/min

    UDTF(User-Defined Table-Generating Functions)  一进多出,如lateral view explore)

    使用方式 :在HIVE会话中add 自定义函数的jar文件,然后创建function继而使用函数

    1、编写自定义函数

    2打包上传到集群机器中

    3进入hive客户端,添加jar包:hive> add jar /root/hive_udf.jar

    4创建临时函数:hive> create temporary function getLen as 'com.raphael.len.GetLength';

    5销毁临时函数:hive> DROP TEMPORARY FUNCTION getLen;

    hive自定义函数中有几类数据类型:

    PrimitiveObjectInspector  常用(基本数据类):

    PrimitiveObjectInspector.PrimitiveCategory.STRING

    ListObjectInspector

    StructObjectInspector

    MapObjectInspector

    等类型

    三种自定义函数都会首先进行参数个数和参数类型检查

    参数类型检查(是那一种大类型(primitive...,然后具体是什么类型)

    注意:在所有方法有返回时要注意返回的类型,最重要的是最后返回出去的结果,一般结果的数据类型会在初始化时就定义了,那么在最后返回结果是应该要转化成那种类型

    初始化时init:

    PrimitiveObjectInspector inputOI;

    inputOI = (PrimitiveObjectInspector) parameters[0];

    都会定义这种设置输入数据的 ObjectInspector(类型吧)

    有多个参数就会设置多个。

    在真正处理数据需要获取数据:

    long distinctId = PrimitiveObjectInspectorUtils.getLong(objects[0], distinctIdOI);

    一般都这样获取,objects是方法的参数,从外界传递进来的,包含很多值,获取第几个值,同时类型是什么(distinctIdOI

    2 UDF

    2.1介绍

    hiveudf有两种实现方式或者实现的API,一种是udf比较简单,一种是GenericUDF比较复杂。

    如果所操作的数据类型都是基础数据类型,如(Hadoop&Hive 基本writable类型,如Text,IntWritable,LongWriable,DoubleWritable等等)。那么简单的org.apache.hadoop.hive.ql.exec.UDF就可以做到。

    如果所操作的数据类型是内嵌数据结构,如MapListSet,那么要采用org.apache.hadoop.hive.ql.udf.generic.GenericUDF

    2.2继承UDF实现

    需要继承org.apache.hadoop.hive.ql.UDF,或者

    org.apache.hadoop.hive.ql.udf.generic.GenericUDF,前者比较简单,只需要实现evaluate函数,evaluate函数支持重载。

    UDF代码如下:

    import org.apache.hadoop.hive.ql.exec.UDF;

    public class GetLength extends UDF{

        public int evaluate(String str) {

            try{

                return str.length();

            }catch(Exception e){

                return -1;

            }

        }

    }

    2.3继承GenericUDF实现

    继承org.apache.hadoop.hive.ql.udf.generic.GenericUDF需要实现三个方法:

    1initialize:只调用一次,在任何evaluate()调用之前可以接收到一个可以表示函数输入参数类型的object inspectors数组。initalize用来验证该函数是否接收正确的参数类型和参数个数,最后提供最后结果对应的数据类型。

    2evaluate:真正的逻辑,读取输入数据,处理数据,返回结果。

    3getDisplayString:返回描述该方法的字符串,没有太多作用。

    继承GenericUDF实现UDF,完成url解码功能代码如下:

    public class UrlDecodeUDF2 extends GenericUDF {

        private transient PrimitiveObjectInspector inputOI;

        public ObjectInspector initialize(ObjectInspector[] objectInspectors) throws UDFArgumentException {

            // 检查参数数量

            if (objectInspectors.length != 1) {

                throw new UDFArgumentException("urlDecode() takes only one argument");

            }

            // 检查参数类型

            if (objectInspectors[0].getCategory() != ObjectInspector.Category.PRIMITIVE) {

                if (((PrimitiveObjectInspector) objectInspectors[0]).getPrimitiveCategory() != PrimitiveObjectInspector.PrimitiveCategory.STRING) {

                    throw new UDFArgumentTypeException(0, "Only String type argument are accepted, but "

                            + objectInspectors[0].getTypeName() + " was pass as parameter 1");

                }

            }

            // 设置输入数据的 ObjectInspector

            inputOI = (PrimitiveObjectInspector) objectInspectors[0];

            // 输出数据的 ObjectInspector

            return PrimitiveObjectInspectorFactory.writableStringObjectInspector;

        }

        public Object evaluate(DeferredObject[] deferredObjects) throws HiveException {

            if (deferredObjects == null || deferredObjects[0] == null) {

                return new Text("");

            }

            // 提取数据

            String component = PrimitiveObjectInspectorUtils.getString(deferredObjects[0].get(), inputOI);

            if (component == null || component.length() <= 0) {

                return "";

            }

            String result = "";

            //使用%25替换字符串中的%

            component = component.replaceAll("%(?![0-9a-fA-F]{2})", "%25");

            try {

                result = URLDecoder.decode(component, "UTF-8");

    //            result = URLDecoder.decode(result, "UTF-8");

            } catch (UnsupportedEncodingException e) {

                result = component;

            }

            System.out.println(result);

            return result;

        }

        public String getDisplayString(String[] strings) {

            StringBuilder sb = new StringBuilder();

            sb.append("url_decode");

            sb.append("(");

            if (strings.length > 0) {

                sb.append(strings[0]);

                for (int index = 1; index < strings.length - 1; index++) {

                    sb.append(",");

                    sb.append(strings[index]);

                }

            }

            sb.append(")");

            return sb.toString();

        }

    }

    3 UDAF

    3.1介绍

    多行进一行出,如sum()min(),用在group  by时。开发通用UDAF有两个步骤

    1、第一个是编写resolver类(继承AbstractGenericUDAFResolver),

    2、第二个是编写evaluator类(继承GenericUDAFEvaluator)在resolver类内部。

    resolver负责类型检查,操作符重载。evaluator真正实现UDAF的逻辑。通常来说,顶层UDAF类继承org.apache.hadoop.hive.ql.udf.GenericUDAFResolver,里面编写嵌套类evaluator (继承GenericUDAFEvaluator)实现UDAF的逻辑。

    实现evaluator所有evaluators必须继承抽象类

    org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator。子类必须实现它的一些抽象方法,实现UDAF的逻辑。

    同时需要一个继承了AggregationBuffer的类,来存储中间过程中记录的数据

    3.2 Mode

    GenericUDAFEvaluator有一个嵌套类Mode,这个类很重要,它表示了udafmapreduce的各个阶段,理解Mode的含义,就可以理解了hiveUDAF的运行流程。

    PARTIAL1, //从原始数据到部分聚合数据的过程(map阶段),将调用iterate()terminatePartial()方法。

    PARTIAL2, //从部分聚合数据到部分聚合数据的过程(map端的combiner阶段),将调用merge() terminatePartial()方法。

    FINAL,    //从部分聚合数据到全部聚合的过程(reduce阶段),将调用merge()terminate()方法。

    COMPLETE  //从原始数据直接到全部聚合的过程(表示只有map,没有reducemap端直接出结果),将调用merge() terminate()方法。

    public static enum Mode {

        /**

         * PARTIAL1: 这个是mapreducemap阶段:从原始数据到部分数据聚合

         * 将会调用iterate()terminatePartial()

         */

        PARTIAL1,

            /**

         * PARTIAL2: 这个是mapreducemap端的Combiner阶段,负责在map端合并map的数据::从部分数据聚合到部分数据聚合:

         * 将会调用merge() terminatePartial()

         */

        PARTIAL2,

            /**

         * FINAL: mapreducereduce阶段:从部分数据的聚合到完全聚合

         * 将会调用merge()terminate()

         */

        FINAL,

            /**

         * COMPLETE: 如果出现了这个阶段,表示mapreduce只有map,没有reduce,所以map端就直接出结果了:从原始数据直接到完全聚合

          * 将会调用 iterate()terminate()

         */

        COMPLETE

      };

    一般情况下,完整的UDAF逻辑是一个mapreduce过程,如果有mapperreducer,就会经历PARTIAL1(mapper)FINAL(reducer),如果还有combiner,那就会经历PARTIAL1(mapper)PARTIAL2(combiner)FINAL(reducer)

    而有一些情况下的mapreduce,只有mapper,而没有reducer,所以就会只有COMPLETE阶段,这个阶段直接输入原始数据,出结果。

    3.3实现代码分析

    UDAF的实现代码主干如下:

    //最外层继承AbstractGenericUDAFResolver

    public class GenericUDAFSum extends AbstractGenericUDAFResolver {

      static final Log LOG = LogFactory.getLog(GenericUDAFSum.class.getName());

    //实现getEvaluator方法

      @Override

      public GenericUDAFEvaluator getEvaluator(TypeInfo[] parameters)

        throws SemanticException {

        // Type-checking goes here!

        return new GenericUDAFSumLong();

      }

    //编写一个类,继承GenericUDAFEvaluator,实现所有方法

      public static class GenericUDAFSumLong extends GenericUDAFEvaluator {

        // UDAF logic goes here!

      }

    }

    说明:

    1、getEvaluator:继承AbstractGenericUDAFResolver 所需要实现的方法,也只需要重写这一个方法,作用是检查参数个数,参数类型等,然后返回GenericUDAFEvaluator对象。

    2、GenericUDAFEvaluatorgetEvaluator最后返回类型就是这个类,真正返回(return)的是继承实现GenericUDAFEvaluator的类。

    例如GenericUDAFSumLong 继承GenericUDAFEvaluator需要实现如下方法:

    1、init(初始化):确定返回类型并返回UDAF的返回类型,

    2、getNewAggregationBuffer:创建新的聚合计算的需要的内存,用来存储mapper,combiner,reducer运算过程中的相加总和,获取聚合中间结果缓存

    3、reset:重置中间结果缓存

    4、iterate: 迭代每一行的数据,等同于 Map 阶段。传进来的行数据由 HiveSQL 决定,计算的中间结果缓存到 AggregationBuffer @param aggregationBuffer 中间结果缓存,@param objects 行中每一列的数据

    5、terminatePartial(终止部分):对部分中间结果数据进行合并,等同于 Map 阶段的 combine,返回mapper结果,combine后的结果

    6、merge(合并):在最终进行 terminate() 前对所有传入的中间结果进行合并,等同于 Reduce 阶段的 merge。 各个 Map 传来的中间结果 partial 合并到 aggregationBuffer

    7、terminate(终止):计算合并后的数据得出最终结果,等同于 Reduce 阶段的逻辑。reducer返回结果,或者是只有mapper,没有reducer时,在mapper端返回结果。

    3.4代码示例

    例子如下:

    public class UDAFDemo extends  AbstractGenericUDAFResolver {

        static final Log LOG = LogFactory.getLog(GenericUDAFSum.class.getName());

        public GenericUDAFEvaluator getEvaluator(TypeInfo[] parameters)

            throws SemanticException {

            // 检查参数个数

            if (parameters.length != 1) {

                throw new UDFArgumentTypeException(parameters.length - 1,

                        "Exactly one argument is expected.");

            }

            // 检查参数类型

            if (parameters[0].getCategory() != ObjectInspector.Category.PRIMITIVE) {

                throw new UDFArgumentTypeException(0,

                        "Only primitive type arguments are accepted but "

                                + parameters[0].getTypeName() + " is passed.");

            }

    //        // 检查参数类型

    //        if (parameters[0].getCategory() != ObjectInspector.Category.PRIMITIVE) {

    //            throw new UDFArgumentTypeException(0, "Only primitive type argument are accepted but "

    //                    + parameters[0].getTypeName() + " was passed as parameter 1");

    //        }

    //        if (((PrimitiveTypeInfo) parameters[0]).getPrimitiveCategory() != PrimitiveObjectInspector.PrimitiveCategory.LONG) {

    //            throw new UDFArgumentTypeException(0, "Only Long Type type argument are accepted but "

    //                    + parameters[0].getTypeName() + " was passed as parameter 1");

    //        }

            // 检查参数类型

            switch (((PrimitiveTypeInfo) parameters[0]).getPrimitiveCategory()) {

                case BYTE:

                case SHORT:

                case INT:

                case LONG:

                case TIMESTAMP:

                    return new GenericUDAFSumLong();

                case FLOAT:

                case DOUBLE:

    //                case STRING:

    //                    return new GenericUDAFSumDouble();

                case BOOLEAN:

                default:

                    throw new UDFArgumentTypeException(0,

                            "Only numeric or string type arguments are accepted but "

                                    + parameters[0].getTypeName() + " is passed.");

            }

        }

        public static class GenericUDAFSumLong extends GenericUDAFEvaluator {

            private PrimitiveObjectInspector inputOI;

            private LongWritable result;

            /** 存储sum的值的类 */

            static class SumLongAgg implements AggregationBuffer {

                boolean empty;

                long sum;

            }

            //这个方法返回了UDAF的返回类型,这里确定了sum自定义函数的返回类型是Long类型

            public ObjectInspector init(Mode m, ObjectInspector[] parameters) throws HiveException {

                assert (parameters.length == 1);

                super.init(m, parameters);

                result = new LongWritable(0);

                inputOI = (PrimitiveObjectInspector) parameters[0];

                return PrimitiveObjectInspectorFactory.writableLongObjectInspector;

            }

            //创建新的聚合计算的需要的内存,用来存储mapper,combiner,reducer运算过程中的相加总和

            public AggregationBuffer getNewAggregationBuffer() throws HiveException {

                SumLongAgg result = new SumLongAgg();

                reset(result);

                return result;

            }

            //mapreduce支持mapperreducer的重用,所以为了兼容,也需要做内存的重用。

            public void reset(AggregationBuffer aggregationBuffer) throws HiveException {

                SumLongAgg myagg = (SumLongAgg) aggregationBuffer;

                myagg.empty = true;

                myagg.sum = 0;

            }

            private boolean warned = false;

            //map阶段调用,只要把保存当前和的对象agg,再加上输入的参数,就可以了。

            public void iterate(AggregationBuffer aggregationBuffer, Object[] objects) throws HiveException {

                assert (objects.length == 1);

                try {

                    merge(aggregationBuffer, objects[0]);

                }catch (NumberFormatException e) {

                    if (!warned) {

                        warned = true;

                        LOG.warn(getClass().getSimpleName() + " "

                                + StringUtils.stringifyException(e));

                    }

                }

            }

            //mapper结束要返回的结果,还有combiner结束返回的结果

            public Object terminatePartial(AggregationBuffer aggregationBuffer) throws HiveException {

                return terminate(aggregationBuffer);

            }

            //combiner合并map返回的结果,还有reducer合并mappercombiner返回的结果。

            public void merge(AggregationBuffer aggregationBuffer, Object o) throws HiveException {

                if (o != null) {

                    SumLongAgg agg = (SumLongAgg) aggregationBuffer;

                    agg.sum += PrimitiveObjectInspectorUtils.getLong(o, inputOI);

                    agg.empty = false;

                }

            }

            //reducer返回结果,或者是只有mapper,没有reducer时,在mapper端返回结果。

            public Object terminate(AggregationBuffer aggregationBuffer) throws HiveException {

                SumLongAgg ragg = (SumLongAgg) aggregationBuffer;

                if (ragg.empty) {

                    return null;

                }

                result.set(ragg.sum);

                return result;

            }

        }

    }

    4 UDTF

    4.1介绍

    udtf用来解决输入一行输出多行(On-to-many maping) 的需求需要继承org.apache.hadoop.hive.ql.udf.generic.GenericUDTF来实现三个方法。

    1、initialize:返回UDTF的返回行的信息(返回个数,类型)。

    2、process:真正的处理过程在process函数中,在process中,每一次forward()调用产生一行;如果产生多列可以将多个列的值放在一个数组中,然后将该数组传入到forward()函数。

    3、close:对需要清理的方法进行清理

    forward()传入的就是最后的结果,里面一般是数组,数组有多少个元素就代码最后一行输出的结果有多少列

    4.2代码示例

    下面有一个切分”key:value;key:value;”这种字符串,返回结果为key, value两个字段。

    public class UDTFDemo extends GenericUDTF {

        @Override

        public void close() throws HiveException {

            // TODO Auto-generated method stub

        }

        /**

         * 此方法返回UDTF的返回行的信息(返回个数,类型)

         */

        @Override

        public StructObjectInspector initialize(ObjectInspector[] args)

                throws UDFArgumentException {

            if (args.length != 1) {

                throw new UDFArgumentLengthException("ExplodeMap takes only one argument");

            }

            if (args[0].getCategory() != ObjectInspector.Category.PRIMITIVE) {

                throw new UDFArgumentException("ExplodeMap takes string as a parameter");

            }

            //多出的列名

            ArrayList<String> fieldNames = new ArrayList<String>();

            //每一列的类型

            ArrayList<ObjectInspector> fieldOIs = new ArrayList<ObjectInspector>();

            fieldNames.add("c1");

            fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);

            fieldNames.add("c2");

            fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);

            return ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames,fieldOIs);

        }

        /**

         * 初始化完成之后会调用process方法,真正的处理过程在process函数中,在process中,每一次forward()调用产生一行;

         * 如果产生多列可以将多个列的值放在一个数组中,然后将该数组传入到forward()函数。

         */

        @Override

        public void process(Object[] args) throws HiveException {

            //每一行数 key:value,key:value,key:value

            String input = args[0].toString();

            String[] test = input.split(";");

            for(int i=0; i<test.length; i++) {

                try {

                    String[] result = test[i].split(":");

                    forward(result);

                } catch (Exception e) {

                    continue;

                }

            }

        }

    }

    5注意点(坑点)

    1、使用String进行数据传递时,有时会出现String强转HadoopText类型异常,或者Integer强转类型Text异常。

    示例:自定义UDAF

     

    实现这个功能时:udaf的输出的list,类型是Object类型,最后统计时使用map存放计数map<object,integer>,注意最后填充到list中时要把mapkeyvalue全部变成Text类型才行,不然可能会出现类型转换错误。我在iterate方法里面需要获取value时(此时类型是String),获取之后马上转换成Text之后存放的。

    2、注意在一些稍微复杂的udaf中会使用list或者其他类型,在中间进行聚合等其他操作时,不同操作的传入的参数类型可能不同,最开始可能传递进来的数据是字符串类型或者Double类型的,但是经过一次迭代或者聚合之后变成了list,那么下一次或者下一个流程的传入数据的类型就变成了list,这时需要在初始化参数类型的时候定义好,如:

     

    根据mode的流程判断处于哪一个阶段,参数类型就哪一种,就切换成哪一种,这里的返回值是定义的最终的返回结果。

  • 相关阅读:
    python3+request接口自动化框架
    类型转换函数
    操作符重载(三)
    操作符重载(二)
    操作符重载(一)
    时间获取函数
    文件和目录
    Linux五种IO模型
    类中的函数重载
    系统调用IO和标准IO
  • 原文地址:https://www.cnblogs.com/lrxvx/p/10974341.html
Copyright © 2020-2023  润新知