工作中遇到通过jdbc连接hive服务器(我们是用HiveServer2),获取的中文是乱码的问题。使用beeline有同样的效果,而用hive命令行却能正常显示中文。而写入,读取的文件,都是用的UTF-8,Java环境也都是UTF-8字符集。这是怎么回事?
我们可以以如下方式重现:
shell> cat upload.txt
row1
行2
行3
row4
hive中在default库中创建表str_test(v string),然后将upload.txt文件上传到该表的存储路径:
shell> hadoop fs -put upload.txt /user/hive/warehouse/str_test/
shell> beeline -d org.apache.hive.jdbc.HiveDriver -u jdbc:hive2://localhost:10000 -e “select * from str_test”
…
+——-+
| v |
+——-+
| row1 |
| ?2 |
| ?3 |
| row4 |
| |
+——-+
shell> hive -e “select * from str_test”
…
row1
行2
行3
row4
出现乱码时,交互方式是:Client->jdbc->Hive Server2->Hadoop,而正常显示的Hive命令行的交互是:Hive CLI->Hadoop。所以基本可以确定乱码问题在Client->jdbc->Hive Server2这三个部分的其中一个部分上。
Client是我们自行开发的Java应用,经查整个项目一直都是用的UTF-8字符集,排查Client的编码问题。
网上查询一番类似的问题,参考了 文章1和2后,我们初步认为,采用网上推荐的方案,更新hive的JDBC驱动程序,将获取的字符强制硬编码使用UTF-8字符集,可以解决我们的问题。
我们使用的驱动包是hive-jdbc-0.10.0-cdh4.5.0.jar,可以看到里面有两个包:
我们按照网上的说法,更新org.apache.hadoop.hive.jdbc.HiveResultSet,采用如下patch:
Index: jdbc/src/java/org/apache/hadoop/hive/jdbc/HiveQueryResultSet.java
===================================================================
— jdbc/src/java/org/apache/hadoop/hive/jdbc/HiveQueryResultSet.java (revision 1195103)
+++ jdbc/src/java/org/apache/hadoop/hive/jdbc/HiveQueryResultSet.java (working copy)
@@ -153,7 +153,7 @@
StructObjectInspector soi = (StructObjectInspector) serde.getObjectInspector();
List fieldRefs = soi.getAllStructFieldRefs();
- Object data = serde.deserialize(new BytesWritable(rowStr.getBytes()));
+ Object data = serde.deserialize(new BytesWritable(rowStr.getBytes(“UTF-8″)));
assert row.size() == fieldRefs.size() : row.size() + “, ” + fieldRefs.size();
for (int i = 0; i < fieldRefs.size(); i++) {
这个patch强制将获取的内容强制使用UTF-8编码解码。修改jar的方式是
按照如上方式处理后,发现依然无效。不仅如此,我们调整LOG为DEBUG(即打印出大量DEBUG日志),然后尝试各种方法:加入打印语句、修改输出内容、甚至整个删除jar中的org.apache.hadoop.hive.jdbc包等等,最终发现,Java程序调用Hive JDBC时根本没进入org.apache.hadoop.hive.jdbc中,即使删除org.apache.hadoop.hive.jdbc这个包都没影响,生效的是org.apache.hive.jdbc包中的内容!
在org.apache.hive.jdbc包中没有找到相似的serde.deserialize这样的反序列化的语句。看来网上的方案在我们这不适用。那么这两个包是什么用途,什么关系呢?
从HiveServer2 Clients中,一段话提醒了我们:
The JDBC connection URL format has the prefix jdbc:hive2:// and the Driver class is org.apache.hive.jdbc.HiveDriver. Note that this is different from the old HiveServer.
再看第一版本的HiveServer Client,使用的是:
driverName = “org.apache.hadoop.hive.jdbc.HiveDriver“;
看到这两个包,在比对我们疑惑的两个包,一切明朗了:
细查org.apache.hive.jdbc的类,发现这个版本与HiveServer1的处理完全不一样了,不是使用serde,还是直接用了thrift,其中也没找到编码相关的选项。这条路走到头了。
剩下就是检查HiveServer2使用的字符集了。
系统层面:
shell> locale
LANG=zh_CN.UTF-8
LC_CTYPE=”zh_CN.UTF-8″
LC_NUMERIC=”zh_CN.UTF-8″
LC_TIME=”zh_CN.UTF-8″
LC_COLLATE=”zh_CN.UTF-8″
LC_MONETARY=”zh_CN.UTF-8″
LC_MESSAGES=”zh_CN.UTF-8″
LC_PAPER=”zh_CN.UTF-8″
LC_NAME=”zh_CN.UTF-8″
LC_ADDRESS=”zh_CN.UTF-8″
LC_TELEPHONE=”zh_CN.UTF-8″
LC_MEASUREMENT=”zh_CN.UTF-8″
LC_IDENTIFICATION=”zh_CN.UTF-8″
LC_ALL=
shell> echo $LANG
zh_CN.UTF-8
我们是使用CDH4.5中自带的hive-server2启动脚本启动的hiveserver2服务,看看脚本环境中字符集是怎样的。
/etc/init.d/hive-server2中添加locale命令,重启后却是这样的:
shell> service hive-server2 restart
LANG=
LC_CTYPE=”POSIX”
LC_NUMERIC=”POSIX”
LC_TIME=”POSIX”
LC_COLLATE=”POSIX”
LC_MONETARY=”POSIX”
LC_MESSAGES=”POSIX”
LC_PAPER=”POSIX”
LC_NAME=”POSIX”
LC_ADDRESS=”POSIX”
LC_TELEPHONE=”POSIX”
LC_MEASUREMENT=”POSIX”
LC_IDENTIFICATION=”POSIX”
LC_ALL=
Starting Hive Server2 (hive-server2): [确定]
Hive Server2 is running [确定]
看来hive-server2的运行环境并没有沿用系统层面的环境,针对它进行配置:
在/etc/init.d/hive-server2删除刚添加的locale,然后添加语句:export LANG=zh_CN.UTF-8
这样确保hive-server2的启动会使用指定字符集。
当然,如果使用hive –service hiveserver2命令启动,只要保证环境变量LANG=zh_CN.UTF-8即可。
测试结果,如上设置,重启hive-server2后,乱码问题解决!
最终解决乱码问题只是添加了export LANG=zh_CN.UTF-8,确保hive-server2的运行环境使用的是UTF-8字符集,这样Client,Hive-Server和Hadoop集群都采用了UTF-8,自然不会有乱码存在了。
Done!
参考:
http://wocclyl.blog.163.com/blog/static/4622350420134301120474/