Hive中文註釋亂碼解決方案(2)
本文來自網易雲社區
作者:王潘安
執行階段
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)