要用spark處理一大堆微信日志數據,日志存放在HDFS上,是xml格式,裡面有大量的中文。用scala + java實現了xml的處理邏輯,其中有一步是要獲取xml中的一個title字段,中文。不管怎麼抓取,最終得到的中文都會變成一堆“?????”,亂碼了。從xml中獲取非中文字段,沒有任何問題。也就是說,代碼的邏輯是沒什麼問題的。
直接用Hadoop fs -text或者hadoop fs -cat查看HDFS上的文件,是可以正常顯示的,也就是說HDFS上存放的原數據是好的。那麼就肯定是讀取數據或者處理數據的過程中出了問題。spark on yarn的數據處理,同時涉及了HDFS,App driver, App excutor之間的交互,所以還真沒法一下就判斷出是哪一步傳輸中出了問題。抽絲剝繭,先梳理一遍spark的處理邏輯:
(1) 從HDFS把xml讀取到每個NM上的executor中(spark on yarn環境)
(2) 在executor中對xml進行處理,獲取中文字段。這裡我實現了一個java方法,調用dom來解析xml。
(3) 把解析後的字段collect到driver中,做存儲或者輸出打印等。
(4) 或者把解析後的字段重新存入HDFS
進入Spark-shell,依次驗證這幾個步驟。讀入HDFS上的xml文件,然後直接寫入HDFS,檢查發現字符顯示正常,排除步驟(1)(4)。讀入HDFS上的xml文件,collect到driver中,然後println,字符顯示正常,排除步驟(3)。說明問題出在executor對字段的解析處理過程中。
無論漢字還是英文字符,本質上還是一組字節流,所以出現亂碼,只能是編碼解析出了問題。查看發現,代碼中只有一個地方對xml文件中的字符做了解析,就是這裡:
DocumentBuilder dbBuilder = dbFactory.newDocumentBuilder(); InputStream strm = new ByteArrayInputStream(xmlStream.getBytes()); Document doc = dbBuilder.parse(strm);
把string轉為inputStream的過程。 找到了出問題的位置,下一步就是檢測。
登錄到executor所在的hadoop節點,進入spark-shell, 輸入System.getProperty("file.encoding"),返回”ISO-8859-1“,說明它的默認編碼方式是ISO-8859-1。另一種檢測方法,是定義一個String變量等於一個漢字,然後a.getBytes().length。檢查它的字節數,並推斷對應的字符編碼。UTF8漢字占3個字節,GBK漢字占2個字節。
ISO-8895-1占1字節,用ISO-8895-1的方式把漢字轉成字節流,然後轉回的過程中,肯定會損失一部分數據,所以會亂碼。
問題定位到後,解決就很簡單了。 在所有涉及到字節轉換時,一定要指定編碼方式。類似這樣:
String -> Byte:
string.getBytes("UTF-8")
Byte -> String:
new String(bytes, "UTF-8")