Hive系列-unix_timestamp 问题

问题描述

环境测试集群
服务器系统版本:centos 7.2
cdh 版本:cdh5.9.0
hadoop 版本:2.6.0+cdh5.9.0
hive 版本:1.1.0+cdh5.9.0
nodeManger 节点:cdh003,cdh004, cdh005, cdh006
HiveServer2 节点:cdh001, cdh003,cdh004, cdh005, cdh006

需求:xxx_detail_incr_表的数据根据create_time字段按天分区导入xxx_detail_incr_par_表

mongodb同步数据到hive,按天分区时调用hive函数unix_timestamp 将时间字符串create_time=Sun May 20 23:59:21 CST 2018 格式化成 create_date=2018-05-20,格式化后的字符串当分区字符串,同样的sql,有时候跑任务格式化成功,有失败。效果十分诡异。

转换SQL如下:

INSERT OVERWRITE TABLE xxx_detail_incr_par_ PARTITION (create_date) 
SELECT *,from_unixtime(unix_timestamp(create_time,'EEE MMM dd HH:mm:Ss z yyyy'),'yyyy-MM-dd') AS create_date 
FROM xxx_detail_incr_;

查看是否成功:

show partitions xxx_detail_incr_par_;

成功时候:
Hive系列-unix_timestamp 问题_第1张图片

失败时候:
这里写图片描述


问题分析

这里用了hive的两个时间函数 from_unixtime,unix_timestamp 估计是这两个刁民在害朕。

create_time的字符串中包含特殊字符

使用hive regexp_replace 函数将不是英文、数字、空格的字符串替换掉。

INSERT OVERWRITE TABLE xxx_detail_incr_par_ PARTITION (create_date) 
SELECT *,from_unixtime(unix_timestamp(regexp_replace(regexp_replace(create_time,'[^:a-zA-Z0-9 \s]+',''),'\\s+',' '),'EEE MMM dd HH:mm:Ss z yyyy'),'yyyy-MM-dd') AS create_date 
FROM xxx_detail_incr_;

重新执行,成功了,所有的分区都有。心中窃喜! 为了稳,多次执行,第二次执行的时候失败。
不是特殊字符。。

hiveserver2 问题

只留cdh004中的hiveserver2将集群中多余的停掉。多次执行,时间字符串个格式化有成功有失败。
不是hive server2 问题??

hive 转 mr job 时候问题

只留cdh003中的nodeManger 别的停掉。多次执行,时间字符串个格式化有全部失败。
问题出现了。。
依次单独只留一个nodeManger依次校验,发现
cdh003,cdh006 的 nodeManger 有问题启动的任务有问题。
cdh004,cdh005的 nodeManger 有问题启动的任务ok。
多次验证 hiveserver2 nodeManger 、服务器任务成功与失败的关系的得出
cdh003,cdh006 的机器环境和cdh004,cdh005 环境有差异。
网上找到unix_timestamp/from_unixtime 格式化时间是调用JAVA的SimpleDateFormat时间时间格式化的。
猜测 机器的时区不一致 ,语言不一致,检查发现时区ok,语言不一致。

Hive系列-unix_timestamp 问题_第2张图片
Hive系列-unix_timestamp 问题_第3张图片

心中窃喜,以为联系运维改系统语言。重启集群,重跑任务,问题解决。然并卵用,问题依旧。。。。

猜想定问题是因为本地化Locale的不同导致SimpleDateFormat转换失败,于是本地做了个小实验。

本地SimpleDateFormat实验

SimpleDateFormat使用默认Locale(中文)转换”Tue May 20 22:41:01 CST 2014”,报错
Hive系列-unix_timestamp 问题_第4张图片

SimpleDateFormat使用Locale.ENGLISH转换,ok
Hive系列-unix_timestamp 问题_第5张图片

看到这里问题貌似出来了,估计是服务器的默认Locale值存在差异。于是乎,撸起袖子就干。

服务器SimpleDateFormat实验

分别在服务器cdh003,cdh004 执行,代码,为了确定问题,指定使用coudera用的jdk版本:

/usr/java/jdk1.7.0_67-cloudera/bin/javac -d . LanTest.java && /usr/java/jdk1.7.0_67-cloudera/bin/java LanTest

Hive系列-unix_timestamp 问题_第6张图片

然而的出来的结果出人意料,cdh003,cdh004结果都一样!!不应该啊,不应该啊,真的不应该这样子的。。

这里写图片描述

百思不得其解,郁闷。。。

SimpleDateFormat没有设定时区是会使用默认的操作系统的locale配置
Java 官方的说明:http://www.oracle.com/us/technologies/java/locale-140624.html

github翻阅hive源码

没办法了,是能去挖坟了,默默打开github,找出hive udf 实现的源码。
源码地址:里面有hive的所有udf源码实现 我用的版本是1.1所以看了1.1分支的代码

from_unixtime 函数的实现类 UDFFromUnixTime ,截图只是重点部分截图

Hive系列-unix_timestamp 问题_第7张图片

源码中发现from_unixtime的实现中并没有指定Locale,跟我猜想的一样,不指定Locale是正常的啦,必将全世界都在用嘛。。

再看看unix_timestamp函数的实现类 GenericUDFUnixTimeStamp ,这哥们的代码稍等惊人。
Hive系列-unix_timestamp 问题_第8张图片

然而这里并没有任何SimpleDateFormat的身影,别慌看父GenericUDFToUnixTimeStamp,熟悉的身影

Hive系列-unix_timestamp 问题_第9张图片

GenericUDFToUnixTimeStamp的转换不走大致可以分为2个步骤:
1、根据输入的不同类型转成不同类型的ObjectInspector

Hive系列-unix_timestamp 问题_第10张图片

2、根据不同类型的ObjectInspector格式化字符串

Hive系列-unix_timestamp 问题_第11张图片

源码中发现unix_timestamp的实现中并没有指定Locale,还是那就老话,必将全世界都在用嘛,不指定是正常的啦。

unix_timestamp、from_unixtime 实现中都没有指定Locale,hive 跑 mr job 时候获得的Locale值跟我在服务器上跑测试例子 LanTest.java 一样的才对,这里为什么不一样呢?无解,真的无解,有被坑过的同学可以告诉下我吗?


问题原因

跟运维沟通确认后发现,服务集群中由于不同批次上线的机器的配置略不同,有些机器装机的时候选择的默认语言是中文的,有的是英文的,后来然统一改成了英文,这样会引发java的一些默认环境有说差异。由于java 默认解析的默认Locale 存在差异,有些机器使用中文,hive 的unix_timestamp函数,使用SimpleDateFormat 格式化这样的”Sun May 20 23:59:21 CST 2018”时间字符串时会报错。
但是,机器已经改已经修改成英文的,为什么hive 跑 mr job 时候获得的Locale值还是获取到原来的中文??????无解,真的无解,有被坑过的同学可以告诉下我吗?


解决方案

终极大法 :hiveServer2,mapreduce.map.java.opts,mapreduce.reduce.java.opts 添加 -Duser.language=en 指定语言

这里写图片描述

Hive系列-unix_timestamp 问题_第12张图片

重启服务器之言,多次校验通过,世界和平,稳稳的。。


总结

  1. 服务集群中由于不同批次上线的机器的默认语言配置略不同,引发了本次血案
  2. 建议后又的服务器机器环境尽可能相同,默认语言,时区,jdk版本 等
  3. 不要一味的想着让机器的软环境适应jvm,能用-D指定的默认值尽可能用-D指定

你可能感兴趣的:(hive系列)