Hive UDAF

  • UDAF是用户自定义聚合函数。Hive支持其用户自行开发聚合函数完成业务逻辑。

从实现上来看,Hive的UDAF分为两种:

  • Simple。即继承org.apache.hadoop.hive.ql.exec.UDAF类,并在派生类中以静态内部类的方式实现org.apache.hadoop.hive.ql.exec.UDAFEvaluator接口。这种方式简单直接,但是在使用过程中需要依赖JAVA反射机制,因此性能相对较低。在Hive源码包org.apache.hadoop.hive.contrib.udaf.example中包含几个示例。可以直接参阅。但是这些接口已经被注解为Deprecated,建议不要使用这种方式开发新的UDAF函数

  • Generic。这是Hive社区推荐的新的写法,以抽象类代替原有的接口。新的抽象类org.apache.hadoop.hive.ql.udf.generic.AbstractGenericUDAFResolver替代老的UDAF接口,新的抽象类org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator替代老的UDAFEvaluator接口。

UDAF相关类和接口简介

AbstractGenericUDAFResolver

该抽象类实现了GenericUDAFResolver2的接口。UDAF主类须继承该抽象类,其主要作用是实现参数类型检查和操作符重载。可以为同一个函数实现不同入参的版本。

org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator

该抽象类为UDAF具体的逻辑处理,包括几个必须实现的抽象方法,这几个方法负责完成UDAF所需要处理的逻辑。

UDAF的运行流程简介

抽象类GenericUDAFEvaluator中,包含一个静态内部枚举类,和一系列抽象方法。这个枚举类的注释中,解释了各个枚举值的运行阶段和运行内容。按照时间先后顺序,分别有:

  • PARTIAL1:原始数据到部分聚合,调用 iterate 和 terminatePartial --> map 阶段
  • PARTIAL2: 部分聚合到部分聚合,调用 merge 和 terminatePartial --> combine 阶段
  • FINAL: 部分聚合到完全聚合,调用 merge 和 terminate --> reduce 阶段
  • COMPLETE: 从原始数据直接到完全聚合 --> map 阶段,并且没有 reduce

那么,这几个方法分别干了些啥呢?

Init

实例化Evaluator类的时候调用的,在不同的阶段需要返回不同的OI。其入参和返回值,以及Mode阶段的关系如下表:
在这里插入图片描述

getNewAggregationBuffer

获取存放中间结果的对象。

iterate

处理一行数据。

terminatePartial

返回部分聚合数据的持久化对象。因为调用这个方法时,说明已经是map或者combine的结束了,必须将数据持久化以后交给reduce进行处理。只支持JAVA原始数据类型及其封装类型、HADOOP Writable类型、List、Map,不能返回自定义的类,即使实现了Serializable也不行,否则会出现问题或者错误的结果

merge

terminatePartial返回的部分聚合数据进行合并,需要使用到对应的OI。

terminate

结束,生成最终结果。

计算器四钟模式

计算器有4种模式,由枚举类GenericUDAFEvaluator.Mode定义:

public static enum Mode {  
    PARTIAL1, //从原始数据到部分聚合数据的过程(map阶段),将调用iterate()和terminatePartial()方法。  
    PARTIAL2, //从部分聚合数据到部分聚合数据的过程(map端的combiner阶段),将调用merge() 和terminatePartial()方法。      
    FINAL,    //从部分聚合数据到全部聚合的过程(reduce阶段),将调用merge()和 terminate()方法。  
    COMPLETE  //从原始数据直接到全部聚合的过程(表示只有map,没有reduce,map端直接出结果),将调用merge() 和 terminate()方法。  
};  

UDAF开发

构造UDAF代码骨架部分
public class GenericUDAFHistogramNumeric extends AbstractGenericUDAFResolver {
  static final Log LOG = LogFactory.getLog(GenericUDAFHistogramNumeric.class.getName());
 
  @Override
  public GenericUDAFEvaluator getEvaluator(GenericUDAFParameterInfo info) throws SemanticException {
     // 该方法做一些参数的校验,比如下面这个校验代码
	if(parameters.length != 7) {
		throw new UDFArgumentTypeException(parameters.length - 1, "Exactly seven arguments are expected.");
	}
		
	for (int i = 0; i < parameters.length; i++) {
		if(parameters[i].getCategory() != ObjectInspector.Category.PRIMITIVE) {
			throw new UDFArgumentTypeException(i,"Only primitive type arguments are accepted but " + parameters[i].getTypeName() + " is passed.");
		}
	}
    
    return new GenericUDAFHistogramNumericEvaluator();
  }
 
  public static class GenericUDAFHistogramNumericEvaluator extends GenericUDAFEvaluator {
    // 完成具体的实现逻辑
  }
}
实现getEvaluator方法

该方法非常简单,其主要目的是校验UDAF的入参个数和入参类型并返回Evaluator对象。调用者传入不同的参数时,向其返回不同的Evaluator或者直接抛出异常。这部分代码可以写入骨架代码中的TODO:1处。例如本例中的实现,该UDAF不支持多种参数的版本,限定参数个数必须为2,并且第一个参数必须是简单数据类型,第二个参数必须是int。

@Override
  public GenericUDAFEvaluator getEvaluator(TypeInfo[] parameters) throws SemanticException {
    if (parameters.length != 2) {
      throw new UDFArgumentTypeException(parameters.length - 1,
          "Please specify exactly two arguments.");
    }

    // validate the first parameter, which is the expression to compute over
    if (parameters[0].getCategory() != ObjectInspector.Category.PRIMITIVE) {
      throw new UDFArgumentTypeException(0,
          "Only primitive type arguments 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 FLOAT:
	    case DOUBLE:
	    case TIMESTAMP:
	    case DECIMAL:
	      break;
	    case STRING:
	    case BOOLEAN:
	    case DATE:
	    default:
	      throw new UDFArgumentTypeException(0,
	          "Only numeric type arguments are accepted but "
	          + parameters[0].getTypeName() + " was passed as parameter 1.");
	}

    // validate the second parameter, which is the number of histogram bins
    if (parameters[1].getCategory() != ObjectInspector.Category.PRIMITIVE) {
      throw new UDFArgumentTypeException(1,
          "Only primitive type arguments are accepted but "
          + parameters[1].getTypeName() + " was passed as parameter 2.");
    }
    if( ((PrimitiveTypeInfo) parameters[1]).getPrimitiveCategory()
        != PrimitiveObjectInspector.PrimitiveCategory.INT) {
      throw new UDFArgumentTypeException(1,
          "Only an integer argument is accepted as parameter 2, but "
          + parameters[1].getTypeName() + " was passed instead.");
    }

    return new GenericUDAFHistogramNumericEvaluator();
  }

实现Evaluator

从骨架代码中,可以看到一个静态内部类实现了Evaluator的抽象类,并且必须实现它的几个抽象方法。这些方法的调用时机即意义参见上面的表格以及GenericUDAFEvaluator类的静态内部枚举类Mode。

注册函数

将函数直接写入FunctionRegistry类的静态代码块中,system.registerGenericUDAF(“histogram_numeric”, new GenericUDAFHistogramNumeric());,或者将UDAF代码单独打包成jar,采用CREATE FUNCTION语句创建函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值