UDF函数用户自定义函数,在查询执行过程就是在Hive转换成MapReduce程序后,执行java方法,类似于像MapReduce执行过程中加入一个插件,方便扩展。Hive中有3种UDF:
- UDF:操作单个数据行,产生单个数据行,如:upper、substr函数
- UDAF:操作多个数据行,产生一个数据行,如sum、min函数
- UDTF:操作一个数据行,产生多个数据行一个表作为输出,如 lateral view 、explode函数
如果函数读和返回都是基础数据类型,即 Hadoop 和 Hive 的基本类型,如Text、IntWritable、LongWritable、DoubleWritable 等,那么继承 org.apache.hadoop.hive.ql.exec.UDF 。如果用来操作内嵌数据结构,如 Map、List 和 Set,则继承 org.apache.hadoop.hive.ql.udf.generic.GenericUDF。
用户构建的UDF使用过程如下:
- 继承UDF或者UDAF或者UDTF,实现特定的方法
- 将写好的类打包为jar,这里是WordTransferUDF.jar
- 进入到Hive shell环境中,输入命令add jar /home/hadoop/WordTransferUDF.jar注册该jar文件;或者把WordTransferUDF.jar上传到HDFS,hadoop fs -put WordTransferUDF.jar /home/hadoop/WordTransferUDF.jar,再输入命令add jar hdfs://hadoop01:8020/user/home/WordTransferUDF.jar
- 为该类起一个别名,create temporary function lower_udf as ‘UDF.lowerUDF’;注意,这里UDF只是为这个Hive会话临时定义的
- 在select中使用lower_udf()
自定义UDF
【pom.xml依赖】
<dependency>
<groupId>org.apache.hive</groupId>
<artifactId>hive-exec</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.7.3</version>
</dependency>
【编写UDF代码】
package UDF;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.hive.ql.exec.UDF;
import org.apache.hadoop.io.Text;
public class WordTransferUDF extends UDF{
/**
* 1. Implement one or more methods named "evaluate" which will be called by Hive.
* 2. "evaluate" should never be a void method. However it can return "null" if needed.
*/
public Text evaluate(Text str){
// input parameter validate
if(null == str){
return null ;
}
// validate
if(StringUtils.isBlank(str.toString())){
return null ;
}
// lower
return new Text(str.toString().toLowerCase()) ;
}
}
【打包】
注意 工程所用的jdk要与Hadoop集群使用的jdk是同一个版本
【注册UDF】
hive> add jar /home/hadoop/LowerUDF.jar
hive> create temporary function lower_udf as "UDF.LowerUDF";
【测试】
hive> create table test (id int ,name string);
hive> insert into test values(1,'TEST');
hive> select lower_udf(name) from test;
注意事项:
- 一个用户UDF必须继承org.apache.hadoop.hive.ql.exec.UDF
- 一个UDF必须要包含有evaluate()方法,但是该方法并不存在于UDF中。evaluate的参数个数以及类型都是用户自定义的。在使用的时候,Hive会调用UDF的evaluate()方法
GenericUDF
GenericUDF API 提供了一种方法去处理那些不是可写类型的对象,例如:struct,map 和 array 类型。这个 API 需要用户亲自为函数的参数管理对象存储格式,验证接收的参数的数量与类型。这个 API 要求实现以下方法:
// 这个类似于简单 API 的 evaluate 方法,它可以读取输入数据和返回结果
abstract Object evaluate(GenericUDF.DeferredObject[] arguments);
// 该方法应当是描述该 UDF 的字符串,显示函数的提示信息
abstract String getDisplayString(String[] children);
// 只调用一次,在任何 evaluate() 调用之前,可以接收到一个可以表示函数输入参数类型的 object inspectors 数组
// 是用来验证该函数是否接收正确的参数类型和参数个数的地方
abstract ObjectInspector initialize(ObjectInspector[] arguments);
例子来自 《Hive 编程指南》,编写一个用户自定义函数,称之为nvl(),这个函数传入的值如果是 null,那么就返回一个默认值。函数 nvl() 要求有 2 个参数。如果第 1 个参数是非null值,那么就返回这个值;如果第 1 个参数是 null,那么就返回第 2 个参数的值。
import org.apache.hadoop.hive.ql.exec.Description;
import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.exec.UDFArgumentTypeException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDFUtils;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
public class GenericUDFNvl extends GenericUDF {
private GenericUDFUtils.ReturnObjectInspectorResolver returnOIResolver;
private ObjectInspector[] argumentOIs;
@Override
public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {
argumentOIs = arguments;
// 1.检验参数个数
if (arguments.length != 2) {
throw new UDFArgumentException("The operator 'NVL' accepts 2 arguments.");
}
// 2.检验参数类型
returnOIResolver = new GenericUDFUtils.ReturnObjectInspectorResolver(true);
if (!(returnOIResolver.update(arguments[0]) && returnOIResolver.update(arguments[1]))) {
throw new UDFArgumentTypeException(2, "The 1st and 2nd args of function NLV should have the same type, "
+ "but they are different: \"" + arguments[0].getTypeName() + "\" and \"" + arguments[1].getTypeName() + "\"");
}
// 3.返回类型,和传入的参数类型一致
return returnOIResolver.get();
}
@Override
public Object evaluate(DeferredObject[] arguments) throws HiveException {
Object retVal = returnOIResolver.convertIfNecessary(arguments[0].get(), argumentOIs[0]);
if (retVal == null) {
retVal = returnOIResolver.convertIfNecessary(arguments[1].get(), argumentOIs[1]);
}
return retVal;
}
@Override
public String getDisplayString(String[] children) {
StringBuilder sb = new StringBuilder();
sb.append("if ");
sb.append(children[0]);
sb.append(" is null ");
sb.append("returns ");
sb.append(children[1]);
return sb.toString();
}
}
//测试
public class GenericUDFNvlTest {
@Test
public void testGenericUDFNvl() throws HiveException {
// 建立需要的模型
GenericUDFNvl example = new GenericUDFNvl();
ObjectInspector stringOI1 = PrimitiveObjectInspectorFactory.javaStringObjectInspector;
ObjectInspector stringOI2 = PrimitiveObjectInspectorFactory.javaStringObjectInspector;
StringObjectInspector resultInspector = (StringObjectInspector) example.initialize(new ObjectInspector[]{stringOI1, stringOI2});
// 测试结果
Object result1 = example.evaluate(new GenericUDF.DeferredObject[]{new GenericUDF.DeferredJavaObject(null), new GenericUDF.DeferredJavaObject("a")});
Assert.assertEquals("a", resultInspector.getPrimitiveJavaObject(result1));
// 测试结果
Object result2 = example.evaluate(new GenericUDF.DeferredObject[]{new GenericUDF.DeferredJavaObject("dd"), new GenericUDF.DeferredJavaObject("a")});
Assert.assertNotEquals("a", resultInspector.getPrimitiveJavaObject(result2));
}
}
自定义UDAF
UDAF 开发主要涉及到以下两个抽象类:
org.apache.hadoop.hive.ql.udf.generic.AbstractGenericUDAFResolver
org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator
大致上,UDAF 函数读取数据(mapper),聚集一堆 mapper 输出到部分聚集结果(combiner),并且最终创建一个最终的聚集结果(reducer)。因为需要对多个combiner 进行聚集,所以需要保存部分聚集结果。UDAF是需要在Hive的SQL语句和group by联合使用,Hive的group by 对于每个分组,只能返回一条记录,这点和MySQL不一样。开发通用UDAF有两个步骤:
- 编写resolver类,负责类型检查,操作符重载,里面创建evaluator类对象
- 编写evaluator类真正实现UDAF的逻辑
【AbstractGenericUDAFResolver】
Resolver 要覆盖实现 getEvaluator 方法,该方法会根据 sql 传人的参数数据格式指定调用哪个 Evaluator 进行处理。
【GenericUDAFEvaluator】
UDAF 逻辑处理主要发生在 Evaluator 中,要实现该抽象类的几个方法。 ObjectInspector 接口与 GenericUDAFEvaluator 中的内部类 Model。
- ObjectInspector:主要是解耦数据使用与数据格式,使数据流在输入输出端可以切换不同的输入输出格式,不同的 Operator上使用不同的格式
- Model:Model 代表了 UDAF 在 mapreduce 的各个阶段
public static enum Mode {
/**
* PARTIAL1: 这个是mapreduce的map阶段:从原始数据到部分数据聚合
* 将会调用iterate()和terminatePartial()
*/
PARTIAL1,
/**
* PARTIAL2: 这个是mapreduce的map端的Combiner阶段,负责在map端合并map的数据:从部分数据聚合到部分数据聚合
* 将会调用merge() 和 terminatePartial()
*/
PARTIAL2,
/**
* FINAL: mapreduce的reduce阶段:从部分数据的聚合到完全聚合
* 将会调用merge()和terminate()
*/
FINAL,
/**
* COMPLETE: 如果出现了这个阶段,表示mapreduce只有map,没有reduce,所以map端就直接出结果了:从原始数据直接到完全聚合
* 将会调用 iterate()和terminate()
*/
COMPLETE
};
一般情况下,完整的 UDAF 逻辑是一个 MapReduce 过程,如果有Mapper 和Reducer,就会经历 PARTIAL1(Mapper),FINAL(Reducer),如果还有 combiner,那就会经历 PARTIAL1(Mapper),PARTIAL2(combiner),FINAL(Reducer)。而有一些情况下的 Mapreduce,只有Mapper,而没有 Reducer,所以就会只有COMPLETE 阶段,这个阶段直接输入原始数据,出结果。
GenericUDAFEvaluator 的方法
// 确定各个阶段输入输出参数的数据格式 ObjectInspectors,一般负责初始化内部字段,通常初始化用来存放最终结果的变量
public ObjectInspector init(Mode m, ObjectInspector[] parameters) throws HiveException;
// 保存数据聚集结果的类
abstract AggregationBuffer getNewAggregationBuffer() throws HiveException;
// 重置聚集结果
public void reset(AggregationBuffer agg) throws HiveException;
// map阶段,迭代处理输入sql传过来的列数据
public void iterate(AggregationBuffer agg, Object[] parameters) throws HiveException;
// map与combiner结束返回结果,得到部分数据聚集结果
public Object terminatePartial(AggregationBuffer agg) throws HiveException;
// combiner合并map返回的结果,还有reducer合并mapper或combiner返回的结果。
public void merge(AggregationBuffer agg, Object partial) throws HiveException;
// reducer阶段,输出最终结果
public Object terminate(AggregationBuffer agg) throws HiveException;
Model与Evaluator关系:Model 各阶段对应 Evaluator 方法调用

Evaluator 各个阶段下处理 mapreduce 流程

【demo】
package cn.wisec.meerkat.analyseOnHive;
import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.exec.UDFArgumentLengthException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.parse.SemanticException;
import org.apache.hadoop.hive.ql.udf.generic.AbstractGenericUDAFResolver;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFParameterInfo;
import org.apache.hadoop.hive.ql.util.JavaDataModel;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.LongObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.typeinfo.TypeInfo;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
/**
* 通常来说,顶层UDAF类继承{@link org.apache.hadoop.hive.ql.udf.generic.GenericUDAFResolver2}
* 里面编写嵌套类evaluator实现UDAF的逻辑
*
* resolver通常继承org.apache.hadoop.hive.ql.udf.GenericUDAFResolver2,但是更建议继承AbstractGenericUDAFResolver,隔离将来hive接口的变化
* GenericUDAFResolver和GenericUDAFResolver2接口的区别是: 后面的允许evaluator实现利用GenericUDAFParameterInfo可以访问更多的信息,例如DISTINCT限定符,通配符(*)。
*/
public class CountUDAF extends AbstractGenericUDAFResolver {
@Override
public GenericUDAFEvaluator getEvaluator(TypeInfo[] params) throws SemanticException {
if (params.length > 1){
throw new UDFArgumentLengthException("Exactly one argument is expected");
}
return new CountUDAFEvaluator();
}
/**
* 这个构建方法可以判输入的参数是*号或者distinct
*/
@Override
public GenericUDAFEvaluator getEvaluator(GenericUDAFParameterInfo info) throws SemanticException {
ObjectInspector[] parameters = info.getParameterObjectInspectors();
boolean isAllColumns = false;
if (parameters.length == 0){
if (!info.isAllColumns()){
throw new UDFArgumentException("Argument expected");
}
if (info.isDistinct()){
throw new UDFArgumentException("DISTINCT not supported with");
}
isAllColumns = true;
}else if (parameters.length != 1){
throw new UDFArgumentLengthException("Exactly one argument is expected.");
}
return new CountUDAFEvaluator(isAllColumns);
}
public static class CountUDAFEvaluator extends GenericUDAFEvaluator{
private boolean isAllColumns = false;
/**
* 合并结果的类型
*/
private LongObjectInspector aggOI;
private LongWritable result;
public CountUDAFEvaluator() {}
public CountUDAFEvaluator(boolean isAllColumns) {
this.isAllColumns = isAllColumns;
}
/**
* 负责初始化计算函数并设置它的内部状态,result是存放最终结果的
* @param m 代表此时在map-reduce哪个阶段,因为不同的阶段可能在不同的机器上执行,需要重新创建对象partial1,partial2,final,complete
* @param parameters partial1或complete阶段传入的parameters类型是原始输入数据的类型
* partial2和final阶段(执行合并)的parameters类型是partial-aggregations(既合并返回结果的类型),此时parameters长度肯定只有1了
* @return ObjectInspector
* 在partial1和partial2阶段返回局部合并结果的类型,既terminatePartial的类型
* 在complete或final阶段返回总结果的类型,既terminate的类型
* @throws HiveException
*/
@Override
public ObjectInspector init(Mode m, ObjectInspector[] parameters) throws HiveException {
super.init(m, parameters);
//当是combiner和reduce阶段时,获取合并结果的类型,因为需要执行merge方法
//merge方法需要部分合并的结果类型来取得值
if (m == Mode.PARTIAL2 || m == Mode.FINAL){
aggOI = (LongObjectInspector) parameters[0];
}
//保存总结果
result = new LongWritable(0);
//局部合并结果的类型和总合并结果的类型都是long
return PrimitiveObjectInspectorFactory.writableLongObjectInspector;
}
/**
* 定义一个AbstractAggregationBuffer类来缓存合并值
*/
static class CountAgg extends AbstractAggregationBuffer{
long value;
/**
* 返回类型占的字节数,long为8
*/
@Override
public int estimate() {
return JavaDataModel.PRIMITIVES2;
}
}
/**
* 创建缓存合并值的buffer
*/
@Override
public AggregationBuffer getNewAggregationBuffer() throws HiveException {
CountAgg countAgg = new CountAgg();
reset(countAgg);
return countAgg;
}
/**
* 重置合并值
*/
@Override
public void reset(AggregationBuffer agg) throws HiveException {
((CountAgg) agg).value = 0;
}
/**
* map时执行,迭代数据
*/
@Override
public void iterate(AggregationBuffer agg, Object[] parameters) throws HiveException {
//parameters为输入数据
//parameters == null means the input table/split is empty
if (parameters == null){
return;
}
if (isAllColumns){
((CountAgg) agg).value ++;
}else {
boolean countThisRow = true;
for (Object nextParam: parameters){
if (nextParam == null){
countThisRow = false;
break;
}
}
if (countThisRow){
((CountAgg) agg).value++;
}
}
}
/**
* 返回buffer中部分聚合结果,map结束和combiner结束执行
*/
@Override
public Object terminatePartial(AggregationBuffer agg) throws HiveException {
return terminate(agg);
}
/**
* 合并结果,combiner或reduce时执行
*/
@Override
public void merge(AggregationBuffer agg, Object partial) throws HiveException {
if (partial != null){
//累加部分聚合的结果
((CountAgg) agg).value += aggOI.get(partial);
}
}
/**
* 返回buffer中总结果,reduce结束执行或者没有reduce时map结束执行
*/
@Override
public Object terminate(AggregationBuffer agg) throws HiveException {
//每一组执行一次(group by)
result.set(((CountAgg) agg).value);
//返回writable类型
return result;
}
}
}
使用:
hive> add jar /root/udf.jar
hive> create temporary function mycount as 'udf.CountUDAF'
hive> select call, mycount(*) as cn from beauty group by call order by cn desc
hive> select tag, mycount(tag) as cn from beauty lateral view explode(tags) lve_beauty as tag group by tag order by cn desc
自定义UDTF
UDTF用来解决输入一行输出多行的需求。限制:
- No other expressions are allowed in SELECT不能和其他字段一起使用:SELECT pageid,explode(adid_list) AS myCol… is not supported
- UDTF’s can’t be nested 不能嵌套:SELECT explode(explode(adid_list)) AS myCol… is not supported
- GROUP BY/ CLUSTER BY/ DISTRIBUTE BY/ SORT BY is not supported:SELECT explode(adid_list) AS myCol…GROUP BY myCol is not supported
继承org.apache.hadoop.hive.ql.udf.generic.GenericUDTF,实现initialize,process,close三个方法。执行过程:
- UDTF首先会调用initialize方法,确定传入参数的类型并确定 UDTF 生成表的每个字段的数据类型(即输入类型和输出类型),主要是判断输入类型并确定返回的字段类型
- process:调用了 initialize() 后,Hive 将把 UDTF 参数传给 process() 方法,处理一条输入记录,输出若干条结果记录,该方法中,每一次调用 forward() 产生一行;如果产生多列可以将多个列的值放在一个数组中,然后将该数组传入到 forward() 函数
- close:在 process 调用结束后调用,用于进行其它一些额外操作,只执行一次,对需要清理的方法进行清理
给出实现explode函数的demo:explode会将一个数组中每个元素都输出一行,map中每对key-value都输出一行,实现对数据展开
package cn.wisec.meerkat.analyseOnHive;
import org.apache.hadoop.hive.ql.exec.TaskExecutionException;
import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.exec.UDFArgumentLengthException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDTF;
import org.apache.hadoop.hive.serde2.objectinspector.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class MyExplodeUDTF extends GenericUDTF {
private transient ObjectInspector inputOI = null;
/**
* 初始化
* 构建一个StructObjectInspector类型用于输出
* 其中struct的字段构成输出的一行
* 字段名称不重要,因为它们将被用户提供的列别名覆盖
*/
@Override
public StructObjectInspector initialize(StructObjectInspector argOIs) throws UDFArgumentException {
//得到结构体的字段
List<? extends StructField> inputFields = argOIs.getAllStructFieldRefs();
ObjectInspector[] udfInputOIs = new ObjectInspector[inputFields.size()];
for (int i = 0; i < inputFields.size(); i++){
//字段类型
udfInputOIs[i] = inputFields.get(i).getFieldObjectInspector();
}
if (udfInputOIs.length != 1){
throw new UDFArgumentLengthException("explode() takes only one argument");
}
List<String> fieldNames = new ArrayList<>();
List<ObjectInspector> fieldOIs = new ArrayList<>();
switch (udfInputOIs[0].getCategory()){
case LIST:
inputOI = udfInputOIs[0];
//指定list生成的列名,可在as后覆写
fieldNames.add("col");
//获取list元素的类型
fieldOIs.add(((ListObjectInspector) inputOI).getListElementObjectInspector());
break;
case MAP:
inputOI = udfInputOIs[0];
//指定map中key的生成的列名,可在as后覆写
fieldNames.add("key");
//指定map中value的生成的列名,可在as后覆写
fieldNames.add("value");
//得到map中key的类型
fieldOIs.add(((MapObjectInspector)inputOI).getMapKeyObjectInspector());
//得到map中value的类型
fieldOIs.add(((MapObjectInspector)inputOI).getMapValueObjectInspector());
break;
default:`在这里插入代码片`
throw new UDFArgumentException("explode() takes an array or a map as a parameter");
}
//创建一个Struct类型返回
return ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames, fieldOIs);
}
//输出list
private transient Object[] forwardListObj = new Object[1];
//输出map
private transient Object[] forwardMapObj = new Object[2];
/**
* 每行执行一次,输入数据args
* 每调用forward,输出一行
*/
@Override
public void process(Object[] args) throws HiveException {
switch (inputOI.getCategory()){
case LIST:
ListObjectInspector listOI = (ListObjectInspector) inputOI;
List<?> list = listOI.getList(args[0]);
if (list == null){
return;
}
//list中每个元素输出一行
for (Object o: list){
forwardListObj[0] = o;
forward(forwardListObj);
}
break;
case MAP:
MapObjectInspector mapOI = (MapObjectInspector) inputOI;
Map<?, ?> map = mapOI.getMap(args[0]);
if (map == null){
return;
}
//map中每一对输出一行
for (Map.Entry<?, ?> entry: map.entrySet()){
forwardMapObj[0] = entry.getKey();
forwardMapObj[1] = entry.getValue();
forward(forwardMapObj);
}
break;
default:
throw new TaskExecutionException("explode() can only operate on an array or a map");
}
}
@Override
public void close() throws HiveException {
}
}
使用
hive> add jar /root/udtf.jar
hive> create temporary function myexplode as 'udf.MyExplodeUDTF'
hive> select myexplode(tags) as tag from beauty
hive> select myexplode(props) as (k,v) from beauty
hive> select tag, count(tag) as cn from beauty lateral view myexplode(tags) lve_beauty as tag group by tag order by cn desc
永久函数
需要上传 jar 包到 HDFS目录中:
hadoop fs -put hive-udf.jar /user/hive/jars
然后进入 hive 中,创建函数:
hive> create function myFunction as 'udf' using jar 'hdfs:/user/hive/jars/hive-udf.jar';
本文详细介绍了Hive中的自定义函数,包括UDF(单行函数)、UDAF(聚合函数)和UDTF(多行函数)的开发和使用。UDF用于处理单个数据行,UDAF处理多个数据行,UDTF则能从一行数据生成多行。文章通过示例讲解了如何编写和注册自定义函数,并提供了不同类型UDF的API实现细节和注意事项。
5721

被折叠的 条评论
为什么被折叠?



