挖挖Hive的代码(一)——UDF

        系列第一篇,先扯扯。Hive的东西太多了,想一篇文章介绍完也是可以的,但是没有意义。所以我会分几篇写下我的“挖掘”经历,当然了,我也没打算把Hive所有的内容都挖一遍,只是记录下我感兴趣的、没见别人挖过的……

         Hive对查询语句的解析过程,在淘宝数据平台的官方博客上有几篇文章介绍了,我就跳开这部分关键内容啦。第一篇打算先写最近挖过的UDF。

         什么是UDF?如果我们写一条这样的HQL:“SELECT SUBSTR(column1, 7), column2 FROM src WHERE column3 < 100”,那么Hive在解析了这条查询语句后,就会将语句中的“substr”和“<"分别封装成对应的UDF对象。大概知道了吧?

         Hive的UDF根据继承的父类可以分为UDF类(指类别,旧)和GenericUDF类,新版本的Hive陆续把部分UDF类的UDF,改写成了GenericUDF类的UDF。udf包中还有一些分工略有不同的UDF,准确的说,叫UDAF,这些是用于GroupBy的聚合函数,其父类也分UDAF和GenericUDAF,但这些不在本篇的讨论范围内。

         先来看看UDF与GenericUDF到底区别在哪:

public class UDF {

  /**
   * The resolver to use for method resolution.
   */
  private UDFMethodResolver rslv;

  public UDF() {
    rslv = new DefaultUDFMethodResolver(this.getClass());
  }

  /**
   * The constructor with user-provided UDFMethodResolver.
   */
  protected UDF(UDFMethodResolver rslv) {
    this.rslv = rslv;
  }

  public void setResolver(UDFMethodResolver rslv) {
    this.rslv = rslv;
  }

  public UDFMethodResolver getResolver() {
    return rslv;
  }
}
        上面这个是UDF.java的主要代码,所有UDF类(指类别)的UDF都必须继承自这个父类。可以看到它主要定义了一个UDFMethodResolver类的属性,这个属性的用处待会会详细说明。它在注释中还规定了,所有子类必须实现一个或多个evaluate方法给Hive框架调用,不难猜到,这个evaluate方法正是所有实现类的逻辑部分所在。

public abstract class GenericUDF {

  /**
   * A Defered Object allows us to do lazy-evaluation and short-circuiting.
   * GenericUDF use DeferedObject to pass arguments.
   */
  public static interface DeferredObject {
    Object get() throws HiveException;
  };

  public GenericUDF() {
  }

  public abstract ObjectInspector initialize(ObjectInspector[] arguments)
      throws UDFArgumentException;

  public abstract Object evaluate(DeferredObject[] arguments)
      throws HiveException;
}
        上面这个是Generic.java的主要代码,同样,所有GenericUDF类(亦指类别)的UDF都必须继承自这个父类。它多了一个initialize方法,这个方法对于一个GenericUDF的实例来说只会调用一次(似乎是废话,不然就不叫这个名字了~);它少了UDFMethodResolver类的属性,因为这个属性的用处被initialize方法替代了。GenericUDF类直接定义了evaluate方法,而且不需要子类重载它的参数定义,当然功能还是一样的。

        好,接下来说说为什么要有这样的改进吧:一个是可以接受和返回复杂数据类型了,例如Array什么的结构体类型,而不像UDF类那样只能是int、string之类的基本类型(当然真正代码中定义的是包装过的后缀为Writable的类型,但还是表示基本类型);新的改进可以接受可变长度以及无限长度的参数了,因为可以用数组来表示输入参数了,而不需要像UDF类的实现类那样,要几种参数组合,就得重载几种方法;最重要的改进是可以通过DeferredObject类来实现所谓的”short-circuit“优化(不知道怎么表述~)。

        从SemanticAnalyzer类(查询语句的语义解析类)的代码可以看出,生成的job plan中已经没有UDF类的身影了,所有UDF都是以GenericUDF类的实例出现的。但这并不意味着Hive抛弃了之前的UDF类的实现,而是会通过GenericUDFBridge类实现转接。接着就通过这个GenericUDFBridge来看看UDF.java中的UDFMethodResolver类属性和initialize方法的用处吧。

        从各GenericUDF的实现类可以看出,initialize方法主要是用来处理该UDF的输入参数的类型信息。一般会根据UDF的输入参数的类型(initialize方法的输入参数,各种ObjectInspector对象),生成对应的ObjectInspector实现类对象作为该UDF对象的成员变量,用于evaluate方法的计算过程。这里所谓的生成ObjectInspector对象,其实是获得一个某ObjectInspector实现类对象的引用,因为这些”生成“操作都是通过工厂类来实现的,而这些工厂类保证了这些类都是单一实例……好像又说废话了,还没解释为什么用到了单例模式,那就跑开一下说说各ObjectInspector类的用处吧。

        我们知道,Hive最终跑的job是Hadoop的job,而Hadoop处理的输入数据要么是文本数据,要么是一些序列化后的二进制格式数据,都是没有数据类型的。每当Hive要根据”表“中定义的类型来处理数据的时候,都需要进行相应的类型处理。各种ObjectInspector类就是GenericUDF实现类中干这个活的,不同的ObjectInspector类知道怎么从Object对象(因为源数据没有类型,都是Object)中提取出相应的Writable对象,或者提取出原始的Java类型数据。

        再跑回initialize方法,所以从效率考虑,它们只要是单例的即可。而特别点的GenericUDFBridge类的initialize方法还做了什么呢?因为它要将这一套ObjectInspector的方式用到老的UDF类上,就要做点额外的工作了,其实就是要获取老的UDF对象的结果类型和需要的输入参数类型。直接贴一下代码吧:

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

    udf = (UDF) ReflectionUtils.newInstance(udfClass, null);

    // Resolve for the method based on argument types
    ArrayList<TypeInfo> argumentTypeInfos = new ArrayList<TypeInfo>(
        arguments.length);
    for (ObjectInspector argument : arguments) {
      argumentTypeInfos.add(TypeInfoUtils
          .getTypeInfoFromObjectInspector(argument));
    }
    udfMethod = udf.getResolver().getEvalMethod(argumentTypeInfos);
    udfMethod.setAccessible(true);

    // Create parameter converters
    conversionHelper = new ConversionHelper(udfMethod, arguments);

    // Create the non-deferred realArgument
    realArguments = new Object[arguments.length];

    // Get the return ObjectInspector.
    ObjectInspector returnOI = ObjectInspectorFactory
        .getReflectionObjectInspector(udfMethod.getGenericReturnType(),
        ObjectInspectorOptions.JAVA);

    return returnOI;
  }

从代码可以看到:创建实例,获取实际的参数类型……注意

 udfMethod = udf.getResolver().getEvalMethod(argumentTypeInfos);

这一行,之前提到的UDFMethodResolver类登场了。跟进它的getEvalMethod方法,发现实际调用了一个FunctionRegistry.getMethodInternal方法:

return FunctionRegistry.getMethodInternal(udfClass, "evaluate", false,
        argClasses);

FunctionRegistry.getMethodInternal的代码里面有这么一段:

    for (Method m : mlist) {
      List<TypeInfo> argumentsAccepted = TypeInfoUtils.getParameterTypeInfos(m,
          argumentsPassed.size());
      if (argumentsAccepted == null) {
        // null means the method does not accept number of arguments passed.
        continue;
      }

      boolean match = (argumentsAccepted.size() == argumentsPassed.size());
      int conversionCost = 0;

      for (int i = 0; i < argumentsPassed.size() && match; i++) {
        int cost = matchCost(argumentsPassed.get(i), argumentsAccepted.get(i),
            exact);
        if (cost == -1) {
          match = false;
        } else {
          conversionCost += cost;
        }
      }
……
很显然,它是在根据实际的参数类型,在UDF类中寻找一个最合适的evaluate方法。回到initialize方法,接下来的内容就好理解了,依然是生成对应的ObjectInspector对象……

好了,对UDF的挖掘就写到这吧,好久没有写技术博客哩~

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值