hive学习笔记之十:用户自定义聚合函数(UDAF)
欢迎访问我的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
hive学习笔记之十:用户自定义聚合函数(UDAF)的更多相关文章
- hive学习笔记之七:内置函数
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- python3.4学习笔记(二十) python strip()函数 去空格\n\r\t函数的用法
python3.4学习笔记(二十) python strip()函数 去空格\n\r\t函数的用法 在Python中字符串处理函数里有三个去空格(包括'\n', '\r', '\t', ' ')的函数 ...
- Spark SQL 用户自定义函数UDF、用户自定义聚合函数UDAF 教程(Java踩坑教学版)
在Spark中,也支持Hive中的自定义函数.自定义函数大致可以分为三种: UDF(User-Defined-Function),即最基本的自定义函数,类似to_char,to_date等 UDAF( ...
- C#学习笔记(十):函数和参数
函数 using System; using System.Collections.Generic; using System.Linq; using System.Text; using Syste ...
- ES[7.6.x]学习笔记(十)聚合查询
聚合查询,它是在搜索的结果上,提供的一些聚合数据信息的方法.比如:求和.最大值.平均数等.聚合查询的类型有很多种,每一种类型都有它自己的目的和输出.在ES中,也有很多种聚合查询,下面我们看看聚合查询的 ...
- Python学习笔记(十)匿名函数
摘抄自:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431843456 ...
- hive学习笔记之一:基本数据类型
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- hive学习笔记之三:内部表和外部表
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- hive学习笔记之四:分区表
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
随机推荐
- python从hello world开始(3)
"""# !usr/bin/env python# -*- coding:utf-8 _*-""""""@Au ...
- IT菜鸟之计算机软件
一.计算机系统的分类 32位操作系统:32/u:更省资源:支持4G以内的内存 64位操作系统:64/u:速度更快:支持4G以外的内存 内存单位:B KB MB GB TB 换算:1024(2的10次 ...
- 059.Python前端Django组件cooki和session
一 会话跟踪技术 1.1 什么是会话 会话是指一个终端用户(服务器)与交互系统(客户端)进行通讯的过程. 1.2 什么是会话跟踪 对同一个用户对服务器的连续的请求和接受响应的监视.(将用户与同一用户发 ...
- 云计算OpenStack共享组件---信息队列rabbitmq(2)
一.MQ 全称为 Message Queue, 消息队列( MQ ) 是一种应用程序对应用程序的通信方法.应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们. 消息传 ...
- Linux基础命令学习记录(一)
使用频繁的Linux命令 一.文件和目录 1.cd命令 cd / 进入根目录 cd .. 返回上一级目录 cd ../.. 返回上两级目录 cd 进入个人的主目录 cd ~ 进入个人的主目录 cd - ...
- Prometheus监控软件部署方法
背景:负责基于区块链的某公正项目的状态上报模块设计编码,基于Prometheus进行二次开发 1.说明Prometheus 是一个开源的服务监控软件,它通过 HTTP 协议从远程机器收集数据并存储在本 ...
- 一文读懂一条 SQL 查询语句是如何执行的
2001 年 MySQL 发布 3.23 版本,自此便开始获得广泛应用,随着不断地升级迭代,至今 MySQL 已经走过了 20 个年头. 为了充分发挥 MySQL 的性能并顺利地使用,就必须正确理解其 ...
- bat脚本总结
1.修改密码 @echo off set /p b=请输入新密码: net user %username% %b% echo 密码修改成功 %b% 请牢记你的密码 pause 2.删除hosts文件并 ...
- descriptor 'decode' requires a 'bytes' object but received a 'NoneType'
记录在使用python过程中踩的坑------ 使用xlwt库对excel文件进行保存时报错 descriptor 'decode' requires a 'bytes' object but rec ...
- 201871030137-杨钦颖 实验三 结对项目—《D{0-1}KP 实例数据集算法实验平台》项目报告
201871030137-杨钦颖 实验三 结对项目-<D{0-1}KP 实例数据集算法实验平台>项目报告 项目 内容 课程班级博客链接 班级连接 这个作业要求链接 作业连接 我的课程学习目 ...