Hive中文注释乱码解决方案(2)

本文详细解析了Hive在处理中文注释时出现乱码的问题,并提供了具体的解决方案。通过对Hive源码和Hive-JSON-Serde插件的深入分析,指出了编码格式和注释信息处理不当是导致乱码的主要原因。通过修改Hive源码中的写流编码和Hive-JSON-Serde插件中的注释信息处理逻辑,成功解决了中文注释显示不正确的问题。

本文来自网易云社区

作者:王潘安


执行阶段

launchTask 回到Driver类的runInternal方法,看以下执行过程。在runInternal方法中,执行过程调用了execute方法。execute方法里面的内容很多,但是跟我们有关系的就只有launchTask方法。这个方法里面有这么关键的几步:

     tsk.initialize(conf, plan, cxt);
    TaskResult tskRes = new TaskResult();
    TaskRunner tskRun = new TaskRunner(tsk, tskRes);

    cxt.launching(tskRun);

    tskRun.runSequential();复制代码

跟进runSequential方法发现调用了如下方法:

 exitVal = tsk.executeTask();复制代码

接着跟进,发现执行了这段代码:

     int retval = execute(driverContext);复制代码

这个execute 方法在执行show create table xx命令时就是执行的DDLTask类中的execute方法。

跟进execute方法找到如下代码:

       ShowCreateTableDesc showCreateTbl = work.getShowCreateTblDesc();
      if (showCreateTbl != null) {
        return showCreateTable(db, showCreateTbl);
      }复制代码

查看showCreateTable方法,发现它干的就是把返回结果的字段都拼接成模板,然后把从metastore里面拿到的内容塞进去,最后写到一个临时文件流里面。我们发现,它最后是这样写到文件流的:

 outStream.writeBytes(createTab_stmt.render());复制代码

中文在这个地方估计被写成乱码了,于是把它改为:

     outStream.write(createTab_stmt.render().getBytes("UTF-8"));复制代码

重新编译一下hive:

 mvn clean package -Phadoop-2 -DskipTests复制代码

把编译完成后的hive源码的ql/target目录的hive-exec-1.2.1.jar替换到运行的hive的lib目录中,建一个测试表,不用json序列化反序列化,发现show create table xx命令的字段中文注释正常了。但是如果测试表仍用json序列化和反序列化,那么仍然会出现注释为from deserializer的现象。

我们回到代码,看看在showCreateTable方法中究竟是如何获取字段的注释信息的。找到如下这段代码:

     List<FieldSchema> cols = tbl.getCols();复制代码

跟进去发现,如果设置了自定义的序列化与反序列化类,就会执行这行操作:

 return MetaStoreUtils.getFieldsFromDeserializer(getTableName(), getDeserializer());复制代码

跟进getFieldsFromDeserializer方法,我们发现如下几行重要代码:

 ObjectInspector oi = deserializer.getObjectInspector();
  List<? extends StructField> fields = ((StructObjectInspector) oi).getAllStructFieldRefs();
  for (int i = 0; i < fields.size(); i++) {
    StructField structField = fields.get(i);
    String fieldName = structField.getFieldName();
    String fieldTypeName = structField.getFieldObjectInspector().getTypeName();
    String fieldComment = determineFieldComment(structField.getFieldComment());

    str_fields.add(new FieldSchema(fieldName, fieldTypeName, fieldComment));
  }复制代码

也就是说注释是从deserializer中拿出来的。那我们在返回去看看,hive给我们的json deserializer传了什么参数。返回到上一段代码,我们看getDeserializer方法干了什么:

     deserializer = getDeserializerFromMetaStore(false);复制代码

我们最好在这打个断点,看看,跟进代码发现执行了:

     return MetaStoreUtils.getDeserializer(SessionState.getSessionConf(), tTable, skipConfError);复制代码

然后通过反射建了一个Deserializer的实例,并且调用了它的initialize方法:

       Deserializer deserializer = ReflectionUtil.newInstance(conf.getClassByName(lib).
              asSubclass(Deserializer.class), conf);
       SerDeUtils.initializeSerDeWithoutErrorCheck(deserializer, conf,
                MetaStoreUtils.getTableMetadata(table), null);复制代码

在跟进initializeSerDeWithoutErrorCheck方法,发现它执行了:

 deserializer.initialize(conf, createOverlayedProperties(tblProps, partProps));复制代码

我们在跟进以下MetaStoreUtils.getTableMetadata(table)发现它执行了MetaStoreUtils.getSchema这个方法。跟进去,我们发现了至关重要的代码,注意所有的奥妙都在这:

     for (FieldSchema col : tblsd.getCols()) {
      if (!first) {
        colNameBuf.append(",");
        colTypeBuf.append(":");
        colComment.append('\0');
      }
      colNameBuf.append(col.getName());
      colTypeBuf.append(col.getType());
      colComment.append((null != col.getComment()) ? col.getComment() : "");
      first = false;
    }
    String colNames = colNameBuf.toString();
    String colTypes = colTypeBuf.toString();
    schema.setProperty(
        org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.META_TABLE_COLUMNS,
        colNames);
    schema.setProperty(
        org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.META_TABLE_COLUMN_TYPES,
        colTypes);
    schema.setProperty("columns.comments", colComment.toString());复制代码

也就是说,Hive是给序列化反序列化类传了注释的信息,首先注释的信息是以\0分割的,其次它是放在key值为columns.comments的property中。


Hive-JSON-Serde调试

然后我们就要打开Hive-JSON-Serde看看它怎么处理这些信息的。很容易找到类JsonSerDe。看看它的initialize方法:

         String columnNameProperty = tbl.getProperty(Constants.LIST_COLUMNS);
        String columnTypeProperty = tbl.getProperty(Constants.LIST_COLUMN_TYPES);复制代码

它根本就没有拿注释信息!然后看它怎么生成的rowObjectInspector:

 rowObjectInspector = (StructObjectInspector) JsonObjectInspectorFactory
                .getJsonObjectInspectorFromTypeInfo(rowTypeInfo, options);复制代码

跟入getJsonObjectInspectorFromTypeInfo方法,找到:

                     result = JsonObjectInspectorFactory.getJsonStructObjectInspector(fieldNames,
                            fieldObjectInspectors, options);复制代码

接着跟进去,发现:

             result = new JsonStructObjectInspector(structFieldNames,
                    structFieldObjectInspectors, options);复制代码

我们看看这个JsonStructObjectInspector类,它是继承的StandardStructObjectInspector,它的构造函数调用了父类的:

   protected StandardStructObjectInspector(List<String> structFieldNames,
      List<ObjectInspector> structFieldObjectInspectors) {
    init(structFieldNames, structFieldObjectInspectors, null);
  }复制代码

一看init函数最后传入的参数是null,就知道问题出在这了,这个父类其实还有另外一个构造方法:

   protected StandardStructObjectInspector(List<String> structFieldNames,
      List<ObjectInspector> structFieldObjectInspectors,
      List<String> structFieldComments) {
    init(structFieldNames, structFieldObjectInspectors, structFieldComments);
  }复制代码

也就是说,它是允许传入注释信息的。那么我们的思路就明确了,第一,把未解析出来的注释信息解析出来。第二,把这个注释信息传入JsonStructObjectInspector的构造函数中:

 String columnCommentProperty = tbl.getProperty("columns.comments");
 if (columnCommentProperty != null){
        if (columnCommentProperty.length() == 0) {
            columnComments = new ArrayList<String>();
        } else {
            columnComments = Arrays.asList(columnCommentProperty.split("\0", columnNames.size()));
        }
 }复制代码

这里有一点要注意:在StandardStructObjectInspector类中,它会强制检查字段数与注释数相等,所以在做split操作时,一定要传2个参数,把注释为空的字段补全,否则要出bug。后面的操作就是把这个注释传参到各个函数中去,这里就不在多述。

然后把JsonStructObjectInspector的构造函数改为:

       public JsonStructObjectInspector(List<String> structFieldNames,
            List<ObjectInspector> structFieldObjectInspectors, List<String> structFieldComments, JsonStructOIOptions opts) {
        super(structFieldNames, structFieldObjectInspectors, structFieldComments);
        
        options = opts;
    }复制代码

最后,重新编译Hive-JSON-Serde:

 mvn -Phdp23 clean package复制代码

这段代码改动比较多。


3.总结

其实Hive中文注释乱码就两个原因造成的。一个是Hive在写注释到流中时,没有把编码格式转为UTF-8。在第三方插件Hive-JSON-Serde中,没有将注释保存下来,如果注释为空,Hive会自动补上from deserializer的字符串。

  因此只需要改动下面一小点即可,首先在Hive的源码中,找到ql目录,找到org.apache.hadoop.hive.ql.exec中的DDLTask类,找到showCreateTable方法。修改第2110行的代码:

 outStream.writeBytes(createTab_stmt.render());复制代码

为:

     outStream.write(createTab_stmt.render().getBytes("UTF-8"));复制代码

  对于Hive-JSON-Serde来说,则是在它的JsonSerDe类的initialize方法中加入解析字段注释的代码:

  String columnCommentProperty = tbl.getProperty("columns.comments");
 if (columnCommentProperty != null){
        if (columnCommentProperty.length() == 0) {
            columnComments = new ArrayList<String>();
        } else {
            columnComments = Arrays.asList(columnCommentProperty.split("\0", columnNames.size()));
        }
 }复制代码

并且在构建rowObjectInspector的时候将注释信息传入:

         rowObjectInspector = (StructObjectInspector) JsonObjectInspectorFactory
                .getJsonObjectInspectorFromTypeInfo(rowTypeInfo, columnComments, options);复制代码

然后把JsonStructObjectInspector的构造函数改为:

        public JsonStructObjectInspector(List<String> structFieldNames,
            List<ObjectInspector> structFieldObjectInspectors, List<String> structFieldComments, JsonStructOIOptions opts) {
        super(structFieldNames, structFieldObjectInspectors, structFieldComments);
        
        options = opts;
    }复制代码



相关阅读:Hive中文注释乱码解决方案(1)


网易云免费体验馆,0成本体验20+款云产品!

更多网易研发、产品、运营经验分享请访问网易云社区


相关文章:
【推荐】 【专家坐堂】四种并发编程模型简介
【推荐】 网易美学-系统架构系列1-分布式与服务化


<think>好的,我现在需要解决用户提出的Hive字段注释乱码的问题。首先,我得回忆一下Hive的相关知识,尤其是元数据存储和字符编码方面的内容。用户提到的是字段注释乱码,可能涉及到Hive的元数据库,比如MySQL的设置问题。 首先,Hive的元数据通常存储在关系型数据库中,比如MySQL。如果注释出现乱码,很可能是数据库的字符编码设置不正确。这时候需要检查MySQL的字符集配置,确保是UTF-8。常见的需要检查的参数包括character_set_server、character_set_database等。如果这些设置不正确,存储中文或其他非拉丁字符时就会出现乱码。 接下来,可能还需要检查Hive本身的配置。比如,Hive在创建表时使用的元数据存储是否支持UTF-8。此外,当使用ALTER TABLE语句修改字段注释时,如果客户端工具的编码设置不正确,也可能导致乱码。例如,终端或客户端工具的默认编码如果不是UTF-8,输入的中文字符可能在传输过程中被错误编码。 另外,元数据库的表结构也需要确认。Hive的元数据库中有一些表,比如COLUMNS_V2,里面的COMMENT字段是否使用了正确的字符集。如果这些表原本是Latin1编码,存储UTF-8数据就会有问题。这时候可能需要修改表的字符集,并且转换现有数据的编码。 处理步骤可能包括:首先检查并修改MySQL的全局字符集配置,然后修改元数据库的字符集,接着修改相关表的字符集,最后修复已有数据。需要注意的是,直接修改数据库字符集可能会有风险,尤其是在生产环境中,需要备份数据。 此外,用户可能在修改后需要重新导入或更新注释,以确保新数据使用正确的编码。还可能涉及到Hive服务的重启,或者元数据存储的刷新,但我不太确定是否需要,可能需要进一步确认。 还需要考虑客户端的编码设置,比如使用Beeline或Hive CLI时,终端的编码是否支持UTF-8。如果客户端编码不对,即使元数据正确,显示时也会乱码。这时候需要配置客户端的环境变量,比如LANG或者LC_ALL为UTF-8编码。 总结下来,解决Hive字段注释乱码的问题需要从元数据库的字符集配置、Hive的元数据表结构、客户端编码等多个方面进行检查和调整。需要逐步排查,确定问题根源,然后进行相应的修改和测试。</think>Hive字段注释出现乱码通常由元数据库字符集不兼容导致,以下是系统级解决方案: **根因分析:** Hive元数据存储于RDBMS(如MySQL)时,若数据库/表/字段未使用UTF-8编码,存储中文字符时会产生乱码。常见于以下场景: 1. MySQL服务端字符集配置非UTF-8 2. 元数据库创建时未指定UTF-8 3. Hive表字段注释包含非ASCII字符 **修复方案:** ```sql -- 1. 修改MySQL全局配置 (需重启服务) [mysqld] character-set-server=utf8 collation-server=utf8_general_ci -- 2. 修改Hive元数据库编码 ALTER DATABASE metastore CHARACTER SET utf8; -- 3. 修正关键表编码(示例) ALTER TABLE COLUMNS_V2 MODIFY COMMENT varchar(256) CHARACTER SET utf8; ALTER TABLE TABLE_PARAMS MODIFY PARAM_VALUE varchar(4000) CHARACTER SET utf8; ``` **数据修复操作:** ```bash # 导出元数据 mysqldump -uroot -p metastore > metastore_backup.sql # 批量转换编码 iconv -f latin1 -t utf-8 metastore_backup.sql > metastore_utf8.sql # 重建数据库并导入 mysql -e "CREATE DATABASE metastore CHARACTER SET utf8" mysql metastore < metastore_utf8.sql ``` **验证步骤:** ```sql -- 创建测试表 CREATE TABLE test_comment ( id INT COMMENT '测试注释' ); -- 查询验证 DESC FORMATTED test_comment; /* 正确输出应显示中文注释 */ ``` **高级配置:** ```xml <!-- hive-site.xml 增加元数据编码声明 --> <property> <name>javax.jdo.option.ConnectionURL</name> <value>jdbc:mysql://hive-mysql:3306/metastore?useUnicode=true&characterEncoding=UTF-8</value> </property> ``` **注意事项:** 1. 生产环境操作前必须备份元数据库 2. 修改字符集可能导致已有注释需要重新录入 3. 需验证HiveServer/HiveMetastore服务的JVM文件编码参数: ```bash -Dfile.encoding=UTF-8 ``` 4. 客户端工具(如Hue、DBeaver)需同步配置UTF-8编码 该方案完整覆盖从底层存储到上层应用的字符集配置体系,可彻底解决Hive元数据中文乱码问题。执行后需验证历史表注释和新创建表注释的显示一致性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值