歡迎訪問我的GitHub
這裡分類和彙總了欣宸的全部原創(含配套原始碼):https://github.com/zq2599/blog_demos
本篇概覽
- 本文是《hive學習筆記》的第十篇,前文實踐過UDF的開發、部署、使用,那個UDF適用於一進一出的場景,例如將每條記錄的指定欄位轉為大寫;
- 除了一進一出,在使用group by的SQL中,多進一出也是常見場景,例如hive自帶的avg、sum都是多進一出,這個場景的自定義函式叫做使用者自定義聚合函式(User Defiend Aggregate Function,UDAF),UDAF的開發比一進一出要複雜一些,本篇文章就一起來實戰UDAF開發;
- 本文開發的UDAF名為udf_fieldlength ,用於group by的時候,統計指定欄位在每個分組中的總長度;
準備工作
- 在一些舊版的教程和文件中,都會提到UDAF開發的關鍵是繼承UDAF.java;
- 開啟hive-exec的1.2.2版本原始碼,卻發現UDAF類已被註解為Deprecated;
- UDAF類被廢棄後,推薦的替代品有兩種:實現GenericUDAFResolver2介面,或者繼承AbstractGenericUDAFResolver類;
- 現在新問題來了:上述兩種替代品,我們們在做UDAF的時候該用哪一種呢?
- 開啟AbstractGenericUDAFResolver類的原始碼瞅一眼,如下所示,是否有種恍然大悟的感覺,這個類自身就是GenericUDAFResolver2介面的實現類:
public abstract class AbstractGenericUDAFResolver
implements GenericUDAFResolver2
{
@SuppressWarnings("deprecation")
@Override
public GenericUDAFEvaluator getEvaluator(GenericUDAFParameterInfo info)
throws SemanticException {
if (info.isAllColumns()) {
throw new SemanticException(
"The specified syntax for UDAF invocation is invalid.");
}
return getEvaluator(info.getParameters());
}
@Override
public GenericUDAFEvaluator getEvaluator(TypeInfo[] info)
throws SemanticException {
throw new SemanticException(
"This UDAF does not support the deprecated getEvaluator() method.");
}
}
- 既然原始碼都看了,也就沒啥好糾結的了,繼承父類還是實現介面都可以,您自己看著選吧,我這裡選的是繼承AbstractGenericUDAFResolver類;
關於UDAF的四個階段
- 在編碼前,要先了解UDAF的四個階段,定義在GenericUDAFEvaluator的Mode列舉中:
- COMPLETE:如果mapreduce只有map而沒有reduce,就會進入這個階段;
- PARTIAL1:正常mapreduce的map階段;
- PARTIAL2:正常mapreduce的combiner階段;
- FINAL:正常mapreduce的reduce階段;
每個階段被呼叫的方法
- 開發UDAF時,要繼承抽象類GenericUDAFEvaluator,裡面有多個抽象方法,在不同的階段,會呼叫到這些方法中的一個或多個;
- 下圖對每個階段呼叫了哪些方法說得很清楚:
- 下圖對順序執行的三個階段和涉及方法做了詳細說明:
- 以上兩張圖片的出處都是kent7306的文章《Hive UDAF開發詳解》,地址:https://blog.csdn.net/kent7306/article/details/50110067
- 上面兩幅圖將抽象方法和每個階段的關係都梳理得很清晰了,接下來我們們開始編碼;
原始碼下載
- 如果您不想編碼,可以在GitHub下載所有原始碼,地址和連結資訊如下表所示:
名稱 | 連結 | 備註 |
---|---|---|
專案主頁 | https://github.com/zq2599/blog_demos | 該專案在GitHub上的主頁 |
git倉庫地址(https) | https://github.com/zq2599/blog_demos.git | 該專案原始碼的倉庫地址,https協議 |
git倉庫地址(ssh) | git@github.com:zq2599/blog_demos.git | 該專案原始碼的倉庫地址,ssh協議 |
- 這個git專案中有多個資料夾,本章的應用在hiveudf資料夾下,如下圖紅框所示:
UDAF開發步驟簡述
開發UDAF分為以下幾步:
- 新建類FieldLengthAggregationBuffer,用於儲存中間結果,該類需繼承AbstractAggregationBuffer;
- 新建類FieldLengthUDAFEvaluator,用於實現四個階段中會被呼叫的方法,該類需繼承GenericUDAFEvaluator;
- 新建類FieldLength,用於在hive中註冊UDAF,裡面會例項化FieldLengthUDAFEvaluator,該類需繼承AbstractGenericUDAFResolver;
- 編譯構建,得到jar;
- 在hive新增jar;
- 在hive註冊函式;
接下來就按照上述步驟開始操作;
開發
- 開啟前文新建的hiveudf工程,新建FieldLengthAggregationBuffer.java,這個類的作用是快取中間計算結果,每次計算的結果都放入這裡面,被傳遞給下個階段,其成員變數value用來儲存累加資料:
package com.bolingcavalry.hiveudf.udaf;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator;
import org.apache.hadoop.hive.ql.util.JavaDataModel;
public class FieldLengthAggregationBuffer extends GenericUDAFEvaluator.AbstractAggregationBuffer {
private Integer value = 0;
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
public void add(int addValue) {
synchronized (value) {
value += addValue;
}
}
/**
* 合併值緩衝區大小,這裡是用來儲存字串長度,因此設為4byte
* @return
*/
@Override
public int estimate() {
return JavaDataModel.PRIMITIVES1;
}
}
- 新建FieldLengthUDAFEvaluator.java,裡面是整個UDAF邏輯實現,關鍵程式碼已經新增了註釋,請結合前面的圖片來理解,核心思路是iterate將當前分組的欄位處理完畢,merger把分散的資料合併起來,再由terminate決定當前分組計算結果:
package com.bolingcavalry.hiveudf.udaf;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector;
/**
* @Description: 這裡是UDAF的實際處理類
* @author: willzhao E-mail: zq2599@gmail.com
* @date: 2020/11/4 9:57
*/
public class FieldLengthUDAFEvaluator extends GenericUDAFEvaluator {
PrimitiveObjectInspector inputOI;
ObjectInspector outputOI;
PrimitiveObjectInspector integerOI;
/**
* 每個階段都會被執行的方法,
* 這裡面主要是把每個階段要用到的輸入輸出inspector好,其他方法被呼叫時就能直接使用了
* @param m
* @param parameters
* @return
* @throws HiveException
*/
@Override
public ObjectInspector init(Mode m, ObjectInspector[] parameters) throws HiveException {
super.init(m, parameters);
// COMPLETE或者PARTIAL1,輸入的都是資料庫的原始資料
if(Mode.PARTIAL1.equals(m) || Mode.COMPLETE.equals(m)) {
inputOI = (PrimitiveObjectInspector) parameters[0];
} else {
// PARTIAL2和FINAL階段,都是基於前一個階段init返回值作為parameters入參
integerOI = (PrimitiveObjectInspector) parameters[0];
}
outputOI = ObjectInspectorFactory.getReflectionObjectInspector(
Integer.class,
ObjectInspectorFactory.ObjectInspectorOptions.JAVA
);
// 給下一個階段用的,即告訴下一個階段,自己輸出資料的型別
return outputOI;
}
public AggregationBuffer getNewAggregationBuffer() throws HiveException {
return new FieldLengthAggregationBuffer();
}
/**
* 重置,將總數清理掉
* @param agg
* @throws HiveException
*/
public void reset(AggregationBuffer agg) throws HiveException {
((FieldLengthAggregationBuffer)agg).setValue(0);
}
/**
* 不斷被呼叫執行的方法,最終資料都儲存在agg中
* @param agg
* @param parameters
* @throws HiveException
*/
public void iterate(AggregationBuffer agg, Object[] parameters) throws HiveException {
if(null==parameters || parameters.length<1) {
return;
}
Object javaObj = inputOI.getPrimitiveJavaObject(parameters[0]);
((FieldLengthAggregationBuffer)agg).add(String.valueOf(javaObj).length());
}
/**
* group by的時候返回當前分組的最終結果
* @param agg
* @return
* @throws HiveException
*/
public Object terminate(AggregationBuffer agg) throws HiveException {
return ((FieldLengthAggregationBuffer)agg).getValue();
}
/**
* 當前階段結束時執行的方法,返回的是部分聚合的結果(map、combiner)
* @param agg
* @return
* @throws HiveException
*/
public Object terminatePartial(AggregationBuffer agg) throws HiveException {
return terminate(agg);
}
/**
* 合併資料,將總長度加入到快取物件中(combiner或reduce)
* @param agg
* @param partial
* @throws HiveException
*/
public void merge(AggregationBuffer agg, Object partial) throws HiveException {
((FieldLengthAggregationBuffer) agg).add((Integer)integerOI.getPrimitiveJavaObject(partial));
}
}
- 最後是FieldLength.java,該類註冊UDAF到hive時用到的,負責例項化FieldLengthUDAFEvaluator,給hive使用:
package com.bolingcavalry.hiveudf.udaf;
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.serde2.typeinfo.TypeInfo;
public class FieldLength extends AbstractGenericUDAFResolver {
@Override
public GenericUDAFEvaluator getEvaluator(GenericUDAFParameterInfo info) throws SemanticException {
return new FieldLengthUDAFEvaluator();
}
@Override
public GenericUDAFEvaluator getEvaluator(TypeInfo[] info) throws SemanticException {
return new FieldLengthUDAFEvaluator();
}
}
至此,編碼完成,接下來是部署和體驗;
部署和體驗
本次部署的註冊方式是臨時函式,如果您想註冊為永久函式,請參考前文;
- 在pom.xml所在目錄執行mvn clean package -U,即可編譯構建;
- 在target目錄得到檔案hiveudf-1.0-SNAPSHOT.jar;
- 上傳到hive伺服器,我這裡是放在/home/hadoop/udf目錄;
- 進入hive會話,執行以下命令新增jar:
add jar /home/hadoop/udf/hiveudf-1.0-SNAPSHOT.jar;
- 執行以下命令註冊:
create temporary function udf_fieldlength as 'com.bolingcavalry.hiveudf.udaf.FieldLength';
- 找一個適合執行group by的表試試,我這裡是前面的文章中建立的address表,完整資料如下:
hive> select * from address;
OK
1 guangdong guangzhou
2 guangdong shenzhen
3 shanxi xian
4 shanxi hanzhong
6 jiangshu nanjing
- 執行下面的SQL:
select province, count(city), udf_fieldlength(city) from address group by province;
執行結果如下,可見guangdong的guangzhou和shenzhen總長度為17,jiangsu的nanjing為7,shanxi的xian和hanzhong總長度12,符合預期:
Total MapReduce CPU Time Spent: 2 seconds 730 msec
OK
guangdong 2 17
jiangshu 1 7
shanxi 2 12
Time taken: 28.484 seconds, Fetched: 3 row(s)
至此,UDAF的學習和實踐就完成了,我們們掌握了多進一出的函式開發,由於涉及到多個階段和外部呼叫的邏輯,使得UDAF的開發難度略大,接下來的文章是一進多出的開發,會簡單一些。
你不孤單,欣宸原創一路相伴
歡迎關注公眾號:程式設計師欣宸
微信搜尋「程式設計師欣宸」,我是欣宸,期待與您一同暢遊Java世界...
https://github.com/zq2599/blog_demos