• Hive UDF开发指南


    编写Apache Hive用户自定义函数(UDF)有两个不同的接口,一个非常简单,另一个...就相对复杂点。

    如果你的函数读和返回都是基础数据类型(Hadoop&Hive 基本writable类型,如Text,IntWritable,LongWriable,DoubleWritable等等),那么简单的API(org.apache.hadoop.hive.ql.exec.UDF)可以胜任
    但是,如果你想写一个UDF用来操作内嵌数据结构,如Map,List和Set,那么你要去熟悉org.apache.hadoop.hive.ql.udf.generic.GenericUDF这个API
    简单API: org.apache.hadoop.hive.ql.exec.UDF
    复杂API:  org.apache.hadoop.hive.ql.udf.generic.GenericUDF
    接下来我将通过一个示例为上述两个API建立UDF,我将为接下来的示例提供代码与测试
    如果你想浏览代码:fork it on Github:https://github.com/rathboma/hive-extension-examples
     

    简单API

    用简单UDF API来构建一个UDF只涉及到编写一个类继承实现一个方法(evaluate),以下是示例:
    [java] view plain copy
     
    1. class SimpleUDFExample extends UDF {  
    2.     
    3.   public Text evaluate(Text input) {  
    4.     return new Text("Hello " + input.toString());  
    5.   }  
    6. }  

    因为该UDF是一个简单的函数,你可以在规范的测试工具测试它,如JUnit。
    [java] view plain copy
     
    1. public class SimpleUDFExampleTest {  
    2.     
    3.   @Test  
    4.   public void testUDF() {  
    5.     SimpleUDFExample example = new SimpleUDFExample();  
    6.     Assert.assertEquals("Hello world", example.evaluate(new Text("world")).toString());  
    7.   }  
    8. }  
     
    好的,在Hive控制台测试一把,也可以在hive中直接测试这个UDF,特别是当你不完全肯定该函数是否能够正确处理问题的时候
    [plain] view plain copy
     
    1. %> hive  
    2. hive> ADD JAR target/hive-extensions-1.0-SNAPSHOT-jar-with-dependencies.jar;  
    3. hive> CREATE TEMPORARY FUNCTION helloworld as 'com.matthewrathbone.example.SimpleUDFExample';  
    4. hive> select helloworld(name) from people limit 1000;  

    事实上,上述UDF有一个bug,不会去检查null参数,null在一个大的数据集当中是很常见的,所以要适当严谨点。作为回应,这边在函数中加了一个null检查
    [java] view plain copy
     
    1. class SimpleUDFExample extends UDF {  
    2.     
    3.   public Text evaluate(Text input) {  
    4.     if(input == null) return null;  
    5.     return new Text("Hello " + input.toString());  
    6.   }  
    7. }  

    然后加了一个测试去验证它
    [java] view plain copy
     
    1. @Test  
    2. public void testUDFNullCheck() {  
    3.   SimpleUDFExample example = new SimpleUDFExample();  
    4.   Assert.assertNull(example.evaluate(null));  
    5. }  

    用mvn test跑一下测试,来保证所有用例通过。
     

    复杂的API

    org.apache.hadoop.hive.ql.udf.generic.GenericUDF API提供了一种方法去处理那些不是可写类型的对象,例如:struct,map和array类型。
    这个API需要你亲自去为函数的参数去管理对象存储格式(object inspectors),验证接收的参数的数量与类型。一个object inspector为内在的数据类型提供一个一致性接口,以至不同实现的对象可以在hive中以一致的方式去访问(例如,只要你能提供一个对应的object inspector,你可以实现一个如Map的复合对象)。
    这个API要求你去实现以下方法:
    [java] view plain copy
     
    1. // 这个类似于简单API的evaluat方法,它可以读取输入数据和返回结果  
    2. abstract Object evaluate(GenericUDF.DeferredObject[] arguments);  
    3.   
    4. // 该方法无关紧要,我们可以返回任何东西,但应当是描述该方法的字符串  
    5. abstract String getDisplayString(String[] children);  
    6.   
    7. // 只调用一次,在任何evaluate()调用之前,你可以接收到一个可以表示函数输入参数类型的object inspectors数组  
    8. // 这是你用来验证该函数是否接收正确的参数类型和参数个数的地方  
    9. abstract ObjectInspector initialize(ObjectInspector[] arguments);  

    可能要通过一个示例才能去了解这个接口,所以接下来往下看。
     

    示例

    我将通过建立一个UDF函数:containsString,来加深对该API了解,该函数接收两个参数:
    一个String的列表(list)
    一个String
     
    根据该list中是否包含所提供的string来返回true或者false,如下:
    [java] view plain copy
     
    1. containsString(List("a", "b", "c"), "b"); // true  
    2.   
    3. containsString(List("a", "b", "c"), "d"); // false  

    不同于UDF接口,这个GenericUDF接口需要更啰嗦点。
    [java] view plain copy
     
    1. class ComplexUDFExample extends GenericUDF {  
    2.   
    3.   ListObjectInspector listOI;  
    4.   StringObjectInspector elementOI;  
    5.   
    6.   @Override  
    7.   public String getDisplayString(String[] arg0) {  
    8.     return "arrayContainsExample()"; // this should probably be better  
    9.   }  
    10.   
    11.   @Override  
    12.   public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {  
    13.     if (arguments.length != 2) {  
    14.       throw new UDFArgumentLengthException("arrayContainsExample only takes 2 arguments: List<T>, T");  
    15.     }  
    16.     // 1. 检查是否接收到正确的参数类型  
    17.     ObjectInspector a = arguments[0];  
    18.     ObjectInspector b = arguments[1];  
    19.     if (!(a instanceof ListObjectInspector) || !(b instanceof StringObjectInspector)) {  
    20.       throw new UDFArgumentException("first argument must be a list / array, second argument must be a string");  
    21.     }  
    22.     this.listOI = (ListObjectInspector) a;  
    23.     this.elementOI = (StringObjectInspector) b;  
    24.       
    25.     // 2. 检查list是否包含的元素都是string  
    26.     if(!(listOI.getListElementObjectInspector() instanceof StringObjectInspector)) {  
    27.       throw new UDFArgumentException("first argument must be a list of strings");  
    28.     }  
    29.       
    30.     // 返回类型是boolean,所以我们提供了正确的object inspector  
    31.     return PrimitiveObjectInspectorFactory.javaBooleanObjectInspector;  
    32.   }  
    33.     
    34.   @Override  
    35.   public Object evaluate(DeferredObject[] arguments) throws HiveException {  
    36.       
    37.     // 利用object inspectors从传递的对象中得到list与string  
    38.     List<String> list = (List<String>) this.listOI.getList(arguments[0].get());  
    39.     String arg = elementOI.getPrimitiveJavaObject(arguments[1].get());  
    40.       
    41.     // 检查空值  
    42.     if (list == null || arg == null) {  
    43.       return null;  
    44.     }  
    45.       
    46.     // 判断是否list中包含目标值  
    47.     for(String s: list) {  
    48.       if (arg.equals(s)) return new Boolean(true);  
    49.     }  
    50.     return new Boolean(false);  
    51.   }  
    52.     
    53. }  

    代码走读

    函数的调用模块如下:

    1、该UDF用默认的构造器来初始化

    2、udf.initialize() 被调用,传人udf参数的object instructors数组,(ListObjectInstructor, StringObjectInstructor)
    1) 检查传人的参数有两个与该参数的数据类型是正确的(见上面)
    2) 我们保存object instructors用以供evaluate()使用(listOI, elementOI)
    3) 返回 object inspector,让Hive能够读取该函数的返回结果(BooleanObjectInspector)

    3、对于查询中的每一行,evaluate方法都会被调用,传人该行的指定的列(例如,evaluate(List(“a”, “b”, “c”), “c”) )。
    1) 我们利用initialize方法中存储的object instructors来抽取出正确的值。
    2) 我们在这处理我们的逻辑然后用initialize返回的object inspector来序列化返回来的值(list.contains(elemement) ? true : false)。
     

    测试

    测试该函数比较复杂的部分是初始化,一旦调用顺序明确了,我们就知道怎么去构建该对象测试流程,非常简单。
    [java] view plain copy
     
    1. public class ComplexUDFExampleTest {  
    2.     
    3.   @Test  
    4.   public void testComplexUDFReturnsCorrectValues() throws HiveException {  
    5.       
    6.     // 建立需要的模型  
    7.     ComplexUDFExample example = new ComplexUDFExample();  
    8.     ObjectInspector stringOI = PrimitiveObjectInspectorFactory.javaStringObjectInspector;  
    9.     ObjectInspector listOI = ObjectInspectorFactory.getStandardListObjectInspector(stringOI);  
    10.     JavaBooleanObjectInspector resultInspector = (JavaBooleanObjectInspector) example.initialize(new ObjectInspector[]{listOI, stringOI});  
    11.       
    12.     // create the actual UDF arguments  
    13.     List<String> list = new ArrayList<String>();  
    14.     list.add("a");  
    15.     list.add("b");  
    16.     list.add("c");  
    17.       
    18.     // 测试结果  
    19.       
    20.     // 存在的值  
    21.     Object result = example.evaluate(new DeferredObject[]{new DeferredJavaObject(list), new DeferredJavaObject("a")});  
    22.     Assert.assertEquals(true, resultInspector.get(result));  
    23.       
    24.     // 不存在的值  
    25.     Object result2 = example.evaluate(new DeferredObject[]{new DeferredJavaObject(list), new DeferredJavaObject("d")});  
    26.     Assert.assertEquals(false, resultInspector.get(result2));  
    27.       
    28.     // 为null的参数  
    29.     Object result3 = example.evaluate(new DeferredObject[]{new DeferredJavaObject(null), new DeferredJavaObject(null)});  
    30.     Assert.assertNull(result3);  
    31.   }  
    32. }  
     

    结束语

    希望这篇文章能够让你了解通过集成怎么去编写hive的自定义函数。
    虽然在这篇文章中有一些其他的东西没提及到,但是另外有UDAF函数与UDTF函数,UDAF函数能够在一个函数中处理与聚集多行数据,如果你更感兴趣,这里有一些资源可以提供帮助。
    另外,值得一读的书籍有Apache Hive Book from O’Reilly该数包含UDF与UDAF的简明的教程,和代码示例,更容易让你们明白如何去构建这些函数、什么异常你必须要指定、什么类型你必须返回
     

    翻译来自于

    http://blog.matthewrathbone.com/2013/08/10/guide-to-writing-hive-udfs.html
  • 相关阅读:
    R()函数的使用 tp2
    __call()的使用
    tp3.1.3 引入静态页面
    tp3.1.3创建应用
    .env文件的使用 thinkphp51
    concat()和push()的区别
    小程序模板template使用
    56. 从1到n整数中1出现的次数
    55. 连续子数组的最大和
    54. 数据流中的中位数
  • 原文地址:https://www.cnblogs.com/hd-zg/p/5947468.html
Copyright © 2020-2023  润新知