乱码一直就是个很麻烦的事情,项目有个功能需要从hive表获得用户的住址(住址有中文)
jdbc执行(查询select address from user_info_all limit 10)的结果直接插入到mysql表(utf8编码),再查看mysql全是乱码
首先怀疑是hive文件编码有问题,于是用CLI方式查询同一条语句,结果中文显示正常
这说明乱码与hive文件编码没有关系,为了排除SecureCRT编码因素,我写了个测试类——从hive表执行这条语句,输出到控制台的同时也原样输出到文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
publicclassTestReadFromHive{
publicstaticvoidmain(String[]args){
hql="select address from user_info_all limit 10";
Connectioncon=null;
Statementstmt=null;
ResultSetrs=null;
try{
con=getHiveConnection();
stmt=con.createStatement();
rs=stmt.executeQuery(hql);
Writerout=newBufferedWriter(newOutputStreamWriter(newFileOutputStream("/tmp/address.txt")));
while(rs.next()){
Stringaddress=rs.getString("address");
out.write(address+"\n");
System.out.println(address);
}
out.flush();
out.close();
}catch(SQLExceptione){
e.printStackTrace();
}catch(Exceptione){
e.printStackTrace();
}
}
编译后上传到服务器上,编写执行脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/bash
MY_LIB=./lib
MY_JAR=\
$MY_LIB/hive-anttasks-0.7.0-CDH3B4.jar:\
$MY_LIB/hive-cli-0.7.0-CDH3B4.jar:\
#$MY_LIB/hive-jdbc-0.7.0-CDH3B4.jar:\
$MY_LIB/hive-jdbc-0.7.0-CDH3B4.jar:\
$MY_LIB/hive-common-0.7.0-CDH3B4.jar:\
$MY_LIB/hive-metastore-0.7.0-CDH3B4.jar:\
$MY_LIB/hive-contrib-0.7.0-CDH3B4.jar:\
$MY_LIB/hive-serde-0.7.0-CDH3B4.jar:\
$MY_LIB/hive-exec-0.7.0-CDH3B4.jar:\
$MY_LIB/hive-service-0.7.0-CDH3B4.jar:\
$MY_LIB/hive-hbase-handler-0.7.0-CDH3B4.jar:\
$MY_LIB/hive-shims-0.7.0-CDH3B4.jar:\
$MY_LIB/hive-hwi-0.7.0-CDH3B4.jar:\
$MY_LIB/libfb303.jar:\
$MY_LIB/hadoop-core-0.20.2-CDH3B4.jar:\
$MY_LIB/commons-logging-1.0.4.jar:\
$MY_LIB/commons-logging-api-1.0.4.jar:\
$MY_LIB/slf4j-api-1.4.3.jar:\
$MY_LIB/slf4j-log4j12-1.4.3.jar:\
$MY_LIB/log4j-1.2.15.jar
CLASSPATH=\
.:\
$MY_JAR
echo$CLASSPATH
exportCLASSPATH
$JAVA_HOME/bin/java-Xms128m-Xmx128m-Xmn32m-XX:+UseParNewGC-XX:+UseConcMarkSweepGC-XX:+UseTLAB-XX:+CMSIncrementalMode-XX:+CMSIncrementalPacing-XX:CMSIncrementalDutyCycleMin=0-XX:CMSIncrementalDutyCycle=10-Dcrgw.module=testHiveorg.javali.mr.test.TestReadFromHive
执行脚本后,控制台显示乱码,文件内容也乱码,将文件拷贝到本地($sz file)用不同的编辑器打开依旧乱码。
到这一步可以确定 jdbc读取出来的内容已经是乱码了,由于我的服务器系统编码是en_US , 很有可能是hive jdbc驱动没使用utf-8编码,而获取了系统编码进行转码。为了验证这个假设,我在执行脚本添加了export LANG=zh_CN.UTF-8 ,再次执行,中文显示正常了。
我们已经找到问题了,有一种解决办法就是将系统编码或者将运行时环境(Tomcat)的编码改为UTF-8,如果是个新开展的项目,做统一的编码设置无可厚非,如果因为要从hive表取一个中文字段,去改变现有系统运行环境的编码,决不是可取的解决办法,因为这个改动很有可能影响其他模块的可用性。
1
2
3
4
5
6
7
8
9
10
11
12
附件的patch里有版本的对比
Index:jdbc/src/java/org/apache/hadoop/hive/jdbc/HiveQueryResultSet.java
===================================================================
---jdbc/src/java/org/apache/hadoop/hive/jdbc/HiveQueryResultSet.java(revision1195103)
+++jdbc/src/java/org/apache/hadoop/hive/jdbc/HiveQueryResultSet.java(workingcopy)
@@-153,7+153,7@@
StructObjectInspectorsoi=(StructObjectInspector)serde.getObjectInspector();
ListfieldRefs=soi.getAllStructFieldRefs();
-Objectdata=serde.deserialize(newBytesWritable(rowStr.getBytes()));
+Objectdata=serde.deserialize(newBytesWritable(rowStr.getBytes("UTF-8")));
assertrow.size()==fieldRefs.size():row.size()+", "+fieldRefs.size();
for(inti=0;i
rowStr其实就是hive文件的一行数据,包装成ByteWritable没有指定编码,程序默认会使用系统的编码(en_US),这就是造成乱码的根源。
我们可以根据patch的变更来手动指定编码类型,不过这就得重新编译jdbc驱动了。
先获取源码,有两种方式:
1)从apache 的仓库checkout到本地 : http://svn.apache.org/repos/asf/hive/trunk
2)cd $HIVE_HOME/src下同样可以获取源码
新建java project——hive_jdbc,然后new package org.apache.hadoop.hive
从$HIVE_HOME/lib 目录找到以下jar包引入到工程CLASSPATH(hive-jdbc××.jar不需要,我们需要重新编译它)
把hive的jdbc包下源码拷贝到相应的package,有错误不用管,只要保证HiveQueryResultSet.java类能正常编译,定位到该类需要变更的位置将Object data = serde.deserialize(new BytesWritable(rowStr.getBytes()));替换为Object data = serde.deserialize(new BytesWritable(rowStr.getBytes(“UTF-8″)));
把编译好的HiveQueryResultSet.class 替换掉hive-jdbc***.jar包里的旧类,同时为了区分,把包重命名为hive-jdbc-0.7.0-CDH3B4_UTF8.jar
上传到服务器上,替换原来的jar包,再次执行测试脚本,此时无论系统编码是什么,只要SecureCRT客户端编码正确,输出到Console或者File都能正常显示中文了不过获取到address字段后并不能正常显示,需要重新用UTF-8构造一遍
String address = rs.getString(“address”); //直接system.out.println 在console会显示乱码
System.out.println(new String(address.getBytes(“UTF-8″)));
这样才能在console显示正常,直接insert到mysql是能正常显示的
借鉴mysql,我们要做的更好
虽然解决了问题,但出现了UTF-8硬编码,这就限定了只适用于UTF-8编码的场景,如果文件编码为GBK,问题又将来临
我们在使用mysql的jdbc连接时,会在jdbc url加上编码设置:
jdbc\:mysql\://localhost\:3306/mdmy?autoReconnect\=true&useUnicode\=true&characterEncoding\=UTF8
同样一种可配置、更通用的方案就是在hive jdbc url上附加上编码参数,将这个参数带到HiveQueryResultSet类使用它转码
这种方案需要对url连接的解析做下变更,但也不算困难,这个留到以后再实现