1. 程式人生 > >Hive中文註釋亂碼解決方案(2)

Hive中文註釋亂碼解決方案(2)

seq cut exit sdn 都在 更多 net hdp 通過反射

本文來自網易雲社區

作者:王潘安


執行階段

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+款雲產品!

更多網易研發、產品、運營經驗分享請訪問網易雲社區。


相關文章:
【推薦】 wireshark抓包分析——TCP/IP協議

Hive中文註釋亂碼解決方案(2)