分享

hive udf开发场景案例【工作经验】分享

阿飞 发表于 2021-1-6 16:23:06 [显示全部楼层] 回帖奖励 阅读模式 关闭右栏 0 1371
本帖最后由 阿飞 于 2021-1-6 17:03 编辑


问题导读

1.Hive udf的需求是什么?
2.为何使用配置文件?
3.实现udf的核心是什么?



项目github地址:bitcarmanlee easy-algorithm-interview-and-practice
欢迎大家star,留言,一起学习进步

关于hive的udf介绍,就不多啰嗦了。网上的教程一抓一大把,也可以上apache的官网去查阅相关资料,我就省了翻译的时间了。重点给大家带来干货,手把手教会你怎样开发一个udf函数,已经如何部署到服务器上的hive环境中运行。用最简单的话来说,就是教大家怎么让自己开发的udf跑起来。。。

项目需求
做数据挖掘项目中,常见的需求之一就是分析节假日订单跟平时订单的区别。于是,我们需要统计节假日订单的分布情况。但是hive中显然没有内置,也不可能内置此函数,因为每年的节假日都是变得嘛。于是,我们就需要自己开发一个udf来满足需求了。

配置文件
考虑到每年的节假日其实并不多,也就那么二十多天,于是采用配置文件的方式,直接将节假日写死在配置文件中。如果需要添加,改配置文件就行。毕竟一年也就这么二十多天,工作量并不大。。。
自己写的配置文件如下:

  1. 20140101=元旦
  2. 20140131=春节
  3. 20140201=春节
  4. 20140202=春节
  5. 20140203=春节
  6. 20140204=春节
  7. 20140205=春节
  8. 20140206=春节
  9. 20140405=清明节
  10. 20140406=清明节
  11. 20140407=清明节
  12. 20140501=五一劳动节
  13. 20140502=五一劳动节
  14. 20140503=五一劳动节
  15. 20140531=端午节
  16. 20140601=端午节
  17. 20140602=端午节
  18. 20140906=中秋节
  19. 20140907=中秋节
  20. 20140908=中秋节
  21. 20141001=国庆节
  22. 20141002=国庆节
  23. 20141003=国庆节
  24. 20141004=国庆节
  25. 20141005=国庆节
  26. 20141006=国庆节
  27. 20141007=国庆节

  28. 20150101=元旦
  29. 20150102=元旦
  30. 20150103=元旦
  31. 20150218=春节
  32. 20150219=春节
  33. 20150220=春节
  34. 20150221=春节
  35. 20150222=春节
  36. 20150223=春节
  37. 20150224=春节
  38. 20150404=清明节
  39. 20150405=清明节
  40. 20150406=清明节
  41. 20150501=五一劳动节
  42. 20150502=五一劳动节
  43. 20150503=五一劳动节
  44. 20150620=端午节
  45. 20150621=端午节
  46. 20150622=端午节
  47. 20150926=中秋节
  48. 20150927=中秋节
  49. 20151001=国庆节
  50. 20151002=国庆节
  51. 20151003=国庆节
  52. 20151004=国庆节
  53. 20151005=国庆节
  54. 20151006=国庆节
  55. 20151007=国庆节

  56. 20160101=元旦
  57. 20160102=元旦
  58. 20160103=元旦
  59. 20160207=春节
  60. 20160208=春节
  61. 20160209=春节
  62. 20160210=春节
  63. 20160211=春节
  64. 20160212=春节
  65. 20160213=春节
  66. 20160402=清明节
  67. 20160403=清明节
  68. 20160404=清明节
  69. 20160430=五一劳动节
  70. 20160501=五一劳动节
  71. 20160502=五一劳动节
  72. 20160609=端午节
  73. 20160610=端午节
  74. 20160611=端午节
  75. 20160915=中秋节
  76. 20160916=中秋节
  77. 20160917=中秋节
  78. 20161001=国庆节
  79. 20161002=国庆节
  80. 20161003=国庆节
  81. 20161004=国庆节
  82. 20161005=国庆节
  83. 20161006=国庆节
  84. 20161007=国庆节
复制代码


以上包含有2014,2015,2016三年的假期。如果想增加,继续往此文件里添加就是。。。

新建maven项目
现在的java项目必须是用maven管理,方便又实用,谁用谁知道,不多解释。maven项目自然只需要知道pom.xml即可。pom文件如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>udf.leilei.elong.com</groupId>
  <artifactId>festival</artifactId>
  <version>1.0</version>
  
  <name>hive</name>
  <url>http://maven.apache.org</url>
  
  <properties>
          <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  
  <dependencies>
          <dependency>
                  <groupId>org.apache.hive</groupId>
                  <artifactId>hive-exec</artifactId>
                  <version>0.13.0</version>
          </dependency>
          <dependency>
                  <groupId>org.apache.hadoop</groupId>
                  <artifactId>hadoop-common</artifactId>
                  <version>2.5.0</version>
          </dependency>
  </dependencies>
  <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.2</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
</build>
</project>
提醒对maven不是很熟的同学们:因为我们项目里有依赖的jar包,所以必须加上 maven-shade-plugin 插件。当然你用 maven-assembly-plugin 也是没有问题的。如果你对maven不是那么熟悉,别管了,先粘过去,跑起来再说吧。。。


UDF具体代码开发

  1. package festival;

  2. import java.io.InputStreamReader;
  3. import java.text.NumberFormat;
  4. import java.util.HashMap;
  5. import java.util.Properties;
  6. import java.util.regex.Matcher;
  7. import java.util.regex.Pattern;

  8. import org.apache.hadoop.hive.ql.exec.UDF;

  9. public class FestivalType extends UDF{

  10.         private HashMap<String,String> festivalMap = new HashMap<String, String>();
  11.        
  12.         public FestivalType() throws Exception {
  13.                 InputStreamReader propFile = new InputStreamReader(getClass().getClassLoader().getResourceAsStream("festival_date.properties"), "UTF-8");
  14.                 Properties prop = new Properties();
  15.                 prop.load(propFile);
  16.                 for(Object key:prop.keySet()) {
  17.                         festivalMap.put(key.toString(), prop.getProperty(key.toString()));
  18.                 }
  19.         }
  20.        
  21.         //解决double转string的科学计数法的问题
  22.         public String double_to_string(double dou) {
  23.                 Double dou_obj = new Double(dou);
  24.                 NumberFormat nf = NumberFormat.getInstance();
  25.                 nf.setGroupingUsed(false);
  26.                 String dou_str = nf.format(dou_obj);
  27.                 return dou_str;
  28.         }
  29.        
  30.         public String evaluate(double date_dou) {
  31.                 String date_str = this.double_to_string(date_dou);
  32.                 return evaluate(date_str);
  33.         }
  34.        
  35.         public int evaluate(double date_dou,String flag) {
  36.                 String date_str = this.double_to_string(date_dou);
  37.                 return evaluate(date_str,flag);
  38.         }
  39.        
  40.         public String evaluate(String date_str) {
  41.                 if (! this.match_date(date_str).equals("null")) {
  42.                         date_str = this.match_date(date_str);
  43.                         return festivalMap.get(date_str) == null ? "null" : festivalMap.get(date_str);
  44.                 } else {
  45.                         return "null";
  46.                 }
  47.         }
  48.        
  49.         public int evaluate(String date_str, String flag) {
  50.                 if (flag.equals("count") && ! this.match_date(date_str).equals("null")) {
  51.                         date_str = this.match_date(date_str);
  52.                         return festivalMap.get(date_str) == null ? 0 :1;
  53.                 } else {
  54.                         return 0;
  55.                 }
  56.         }
  57.        
  58.         public String match_date(String date_str) {
  59.                 //匹配20160101这种日期格式
  60.                 Pattern pat_common = Pattern.compile("\\d{8}");
  61.                 Matcher mat_common = pat_common.matcher(date_str);
  62.                
  63.                 //匹配2016-01-01这种日期格式
  64.                 Pattern pat_strike = Pattern.compile("\\d{4}-\\d{2}-\\d{2}");
  65.                 Matcher mat_strike = pat_strike.matcher(date_str);
  66.                
  67.                 //匹配 2016-01-01 10:35:46 这种日期格式
  68.                 Pattern pat_colon = Pattern.compile("\\d{4}-\\d{2}-\\d{2}(\\s)+\\d{2}:\\d{2}:\\d{2}");
  69.                 Matcher mat_colon = pat_colon.matcher(date_str);
  70.                
  71.                 if (mat_colon.find()) {
  72.                         return date_str.replace("-", "").substring(0, 8);
  73.                 } else if(mat_strike.find()) {
  74.                         return date_str.replace("-", "");
  75.                 } else if (mat_common.find()) {
  76.                         return date_str;
  77.                 } else {
  78.                         return "null";
  79.                 }
  80.         }

  81.         //测试的main方法
  82.         public static void main(String[] args) throws Exception{
  83.                 FestivalType fes = new FestivalType();
  84.                 String date_str = "20150101";
  85.                 System.out.println(fes.evaluate(date_str));
  86.                
  87.                 String date_str1 = "20160101";
  88.                 String result = fes.evaluate(date_str1);
  89.                 System.out.println("result is:" + result);
  90.                
  91.                 double date_dou = 20160101;
  92.                 int result_dou = fes.evaluate(date_dou,"count");
  93.                 System.out.println(result_dou);
  94.                 System.out.println(date_dou);
  95.         }
  96. }

复制代码
将之前的配置文件放在src/main/resources下面,然后运行此代码,输出如下:
  1. 节日类型是:元旦
  2. 节日天数:1
复制代码



因为我们这篇文章的目的是让udf以最快的速度跑起来,所以udf具体实现细节以及原理就不多写了。大家记住下面一句话就可以:继承UDF类,重写evaluate方法,就可以了。

maven打包
上面代码测试通过以后,然后用maven打成jar包。如果是老司机,自然知道怎么做。如果是新司机,我偷偷告诉大家,eclipse里在项目上右击,选择 run as,然后maven install,maven就开始帮你打包了。如果是第一次,maven还会帮你把依赖的jar下载到本地仓库。打包完了以后,你就可以看到target目录下面出现了一个jar包,festival-1.0.jar。OK,这就是我们需要的jar包。

将jar包上传
接下来,我们将前面的jar包上传到服务器上的任何一个位置。比如我就是用scp命令,不多说。

使用udf查询

  1. #!/bin/bash

  2. hive -e "add jar /home/xxx/lei.wang/festival-1.0.jar;
  3.          create temporary function festival as 'festival.FestivalType';
  4.          set mapred.reduce.tasks = 10;
  5.          select cast(a.create_date_wid as int) create_date_wid,sum(a.festival_order_num) from
  6.             (select create_date_wid,festival(create_date_wid,'count') as festival_order_num from ora_dw_rewrite.olap_b_dw_hotelorder_f
  7.                 where create_date_wid>=20160101 and create_date_wid<=20160406)a
  8.             where a.festival_order_num > 0
  9.                 group by a.create_date_wid
  10.                     order by create_date_wid;"
复制代码


为了达到快速上手的目的,直接上代码。
  1. add jar /home/xxx/lei.wang/festival-1.0.jar;
复制代码
这一行是将jar包加进来,后面是你前面上传的路径地址
  1. create temporary function festival as 'festival.FestivalType';
复制代码
创建一个临时函数,用来查询,festival.FestivalType是package名+类名,无需多解释。
后面的查询语句,也不多解释了。有点sql基础的同学,应该都能看明白。

最后查询结果



  1. ..........
  2. Stage-Stage-1: Map: 62  Reduce: 10   Cumulative CPU: 1547.44 sec   HDFS Read: 29040366442 HDFS Write: 1298 SUCCESS
  3. Stage-Stage-2: Map: 8  Reduce: 1   Cumulative CPU: 23.88 sec   HDFS Read: 4608 HDFS Write: 205 SUCCESS
  4. Total MapReduce CPU Time Spent: 26 minutes 11 seconds 320 msec
  5. OK
  6. 20160101        xxx
  7. 20160102        xxx
  8. 20160103        xxx
  9. 20160207        xxx
  10. 20160208        xxx
  11. 20160209        xxx
  12. 20160210        xxx
  13. 20160211        xxx
  14. 20160212        xxx
  15. 20160213        xxx
  16. 20160402        xxx
  17. 20160403        xxx
  18. 20160404        xxx
  19. Time taken: 159.805 seconds, Fetched: 13 row(s)
复制代码


后面的具体数值是公司机密,不能透露,嘻嘻。
稍微注意一点是cast(a.create_date_wid as int),这里因为create_date_wid存的是double类型,如果不做转化的话,会显示成科学计数法,2.0150326E7这种形式。所以为了好看一些,将其强转成int类型。
还有就是这种add的方式的有效期是session级别,就是在此seession有效,session关闭以后就无效。所以如果要将其固话的话,可以加到hive的classpath里头。这样hive启动的时候,就将其加到classpath里了,无需再手动add jar。

结束语
至此,一个完整的hive udf开发过程就结束了。当然还可以开发UDAF,限于时间有限,就不再详细介绍。欢迎有搞数据,算法的志同道合的同学们一起研究讨论






没找到任何评论,期待你打破沉寂

关闭

推荐上一条 /2 下一条