系列第一篇,先扯扯。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;
- }
- }
- 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;
- }
好,接下来说说为什么要有这样的改进吧:一个是可以接受和返回复杂数据类型了,例如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的挖掘就写到这吧,好久没有写技术博客哩~