Co2y's Blog

Hive UDF开发的一些坑

Hive UDF的开发,实习的第一个任务,以前也写过,记录点东西。

UDF

Hive UDF开发可以继承UDF或者GenericUDF。总结以下几点:

  • UDF比较简单,只需要实现一个evaluate方法,Hive会根据实际UDF传入的参数来调用对应的evaluate方法,这也正是我们在源码中看到有些UDF的evaluate方法重载了很多次来支持不同类型的输入。
  • UDF的返回值一定要是writable的,比如TextIntegerWritable等。
  • GenericUDF需要实现三个方法,initializeevaluategetDisplayStringevaluate方法和UDF的evaluate是一个含义,对每行的数据做处理。getDisplayString一定不能返回null。initialize只在每次调用开始时执行一次,主要用于获得参数类型,检查参数等。从源码的内置GenericUDF中可以看出,主要是要掌握对ObjectInspector及其相关的几个类。下面简单介绍下常用的方法,以及参数从Hive类型到Java标准类型的转换。

ObjectInspector

  1. 获取参数类型 ObjectInspector.getCategory();
  2. 获取converter ObjectInspectorConverters.getConverter(arguments, PrimitiveObjectInspectorFactory.writableXXXObjectInspector);
  3. 类型转换 converter.convert(argument.get())
  4. 基本类型的ObjectInspector的操作,强制转换即可 ((PrimitiveObjectInspector) argument).getPrimitiveCategory()
  5. 相关工厂类和工具类的使用,主要是ObjectInspectorFactoryPrimitiveObjectInspectorUtils,例如类型转换也可以用PrimitiveObjectInspectorUtils.getInt(object, inputOI)

UDTF

UDTF是一对多的生成函数,用的不多,写起来也很简单。继承GenericUDTF包括以下几个函数:initializeprocessclosetoString,基本上都是字面意思。处理逻辑主要在process,和GenericUDF一样,输入是一行,但是UDTF可以通过循环forward来生成多行数据,forward的是一个Object[],里面是writable的元素。

UDAF

UDAF是最复杂的,UDAF通常包括两个静态内部类,外部类继承自AbstractGenericUDAFResolver只需要实现一个方法getEvaluatorgetEvaluator返回一个GenericUDAFEvaluator,也就是第一个内部类需要继承实现的GenericUDAFEvaluator,包括initgetNewAggregationBufferresetiterateterminatePartialmergeterminate,以及另一个内部类AggregationBuffer,也就是getNewAggregationBuffer返回的对象。这些方法主要对应MapReduce的不同阶段,这里不详细介绍,主要记录遇到的坑,包括四点

  1. AggregationBuffer的初始化顺序,或者说调用顺序不直观,有的初始化工作需要在运行时做。
  2. outputOI = ObjectInspectorUtils.getStandardObjectInspector(inputOI),这条语句可以传入第二个参数JAVA等,默认是由Hive来判断,这里一定要注意前后一致,不然很容易出现类似与IntIntegerWritable之间转换的问题。
  3. ObjectInspectorUtils.compare(maxagg.o, outputOI, object, inputOI); 其中outputOI通常是由inputOI得到的,见第2点,具体可参考内置的Max函数。
  4. struct的结果可以这样构造ObjectInspectorFactory.getStandardStructObjectInspector(fName, fieldOIs),调用时可以转换成LazyBinaryStruct来操作, objects = ((LazyBinaryStruct) partial).getFieldsAsList();

其他

这几天开发了几十个UDF大部分都是与时间处理有关的,这里记录下Hive和Java关于时间处理的一些问题。

  1. Hive在许多新版本,例如1.2、1.3、2.0中加入了一些新的UDF是老版本所没有的,可以迁移过来,但是也要迁移相关的一些类,有的可能需要新版本的GenericUDF
  2. 日期处理通常是由一个统一的模块来解析不同含义变量代表的时间,例如StringDateTimestamp甚至也可以用int来表示时间。通常用SimpleDateFormat来parse这些参数,需要注意format的格式,可以通过调整顺序等方式,达到最优的parse效果。
  3. Hive里不像MySQL区分DateDateTime,以及Time。需要自行处理。
  4. Java8对时间有了很好的支持,包括1.8以前支持的Date只能精确到毫秒。Java8的time包可以更加精确,且对Time,Date等做了区分。
  5. Joda310这些第三方库对时间处理支持很好,提供了许多实用的函数。比DateCalendar好用。
  6. 滑动窗口取最值问题:双端队列维护递增序列。
  7. UDF的泛型要么是参数重载,要么是switch-case来确定参数的ObjectInspector得到的类型。