系列第一篇,先扯扯。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 {上面这个是UDF.java的主要代码,所有UDF类(指类别)的UDF都必须继承自这个父类。可以看到它主要定义了一个UDFMethodResolver类的属性,这个属性的用处待会会详细说明。它在注释中还规定了,所有子类必须实现一个或多个evaluate方法给Hive框架调用,不难猜到,这个evaluate方法正是所有实现类的逻辑部分所在。
/**
* 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;
}
}
public abstract class GenericUDF {上面这个是Generic.java的主要代码,同样,所有GenericUDF类(亦指类别)的UDF都必须继承自这个父类。它多了一个initialize方法,这个方法对于一个GenericUDF的实例来说只会调用一次(似乎是废话,不然就不叫这个名字了~);它少了UDFMethodResolver类的属性,因为这个属性的用处被initialize方法替代了。GenericUDF类直接定义了evaluate方法,而且不需要子类重载它的参数定义,当然功能还是一样的。
/**
* 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;
}
好,接下来说说为什么要有这样的改进吧:一个是可以接受和返回复杂数据类型了,例如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) {很显然,它是在根据实际的参数类型,在UDF类中寻找一个最合适的evaluate方法。回到initialize方法,接下来的内容就好理解了,依然是生成对应的ObjectInspector对象……
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的挖掘就写到这吧,好久没有写技术博客哩~