hive udaf總結

0xx01 概述

hive的udaf全稱 User-Defined Aggregation Functions。hive有兩種udaf,simple and generic.區別如下

Simple UDAFs, as the name implies, are rather simple to write, but incur performance penalties because of the use of Java Reflection, and do not allow features such as variable-length argument lists. Generic UDAFs allow all these features, but are perhaps not quite as intuitive to write as Simple UDAFs.
簡單 UDAF 因爲使用Java反射導致性能損失,並且不允許使用變長參數列表等功能,已經被棄用了。通用的UDAF允許使用哪些特性,但是寫起來不如簡單的udaf直觀。

所以以下的特性是基於generic udaf來說明的。

​ 開發自定義聚類函數涉及到兩個抽象類

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

0xx02 抽象類介紹

​ 因爲這兩個都是抽象類的API,所以需要明確hive處理的流程,結合流程來進行理解。hive本質上是一個hdfs client,它將我們的sql轉換成對應的mapreduce來進行執行,對應的mapreduce過程分別包含 maper,[combiner],reducer。

​ 所以udaf的執行應該是讀取數據(mapper),根據設定(可能不存在)在reduce之前聚集一堆mapper計算部分結果(combiner),最終得到出一個最終結果(reducer)。因爲在mapreduce模型中,符合分而治之然後彙總的思想,所以需要將不同機器上得到的結果進行聚集(如果combiner存在,我們需要將多個部分結果進行聚集,即使不存在也需要將多個mapper的結果進行聚集),所以需要保存部分聚集結果。

關於 AbstractGenericUDAFResolver

這個抽象類的作用是對輸入的參數進行校驗,需要被實現,然後重寫以下方法

public GenericUDAFEvaluator getEvaluator(TypeInfo[] info) throws SemanticException {}

關於TypeInfo

TypeInfo[] 是對傳入參數類型信息的抽象,支持八種類型
	1. Primitive objects (String, Number, etc)
 	2. List objects (a list of objects of a single type) 
 	3. Map objects (a map from objects of one type to objects of another type)
  4. Struct objects (a list of fields with names and their own types) 
  5. Union objects
  6. Decimal objects 
  7. Char objects 
  8. Varchar objects
通過它可以進行參數類別(getCategory())和 類型名稱(getTypeName())以及限定類型名(getQualifiedName() 內部通過getTypeName()執行)的獲取

返回值是一個GenericUDAFEvaluator的具體實現。

關於GenericUDAFEvaluator

​ udaf的具體處理邏輯需要在這裏實現,需要實現該抽象類的如下方法

    public static class MyGeneric extends GenericUDAFEvaluator {
      
        //每個階段都會執行init,不同階段對應的parameters是不一樣的,
        //在map階段parameters代表的是sql語句中每個udaf對應參數的ObjectInspector,
        //而在combiner或者reducer中parameters代表部分聚合結果對應的ObjectInspector。所以要區分對待。
        //從iterate和merge的參數類型(一個數組類型,一個是object)就能看出來。
        @Override
        public ObjectInspector init(Mode m, ObjectInspector[] parameters) throws HiveException {
          //這行代碼必須有
            super.init(m,parameters);
        }
      
      //創建新的聚合計算的需要的內存,用來存儲mapper,combiner,reducer運算過程中的相加總和。
      // 保存數據聚集結果
        public AggregationBuffer getNewAggregationBuffer() throws HiveException {
            return null;
        }
      
      //mapreduce支持mapper和reducer的重用,所以爲了兼容,也需要做內存的重用。
      // 重置聚集結果
				@Override
        public void reset(AggregationBuffer agg) throws HiveException {
        }
      
      //map階段調用,迭代處理輸入sql傳過來的列數據,對每一列數據進行處理
				@Override
        public void iterate(AggregationBuffer agg, Object[] parameters) throws HiveException {}
			// map與combiner結束返回結果,得到部分數據聚集結果	
      @Override
        public Object terminatePartial(AggregationBuffer agg) throws HiveException {
            return null;
        }
      
      //combiner合併map返回的結果,還有reducer合併mapper或combiner返回的結果。
				@Override
        public void merge(AggregationBuffer agg, Object partial) throws HiveException {
        }
      
      //reducer返回結果,或者是隻有mapper,沒有reducer時,在mapper端返回結果。
				@Override
        public Object terminate(AggregationBuffer agg) throws HiveException {
            return null;
        }
    }

Mode

因爲需要根據mapReduce的不同階段(map,combiner,reduce)進行不同的代碼處理,這就涉及到GenericUDAFEvaluator的嵌套類Mode,這個類很重要,它表示了udaf在mapreduce的各個階段,理解Mode的含義,就可以理解了hive的UDAF的運行流程。

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階段,這個階段直接輸入原始數據,出結果。

objectInspector

同時還需要了解objectInspector接口,作用主要是解耦數據使用與數據格式,使得數據流在輸入輸出端切換不同的輸入輸出格式,不同的Operator上使用不同的格式。

圖解Model和Evaluator關係
在這裏插入圖片描述

在這裏插入圖片描述

這裏借用org.apache.hadoop.hive.ql.udf.generic.GenericUDAFSum的代碼來進行分析

public static class GenericUDAFSumLong extends GenericUDAFEvaluator {

private PrimitiveObjectInspector inputOI;
    private LongWritable result;

   //這個方法返回了UDAF的返回類型,這裏確定了sum自定義函數的返回類型是Long類型
    @Override
    public ObjectInspector init(Mode m, ObjectInspector[] parameters) throws HiveException {
      assert (parameters.length == 1);
      super.init(m, parameters);
      result = new LongWritable(0);
      inputOI = (PrimitiveObjectInspector) parameters[0];
      return PrimitiveObjectInspectorFactory.writableLongObjectInspector;
    }

    /** 存儲sum的值的類 */
    static class SumLongAgg implements AggregationBuffer {
      boolean empty;
      long sum;
    }

    //創建新的聚合計算的需要的內存,用來存儲mapper,combiner,reducer運算過程中的相加總和。

    @Override
    public AggregationBuffer getNewAggregationBuffer() throws HiveException {
      SumLongAgg result = new SumLongAgg();
      reset(result);
      return result;
    }
    
    //mapreduce支持mapper和reducer的重用,所以爲了兼容,也需要做內存的重用。

    @Override
    public void reset(AggregationBuffer agg) throws HiveException {
      SumLongAgg myagg = (SumLongAgg) agg;
      myagg.empty = true;
      myagg.sum = 0;
    }

    private boolean warned = false;
  
    //map階段調用,只要把保存當前和的對象agg,再加上輸入的參數,就可以了。
    @Override
    public void iterate(AggregationBuffer agg, Object[] parameters) throws HiveException {
      assert (parameters.length == 1);
      try {
        merge(agg, parameters[0]);
      } catch (NumberFormatException e) {
        if (!warned) {
          warned = true;
          LOG.warn(getClass().getSimpleName() + " "
              + StringUtils.stringifyException(e));
        }
      }
    }
   //mapper結束要返回的結果,還有combiner結束返回的結果
    @Override
    public Object terminatePartial(AggregationBuffer agg) throws HiveException {
      return terminate(agg);
    }
    
    //combiner合併map返回的結果,還有reducer合併mapper或combiner返回的結果。
    @Override
    public void merge(AggregationBuffer agg, Object partial) throws HiveException {
      if (partial != null) {
        SumLongAgg myagg = (SumLongAgg) agg;
        myagg.sum += PrimitiveObjectInspectorUtils.getLong(partial, inputOI);
        myagg.empty = false;
      }
    }
     
    //reducer返回結果,或者是隻有mapper,沒有reducer時,在mapper端返回結果。
    @Override
    public Object terminate(AggregationBuffer agg) throws HiveException {
      SumLongAgg myagg = (SumLongAgg) agg;
      if (myagg.empty) {
        return null;
      }
      result.set(myagg.sum);
      return result;
    }

  }

備註:關於ObjectInspector(https://blog.csdn.net/czw698/article/details/38407817)

Hive 中operator處理的數據都是Object加上一個ObjectInspector對象,我們可以非常方便的通過該ObjectInspector對象瞭解到上游傳過來的值是什麼,如果是Struct對象,可以進一步瞭解到有多少了Filed,進而獲取每個Filed的值,而且通過Serde可以方便的完成數據的序列化操作。

0xx03 總結

在 hive-exec對應的jar包中的org.apache.hadoop.hive.ql.exec.FunctionRegistry類描述了內置的udaf,如果想進階學習,可以學習下這些代碼

org.apache.hadoop.hive.ql.udf.generic.GenericUDAFAverage這裏可以更詳細的看到UDAF對hive運行流程的控制。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章